【学习笔记】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中实现的各类字符设备操作函数
posted @ 2019-01-04 09:01  SoftAndHardMan  阅读(292)  评论(0)    收藏  举报