linux驱动开发第2讲:应用程序里的write如何调到驱动里的write?

关注“技术简说”,带你一步一步学习linux内核驱动 。
在linux操作系统中,一切皆是文件:文件是文件,目录是文件,设备是文件,socket套接字是文件,管道也是文件 。
linux操作系统用文件抽象出了这一切,文件成为了以上这些实体的编程接口 。正由于此,基于linux的编程变成了面向文件的编程,对于linux应用程序开发者而言,简直是爽的不要不要的 。
但是,对于内核开发者而言,却是未必 。虽然应用层可以用open, write,read操纵一切,但是在内核里面,却需要不同的部分(或者说驱动)来真正实现这一切 。
本文接着linux驱动开发第1讲:带你编写一个最简单的字符设备驱动,来讲述linux应用程序中的write()函数如何调用到hello驱动里的write()函数,并顺道回答上一讲最后的几个遗留问题 。
先上一张图简单说明下调用流程:
linux驱动开发第2讲:应用程序里的write如何调到驱动里的write?

文章插图
 
任何一个可以正常使用的函数,如果你的应用程序里没有定义,那么肯定定义在c库里 。而c库怎么做会视情况而定 。像一些字符处理函数,c库里会实现它们;但是像write函数,c库只会做一些检查,然后就陷入write的系统调用,系统调用会通过软中断的方式陷入到内核空间里去执行 。
应用空间和内核空间是彼此隔离的,互相看不到对方,也无法访问对方的数据,这是为了安全 。所以就write函数而言,当从用户空间通过系统调用进入到内核空间以后,内核需要通过copy_from_user函数才能把应用程序传给write的数据拷贝到内核空间,之后内核空间才能对此数据进行处理 。
整个流程,上图表现的已经非常明显,但是问题也是有的,操作系统中的系统调用最终是如何知道应该调用哪个驱动里的write函数呢?在linux驱动开发第1讲:带你编写一个最简单的字符设备驱动里,我们确实看到当执行测试程序的时候,当调用测试程序里的open, write和read函数的时候,分别调用到了hello驱动里的hello_open, hello_write和hello_read函数,难道这是一个巧合吗?
当然不是!
我们先从逻辑上说明这个问题 。
如果我们没有记错,在hello驱动里,有定义主次设备号:
int reg_major=232;int reg_minor =0;int hello_init(void){devNum = MKDEV(reg_major, reg_minor);gDev = kzalloc(sizeof(struct cdev), GFP_KERNEL);gFile = kzalloc(sizeof(struct file_operations), GFP_KERNEL);...cdev_init(gDev, gFile);cdev_add(gDev, devNum, 3);}在hello_init里,我们把主设备号232和此设备号0组合成了devNum 。
cdev_init(gDev, gFile); 建立了gDev和gFile的逻辑关系;
cdev_add(gDev, devNum, 3); 建立了gDev和devNum的逻辑关系;
其实你翻开代码看细节会发现,以上两句代码其实建立了gFile和devNum的对应关系,也就是file_operations和devNum的对应关系,也就是建立了file_operation和主次设备号(232,0)的对应关系 。
注意:在linux里,在应用层用文件句柄也就是fd表示一个打开的文件,但是在内核里用struct file 表示一个打开的文件,用struct file_operations表示对该文件的操作 。fd和struct file是一一对应的,而struct file和struct file_operations也是一一对应的 。这是struct file_operations的结构体定义:
struct file_operations { struct module *owner; loff_t (*llseek) (struct file *, loff_t, int); ssize_t (*read) (struct file *, char __user *, size_t, loff_t *); ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *); int (*mmap) (struct file *, struct vm_area_struct *); int (*open) (struct inode *, struct file *); int (*flush) (struct file *, fl_owner_t id); int (*release) (struct inode *, struct file *); int (*fsync) (struct file *, loff_t, loff_t, int datasync); int (*fasync) (int, struct file *, int);...};在上一讲的例子里,我们打开的文件名字是/dev/hello,这是一个设备文件,对应的主次设备号分别为232和0 。所以,当你打开/dev/hello之后,就已经建立了这个文件和hello驱动里的 struct file 的对应关系,也就建立了这个文件和hello驱动里的struct file_operations的对应关系 。
好,了解以上的背景之后,我们来看看代码 。
我们从内核里write系统调用的实现部分开始阅读:
【linux驱动开发第2讲:应用程序里的write如何调到驱动里的write?】相关的代码在:fs/read_write.c
linux驱动开发第2讲:应用程序里的write如何调到驱动里的write?

文章插图
 
备注:SYSCALL_开头表示是系统调用 。
关键代码在vfs_write 。所以,我们继续跟进入:
linux驱动开发第2讲:应用程序里的write如何调到驱动里的write?


推荐阅读