【学习笔记】2~字符设备驱动的实现原理
本文写的什么:
Linux内核按照主要功能划分主要分为进程管理、内存管理、文件系统、设备驱动,其他的诸如网络、时钟、进程间通信也是内核的主要组成部分,但是不能算是基础设施吧(其实也不对,网络在这个年代也算是基础设施了)。
前面写了Linux内核的功能划分,实在是从代码角度分析进程管理、内存管理、文件系统、设备驱动 这四部分密不可分。对于一个简单的字符设备驱动程序而言,并不只是熟悉设备驱动开发就可以了,因为借助于“万物皆文件”的概念,设备驱动其实也是作为文件系统的一个特例,而文件系统的管理又是和进程密不可分的,而且在具体的操作过程中离不开memalloc(不是c标准的内存申请操作了,而是内核自己的内存申请操作了)。
一个设备驱动程序所涉及的不外乎以下几种操作:设备注册,打开设备,读设备,写设备,操作设备(字符设备好像没见过ioctrl相关的)。
设备注册:
首先,在linux内核里,主设备号标识一类设备(这类设备有一个统一的设备驱动程序,那不同字符设备操作不一样怎么办?设备驱动程序里面依赖于次设备号找到对应的函数指针,参考def_chr_fops)。而为了根据设备号快速获得字符设备及其驱动程序的函数指针,所以内核提供了cdev_add函数把我们创建的字符设备添加到一个cdev_map这个HASH表里面。简单上个图,这块儿内核是采用了kobject机制添加的。
C 代码
01 |
int cdev_add(struct cdev *p, dev_t dev, unsigned count) |
02 |
{ |
03 |
int error; |
04 |
05 |
p->dev = dev; |
06 |
p->count = count; |
07 |
08 |
error = kobj_map(cdev_map, dev, count, NULL, |
09 |
exact_match, exact_lock, p); |
10 |
if (error) |
11 |
return error; |
12 |
13 |
kobject_get(p->kobj.parent); |
14 |
15 |
return 0; |
16 |
} |
设备添加的时候,还要告诉内核当前设备驱动程序的read\write回调接口:
C 代码
1 |
cdev_init(&(dev->dev), &hello_fops); |
2 |
dev->dev.owner = THIS_MODULE; |
3 |
dev->dev.ops = &hello_fops; |
4 |
5 |
/*注册字符设备*/ |
6 |
err = cdev_add(&(dev->dev),devno, 1); |
真正创建设备的操作上面还没写出来,上面只是按照内核提供的框架给出了各种文件操作的回调函数。我们还得在文件系统中看到/dev/hello才行。
C 代码
01 |
/*在/sys/class/目录下创建设备类别目录hello*/ |
02 |
hello_class = class_create(THIS_MODULE, HELLO_DEVICE_CLASS_NAME); |
03 |
if(IS_ERR(hello_class)) { |
04 |
err = PTR_ERR(hello_class); |
05 |
printk(KERN_ALERT"Failed to create hello class.\n"); |
06 |
goto destroy_cdev; |
07 |
} |
08 |
09 |
/*在/dev/目录和/sys/class/hello目录下分别创建设备文件hello*/ |
10 |
temp = device_create(hello_class, NULL, dev, "%s", HELLO_DEVICE_FILE_NAME); |
11 |
if(IS_ERR(temp)) { |
12 |
err = PTR_ERR(temp); |
13 |
printk(KERN_ALERT"Failed to create hello device."); |
14 |
goto destroy_class; |
15 |
} |
最后内核会调用到:
C 代码
01 |
if (MAJOR(dev->devt)) { |
02 |
error = device_create_file(dev, &dev_attr_dev); |
03 |
if (error) |
04 |
goto ueventattrError; |
05 |
06 |
error = device_create_sys_dev_entry(dev); |
07 |
if (error) |
08 |
goto devtattrError; |
09 |
10 |
devtmpfs_create_node(dev); |
11 |
} |
12 |
***通过uevent事件通知udev这个守护程序创建dev目录。 |
13 |
kobject_uevent(&dev->kobj, KOBJ_ADD); |
uevent来负责完成mknod自动创建设备节点
mknod会触发ramfs系统调用创建inode
C 代码
1 |
error = dir->i_op->mknod(dir, dentry, mode, dev);即为ramfs_dir_inode_operations->mknod |
2 |
3 |
ramfs_mknod(struct inode *dir, struct dentry *dentry, umode_t mode, dev_t dev) |
4 |
{ |
5 |
struct inode * inode = ramfs_get_inode(dir->i_sb, dir, mode, dev);//新建立一个inode节点并和设备信息绑定 |
6 |
int error = -ENOSPC;<br> |
打开设备:
打开设备,就和打开文件一样了,关键是需要找到inode和file到device的转换操作
字符设备的inode是ramfs在mknod时生成的,do_sys_open() 系统调用会根据文件名找到inode并创建file实例(对应文件描述符)。找到了inode也就找到了file_operations,因为在创建设备时候,我们会把file_operations的值赋值到inode里面。

一个全景图如下:
![]()

读、写、操作字符设备:
用户空间对字符设备的后续操作,比如read、write和ioctl等,将通过open函数返回的fd找到对应的filp(这玩意儿说白了就是我们的文件描述符),然后调用filp->f_op中实现的各类字符设备操作函数

浙公网安备 33010602011771号