字符设备驱动框架
前两篇文章我们简单的介绍了linux字符设备驱动的编写流程,但是总觉得不得劲,毕竟只知道了调用那些函数,而不知道这些函数背后做了什么事情,这篇文章我们简要的分析一下系统调用的过程,它是怎么调用到我们底层的操作函数的。
我们简单的回顾一下字符设备驱动框架:
看完这个框架我们抛出一下几个问题:
- 每一个设备都有自己的底层操作函数;也就是说每一个设备都对应着自己的file_operations结构体;
- 那么打开的文件file->f_op指针应该指向那一个file_operations结构体呢?或者说 file->f_op怎样找到对应的file_operations呢?
字符设备驱动框架分析
前面我们说过设备号的注册或自动分配函数是下面两个:
alloc_chrdev_region()
register_chrdev_region()
而这两个函数都会调用__register_chrdev_region()
函数;这个函数完成如下操作:
- 如果是自动分配设备号,在全局数组chrdevs[ ]中找到一个空位,这个空位的数组下标就是主设备号;设置这个空位
- 如果是直接注册设备号,以主设备号为下标,找到数组chrdevs[ ]中的成员,设置这个成员;
- 全局数组chrdevs[]当前的成员数为255,下标从0-254,也就是说,当前内核的字符设备主设备号范围 0-254.
注册cdev分析
我们回顾一下cdev注册的步骤:
- 初始化cdev结构
-
注册
下面我们分析一下cdev_add()函数,他到底做了哪些事情:
从上面的分析我们可以得出以下结论:
- 内核使用一个数据结构cdev_map来记录系统中所有的字符设备(cdev);
- 而设备号的维护是由全局chrdevs[]来完成的;
- 在驱动模块入口函数中:
- 申请设备号:就是在数组chrdevs[]中找一个空位;
- 注册字符设备:就是将cdev添加到cdev_map中;
以上流程可以在linux/char_dev.c文件中分析得到
open系统调用分析
open系统调用的实现在fs/open.c文件中
-
open系统调用的简要流程如下:
- 在上图中,do_filp_open()函数经过重重调用,会调用vfs_select_inode 和do_dentry_open,他的调用流程简化代码如下:
do_filp_open()
path_openat
do_last
vfs_open
inode = vfs_select_inode //获取文件的inode信息
do_dentry_open
f->f_op = fops_get(inode->i_fop);
//将inode->i_fop拷贝给f->f_op
open = f->f_op->open;
//调用inode->i_fop->open
open(inode, f);
从上面的分析我们可以知道:
- inode对应一个文件;我们在创建一个设备文件时,就是创建一个inode;
- 在用户空间创建设备文件时,对应的inode->i_fop指向fs/char_dev.c中的def_chr_fops,def_chr_fops的成员open指向chrdev_open()函数。
下面我们看一下open系统调用的层次关系:
分析chardev_open函数
从上面的分析可以很清晰的知道了系统调用到内核操作函数的整个流程,下面我们做一下简单的总结: