Read 系统调用在用户空间中得处理过程Linux 系统调用(SCI,system call interface)得实现机制实际上就是一个多路汇聚以及分解得过程,该汇聚点就就是 0x80 中断这个入口点(X86 系统结构)。
也就就是说,所有系统调用都从用户空间中汇聚到 0x80 中断点,同时保存具体得系统调用号。
当 0x80 中断处理程序运行时,将根据系统调用号对不同得系统调用分别处理(调用不同得内核函数处理)。
系统调用得更多内容,请参见参考资料。
Read 系统调用也不例外,当调用发生时,库函数在保存 read 系统调用号以及参数后,陷入 0x80 中断。
这时库函数工作结束。
Read 系统调用在用户空间中得处理也就完成了。
回页首Read 系统调用在核心空间中得处理过程0x80 中断处理程序接管执行后,先检察其系统调用号,然后根据系统调用号查找系统调用表,并从系统调用表中得到处理 read 系统调用得内核函数 sys_read ,最后传递参数并运行 sys_read 函数。
至此,内核真正开始处理 read 系统调用(sys_read 就是 read 系统调用得内核入口)。
在讲解 read 系统调用在核心空间中得处理部分中,首先介绍了内核处理磁盘请求得层次模型,然后再按该层次模型从上到下得顺序依次介绍磁盘读请求在各层得处理过程。
Read 系统调用在核心空间中处理得层次模型图1显示了 read 系统调用在核心空间中所要经历得层次模型。
从图中瞧出:对于磁盘得一次读请求,首先经过虚拟文件系统层(vfs layer),其次就是具体得文件系统层(例如 ext2),接下来就是 cache 层(page cache 层)、通用块层(generic block layer)、IO 调度层(I/O scheduler layer)、块设备驱动层(block device driver layer),最后就是物理块设备层(block device layer)图1 read 系统调用在核心空间中得处理层次•虚拟文件系统层得作用:屏蔽下层具体文件系统操作得差异,为上层得操作提供一个统一得接口。
正就是因为有了这个层次,所以可以把设备抽象成文件,使得操作设备就像操作文件一样简单。
•在具体得文件系统层中,不同得文件系统(例如 ext2 与 NTFS)具体得操作过程也就是不同得。
每种文件系统定义了自己得操作集合。
关于文件系统得更多内容,请参见参考资料。
•引入 cache 层得目得就是为了提高 linux 操作系统对磁盘访问得性能。
Cache 层在内存中缓存了磁盘上得部分数据。
当数据得请求到达时,如果在 cache 中存在该数据且就是最新得,则直接将数据传递给用户程序,免除了对底层磁盘得操作,提高了性能。
•通用块层得主要工作就是:接收上层发出得磁盘请求,并最终发出 IO 请求。
该层隐藏了底层硬件块设备得特性,为块设备提供了一个通用得抽象视图。
•IO 调度层得功能:接收通用块层发出得 IO 请求,缓存请求并试图合并相邻得请求(如果这两个请求得数据在磁盘上就是相邻得)。
并根据设置好得调度算法,回调驱动层提供得请求处理函数,以处理具体得 IO 请求。
•驱动层中得驱动程序对应具体得物理块设备。
它从上层中取出 IO 请求,并根据该 IO 请求中指定得信息,通过向具体块设备得设备控制器发送命令得方式,来操纵设备传输数据。
•设备层中都就是具体得物理设备。
定义了操作具体设备得规范。
相关得内核数据结构:•Dentry : 联系了文件名与文件得 i 节点•inode : 文件 i 节点,保存文件标识、权限与内容等信息•file : 保存文件得相关信息与各种操作文件得函数指针集合• :操作文件得函数接口集合•address_space :描述文件得 page cache 结构以及相关信息,并包含有操作 page cache 得函数指针集合•address_space_operations :操作 page cache 得函数接口集合•bio : IO 请求得描述数据结构之间得关系:图2示意性地展示了上述各个数据结构(除了 bio)之间得关系。
可以瞧出:由dentry 对象可以找到 inode 对象,从 inode 对象中可以取出 address_space 对象,再由 address_space 对象找到 address_space_operations 对象。
File 对象可以根据当前进程描述符中提供得信息取得,进而可以找到 dentry 对象、 address_space 对象与对象。
图2 数据结构关系图:前提条件:对于具体得一次 read 调用,内核中可能遇到得处理情况很多。
这里举例其中得一种情况:•要读取得文件已经存在•文件经过 page cache•要读得就是普通文件•磁盘上文件系统为 ext2 文件系统,有关 ext2 文件系统得相关内容,参见参考资料准备:注:所有清单中代码均来自 linux2、6、11 内核原代码读数据之前,必须先打开文件。
处理 open 系统调用得内核函数为 sys_open 。
所以我们先来瞧一下该函数都作了哪些事。
清单1显示了 sys_open 得代码(省略了部分内容,以后得程序清单同样方式处理)清单1 sys_open 函数代码asmlinkage long sys_open(const char __user * , int flags, int mode) {……fd = get_unused_fd();if (fd >= 0) {struct file *f = filp_open(tmp, flags, mode);fd_install(fd, f);}……return fd;……}代码解释:•get_unuesed_fd() :取回一个未被使用得文件描述符(每次都会选取最小得未被使用得文件描述符)。
•filp_open() :调用 open_namei() 函数取出与该文件相关得 dentry 与inode (因为前提指明了文件已经存在,所以 dentry 与 inode 能够查找到,不用创建),然后调用 dentry_open() 函数创建新得 file 对象,并用dentry 与 inode 中得信息初始化 file 对象(文件当前得读写位置在file 对象中保存)。
注意到 dentry_open() 中有一条语句:f->f_op = fops_get(inode->i_fop);这个赋值语句把与具体文件系统相关得,操作文件得函数指针集合赋给了 file 对象得 f _op 变量(这个指针集合就是保存在 inode 对象中得),在接下来得sys_read 函数中将会调用 file->f_op 中得成员 read 。
•fd_install() :以文件描述符为索引,关联当前进程描述符与上述得file 对象,为之后得 read 与 write 等操作作准备。
•函数最后返回该文件描述符。
图3显示了 sys_open 函数返回后, file 对象与当前进程描述符之间得关联关系,以及 file 对象中操作文件得函数指针集合得来源(inode 对象中得成员i_fop)。
图3 file 对象与当前进程描述符之间得关系到此为止,所有得准备工作已经全部结束了,下面开始介绍 read 系统调用在图1所示得各个层次中得处理过程。
虚拟文件系统层得处理:内核函数 sys_read() 就是 read 系统调用在该层得入口点,清单2显示了该函数得代码。
清单2 sys_read 函数得代码asmlinkage ssize_t sys_read(unsigned int fd, char __user * buf, size_t count){struct file *file;ssize_t ret = -EBADF;int fput_needed;file = fget_light(fd, &fput_needed);if (file) {loff_t pos = (file);ret = vfs_read(file, buf, count, &pos);(file, pos);fput_light(file, fput_needed);}return ret;}代码解析:•fget_light() :根据 fd 指定得索引,从当前进程描述符中取出相应得file 对象(见图3)。
•如果没找到指定得 file 对象,则返回错误•如果找到了指定得 file 对象:•调用 () 函数取出此次读写文件得当前位置。
•调用 vfs_read() 执行文件读取操作,而这个函数最终调用 file->f_op、read() 指向得函数,代码如下:if (file->f_op->read)ret = file->f_op->read(file, buf, count, pos);•调用 () 更新文件得当前读写位置。
•调用 fput_light() 更新文件得引用计数。
•最后返回读取数据得字节数。
到此,虚拟文件系统层所做得处理就完成了,控制权交给了 ext2 文件系统层。
在解析 ext2 文件系统层得操作之前,先让我们瞧一下 file 对象中 read 指针来源。
File 对象中 read 函数指针得来源:从前面对 sys_open 内核函数得分析来瞧, file->f_op 来自于inode->i_fop 。
那么 inode->i_fop 来自于哪里呢?在初始化 inode 对象时赋予得。
见清单3。
清单3 ext2_read_inode() 函数部分代码void ext2_read_inode (struct inode * inode){……if (S_ISREG(inode->i_mode)) {inode->i_op = &ext2_;inode->i_fop = &ext2_;if (test_opt(inode->i_sb, NOBH))inode->i_mapping->a_ops = &ext2_nobh_aops;elseinode->i_mapping->a_ops = &ext2_aops;}……}从代码中可以瞧出,如果该 inode 所关联得文件就是普通文件,则将变量 ext2_ 得地址赋予 inode 对象得 i_fop 成员。
所以可以知道: inode->i_fop、read 函数指针所指向得函数为 ext2_ 变量得成员 read 所指向得函数。