write函数

关注“技术简报”,带你一步步学习linux内核驱动。

在linux操作系统中,一切都是文件:文件是文件,目录是文件,设备是文件,套接字是文件,管道也是文件。

Linux操作系统用文件把这一切抽象出来,文件成为这些实体的编程接口。正因为如此,基于linux的编程变成了面向文件的编程,对于linux应用开发者来说简直酷毙了。

但是,对于内核开发者来说,不一定。虽然应用层可以用open、write、read read操纵一切,但是在内核中,它需要不同的部分(或者驱动程序)才能真正实现。

本文继续第一讲linux驱动开发:我带大家写一个最简单的字符设备驱动,如何将linux应用中的write()函数调用到hello driver中的write()函数,并回答上一讲遗留下来的最后几个问题。

首先,一张图简要说明了通话过程:

write函数

应用层–>系统调用–>驱动应用层->;系统调用-& gt;驱动器

任何可以正常使用的函数,如果没有在你的应用中定义,都必须在C库中定义。C库会怎么做要看情况。像一些字符处理函数,C库会实现;但是和write函数一样,C库只会做一些检查,然后会落入write的系统调用,系统调用通过软中断落入kernel 空来执行。

应用空和内核空是相互隔离的,彼此看不到对方,也无法访问对方的数据。这是为了安全。所以就write函数而言,用户空通过系统调用进入内核空后,内核需要通过copy_from_user函数将应用程序发送的数据复制到内核空中,然后内核空才能对数据进行处理。

整个过程,如上图所示,非常明显,但也有问题。操作系统中的系统调用如何最终知道哪个驱动应该调用write函数?在linux驱动开发第一讲:带你写一个最简单的字符设备驱动中,我们确实看到在执行测试程序时,调用测试程序中的open、write、read函数时,分别调用hello驱动中的hello_open、hello_write、hello_read函数。这是巧合吗?

当然不是!

我们先从逻辑上解释一下这个问题。

如果我们没记错,在hello驱动程序中,定义了主设备号和辅助设备号:

int reg _ major = 232int 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文件来表示,文件的操作用一个struct file_operations来表示。Fd和struct file是一一对应的,struct file和struct file_operations也是一一对应的。这是struct file_operations的结构定义:

结构文件_操作{结构模块*所有者;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)(结构文件*,fl _ owner _ t id);int (*release) (struct inode *,struct file *);int (*fsync) (struct file *,loff_t,loff_t,int data sync);int (*fasync) (int,struct file *,int);…};上一个例子中,我们打开的文件名称是/dev/hello,这是一个设备文件,对应的主设备号和次设备号分别是232和0。所以,当你打开/dev/hello的时候,你已经建立了这个文件和hello驱动中的struct文件的对应关系,也建立了这个文件和hello驱动中的struct file_operations的对应关系。

好了,了解了上面的背景之后,我们来看代码。

让我们从内核中write系统调用的实现开始:

相关代码在:fs/read _ write.c中。

备注:SYSCALL_的开头表示系统调用。

关键代码在vfs_write里。那么,让我们继续进入:

继续跟踪__vfs_write:

ssize _ t _ _ VFS _ write(struct file * file,const char __user *p,size_t count,loff _ t * pos){ if(file-& gt;f _ op-& gt;写)返回文件-& gt;f _ op-& gt;write(文件、p、计数、位置);else if(文件-& gt;f _ op-& gt;write_iter)返回new_sync_write(file,p,count,pos);elsereturn-EINVAL;关键代码在这里:

如果(文件-& gt;f _ op-& gt;写)返回文件-& gt;f _ op-& gt;write(文件、p、计数、位置);上面提到/dev/hello和file_operations之间的关系已经建立。所以这里其实是判断hello驱动中是否定义了写函数。如果有,调用hello驱动中的write函数。

所以按照上面的路径,应用程序中的write成功调用到hello驱动中的write函数。因为我们驱动中的hello_write和hello_read都返回0。因此,应用程序中的写入和读取也返回0。

ssize _ t hello _ write(struct file * f,const char __user *u,size_t s,loff _ t * l){ printk(KERN _ EMERG & # 34;hello _ write \ r \ n & # 34);返回0;} ssize _ t hello _ read(struct file * f,char __user *u,size_t s,loff _ t * l){ printk(KERN _ EMERG & # 34;hello _ read \ r \ n & # 34);返回0;}如果想让测试程序中的写和读返回非零值,只需要把驱动中的return 0改成任意值,自己测试就可以了。

关注“技术简说”,带你一步一步开发linux内核驱动。

免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。

发表回复

登录后才能评论