char device driver

1. 什么是字符设备驱动

Linux将设备分为三大类:字符设备、块设备和网络设备。字符设备是其中较为基础的一类,它的读写操作需要一个字节一个字节地进行,无法随机读取设备中的某一字节的数据。比较常见的字符设备有鼠标、键盘、串口等。

2. char device driver internal

2.1 数据结构

Linux 使用 cdev 结构体来表示一个字符设备

1
2
3
4
5
6
7
8
struct cdev {
struct kobject kobj;
struct module *owner;
const struct file_operations *ops;
struct list_head list;
dev_t dev;
unsigned int count;
} __randomize_layout;

kojb 不必多说,嵌入在 cdev 中,从而构成设备间的层次关系。

其中,dev_t 表示设备号,一共有 32 bit,其中 24 bit 为主设备号,8 bit 为次设备号,可以通过下面的宏快速操作

1
2
3
#define MAJOR(dev)	((dev)>>8)
#define MINOR(dev) ((dev) & 0xff)
#define MKDEV(ma,mi) ((ma)<<8 | (mi))

另一个关键成员 ops 则定义了字符设备驱动提供给虚拟文件系统的接口函数。

2.2 设备初始化

每种设备都需要分配空间+初始化之后才能使用,字符设备也不例外,分别对应 cdev_alloc 和cdev_device_add。

1
2
3
4
5
6
7
8
9
struct cdev *cdev_alloc(void)
{
struct cdev *p = kzalloc(sizeof(struct cdev), GFP_KERNEL);
if (p) {
INIT_LIST_HEAD(&p->list);
kobject_init(&p->kobj, &ktype_cdev_dynamic);
}
return p;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
int cdev_device_add(struct cdev *cdev, struct device *dev)
{
int rc = 0;

if (dev->devt) {
cdev_set_parent(cdev, &dev->kobj);

rc = cdev_add(cdev, dev->devt, 1);
if (rc)
return rc;
}

rc = device_add(dev);
if (rc)
cdev_del(cdev);

return rc;
}

同时需要注意,调用 cdev_device_add 添加设备之前,还需要通过 register_chrdev_region 申请设备号,从而赋予该设备唯一的标识

2.3 文件系统 API

file_operations 中的回调函数是字符设备驱动的关键,这些函数会在虚拟文件系统的 open,read,write,close 等操作时被内核回调。

下面通过一些具体的例子来说明字符设备驱动应该如何设置 file_operations

2.3.1 tty

1
2
3
4
5
6
7
8
9
10
11
12
static const struct file_operations tty_fops = {
.llseek = no_llseek,
.read = tty_read,
.write = tty_write,
.poll = tty_poll,
.unlocked_ioctl = tty_ioctl,
.compat_ioctl = tty_compat_ioctl,
.open = tty_open,
.release = tty_release,
.fasync = tty_fasync,
.show_fdinfo = tty_show_fdinfo,
};

本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!