学习Linux系统下驱动程序开发已有大半年时间,心中一直有个疑惑:那就是诸如open、write、read等系统调用是怎么和
内核或底层驱动建立起联系的呢?今天将自己的一些粗略的理解总结如下。
学过Linux系统下驱动程序开发的都知道,大部分的基础性的驱动操作都包括3个重要的内核数据结构,称为file_operations,
file,和inode。我们需要对这些结构有个基本了解才能够做大量感兴趣的事情。
1、struct file_operations是一个把字符设备驱动的操作和设备号联系在一起的纽带,是一系列指针的集合,每个被打开的文件
都对应于一系列的操作,这就是file_operations,用来执行一系列的系统调用。
2、struct file代表一个打开的文件,在执行file_operation中的open操作时被创建,这里需要注意的是与用户空间inode指针的区
别,一个在内核,而file指针在用户空间,由c库来定义。
3、struct inode被内核用来代表一个文件,注意和struct file的区别,struct inode一个是代表文件,struct file一个是代表打开的文件
struct inode 包括很重要的两个成员:
dev_t i_rdev 设备文件的设备号
struct cdev *i_cdev 代表字符设备的数据结构,struct inode结构是用来在内核内部表示文件的。同一个文件可以被打开好多
次,所以可以对应很多struct file,但是只对应一个struct inode.
应用层调用open函数,首先会发出open系统调用,然后进入 内核,调用sys_open函数,打开文件系统中的/dev/fs文件,这个文件要么
你是用mknod建立的,要么就直接在内核中用devfs方式来建立的,无论你用哪种方式建立,最终都会读取其文件属性,如果发现其是设备
文件,就会调用LINUX内核中的设备管理部分,根据其属性的主设备号(在建立设备节点时已经把主设备号写入文件属性当中,如果你仔细
看过mknod的用法就明白了),查找内核中相关联的file_operations,最终找到你的test_open函数。
所以说 "在LINUX中设备即是文件 ",设备驱动首先会走文件系统这一条路(比如最先的 "/dev/fs "),然后根据设备文件的属性最终找
到相关联的file_operations,从而调用你的设备驱动例程.假如发现这个文件不是设备文件而只是磁盘文件,就继续走文件系统,高速缓冲
与磁盘调度这一条路了。
sys_open(内核函数)就直接到VFS层了,open过程中会得到设备文件的inode(通过传进来的文件名参数),(参考我的博文里
关于inode介绍),其file_operations结构体会赋给file结构体(含此成员fops)中相应成员,且当open方法不为空调用之。不过特殊文件
的inode里的file_operations都是一样的,比如字符设备文件inode之file_operations只定义了一个open方法,open方法根据inode中的设备
号在已注册的字符设备驱动中查找cdev,这个cdev里的fops才是驱动提供的。它会赋给file结构体中相应成员(file_operations结构体)
(覆盖了之前的一次赋值),其open方法不为空则调用之。
现在我们就演示一下用户使用write函数将数据写到设备里面这个过程到底是怎么实现的:
1,insmod驱动程序。驱动程序申请设备名和主设备号,这些可以在/proc/devieces中获得。
2,从/proc/devices中获得主设备号,并使用mknod命令建立设备节点文件。这是通过主设备号将设备节点文件和设备驱动程序联系在
一起。设备节点文件中的file属性中指明了驱动程序中fops方法实现的函数指针。
3,用户程序使用open打开设备节点文件,这时操作系统内核知道该驱动程序工作了,就调用fops方法中的open函数进行相应的工作。
open方法一般返回的是文件标示符,实际上并不是直接对它进行操作的,而是由操作系统的系统调用在背后工作。
4,当用户使用write函数操作设备文件时,操作系统调用sys_write函数,该函数首先通过文件标示符得到设备节点文件对应的inode指针
和flip指针。inode指针中有设备号信息,能够告诉操作系统应该使用哪一个设备驱动程序,flip指针中有fops信息,可以告诉操作系统相应
的fops方法函数在那里可以找到。
5,然后这时sys_write才会调用驱动程序中的write方法来对设备进行写的操作。
其中1-3都是在用户空间进行的,4-5是在核心空间进行的。用户的write函数和操作系统的write函数通过系统调用sys_write联系在了一起。
注意:
总的来说:设备文件通过设备号绑定了设备驱动,fops绑定了应用层的write和驱动层的write。当应用层写一个设备文件的时候,系统
找到对应的设备驱动,再通过fops找到对应的驱动write函数。
int open(const char *pathname, int flags, mode_t mode); --系统调用
||
\/
long sys_open(const char __user *filename, int flags, int mode) -- fs/open.c
/*对应内核中的open接口函数*/
||
\/
long do_sys_open(int dfd, const char __user *filename, int flags, int mode) --fs/open.c
/*用户空间的filename被拷贝到内核空间,获取当前可用的文件描述符*/
||
\/
static struct file *do_filp_open(int dfd, const char *filename, int flags, int mode) --fs/open.c
||
\/
int open_namei(int dfd, const char *pathname, int flag,
int mode, struct nameidata *nd)
/*获取该文件对应的nameidata结构.该函数执行完毕,接着调用下面函数。这两个函数是顺序被do_filp_open调用*/
||
\/
struct file *nameidata_to_filp(struct nameidata *nd, int flags) --fs/open.c
/*将nameidata 结构转换为打开的struct file结构*/
||
\/
static struct file *__dentry_open(struct dentry *dentry, struct vfsmount *mnt,
int flags, struct file *f,
int (*open)(struct inode *, struct file *)) --fs/open.c
||
\/
f->f_op = fops_get(inode->i_fop); --fs/open.c
/*这里将系统调用中需要对应打开文件对应到内核中的file_operations结构体获取到,然后根据其函数指针就可以找到该结构体中对
该种文件操作的所有方法。scull对应的结构体是在scull_init的时候向内核注册的。
||
\/
open = f->f_op->open;
open(inode, f); --fs/open.c
/*以上两行代码分别完成了open系统调用时执行实际文件对应内核的open方法,即scull_open。