学习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。