参考文献:
《Linux内核设计与实现》
http://www.ibm.com/developerworks/cn/linux/l-cn-vfs/
ttp://www.ibm.com/developerworks/cn/linux/l-linux-filesystem/
http://www.ibm.com/developerworks/cn/linux/l-cn-read/index.html
首先来看一下
open的执行过程:v2.6.30
Open
Sys_open
|do_sys_open()
|get_unused_fd_flags () //得到一个可用的文件描述符;通过该函数,可知文件描述符
//实质是进程打开文件列表中对应某个文件对象的索引值;
|do_filp_open() //打开文件,返回一个file对象,代表由该进程打开的一个文
//件;进程通过这样的一个数据结构对物理文件进行读写操作。
|if(!(flag & O_CREAT)) //不是要创建
|path_lookup_open() //根据文件路径名查找文件,并初始化struct nameidata对象
| do_path_lookup()
|goto OK;
|else //创建一个新的文件
|do_path_lookup() //查找父目录
|__open_namei_create
|vfs_create()//创建inode在vfs_create()里的一句核心语句dir->i_op->create(dir, dentry, mode, nd) 可 知它调用了具体的文件系统所提供的创建索引节点的方法。注意:这边的索引节点的概念,还只是位于内存之中,它和磁盘上的物理的索引节点的关系就像位于内存中和位于磁盘中的文件一样。此时新建的索引节点还不能完全标志一个物理文件的成功创建,只有当把索引节点回写到磁盘上才是一个物理文件的真正创建。想想我们以新建的方式打开一个文件,对其读写但最终没有保存而关闭,则位于内存中的索引节点会经历从新建到消失的过程,而磁盘却始终不知道有人曾经想过创建一个文件,这是因为索引节点没有回写的缘故。
|may_open() //检查是否可以打开该文件;一些文件如链接文件和
只有写权限的目录是不能被打开的,先检查nd->dentry->inode所指的文件是否是这一类文件,是的话则错误返回。还有一些文件是不能以TRUNC的方式打开的,若nd->dentry->inode所指的文件属于这一类,则显式地关闭TRUNC标志位。接着如果有以TRUNC方式打开文件的,则更新nd->dentry->inode的信息
|nameidata_to_filp() //将nameidata 转换为一个open的file :filp
|__dentry_open() //将调用实际文件的操作方法赋值给file对象,这样当最后通过统一的系统调用处理file对象的时候,就会调用正确的实际文件系统方法。
?end do_filp_open
|fd_install() //建立文件描述符与file对象的联系,即把file对象赋值到fd数组中,以后进程对文件的读写就可以通过操纵该文件描述符而进行。
?end do_sys_open
?end open
通过以上的过程可以把open过程总结如下:
1首先获得一个未使用的文件描述符
2然后通过把路径解析为各个递进的目录项对象(如把/home/test/a.txt解析为”/”、”home”、”test”、”a.txt”),来查找实际的文件(也可以是路径)是否存在,该过程就是为了获得文件的inode节点,并最终赋值给可操作的file对象
查找的过程首先判断该目录(如”/”,由d_hash计算查找)是否存在于dentry cache当中,如果存在则不需要再创建该目录项对象,直接得到目录项对象(此时说明inode也存在于inode cache中)所以就可以直接得到inode节点,然后打开该文件(这里就是”/”目录),然后依次类推,直接找到a.txt的inode节点为止。如果该目录对应的目录项对象不存在于dentry cache中,则先创建一个目录项对象,然后再在磁盘中查找该目录项对象对应的inode节点是否存在,如果存在则缓存到cache中,并查找下一级目录,如果不存在并且falg标志为O_CREAT的话,则创建一个inode节点。因为只有得到inode节点,才能知道文件的所在磁盘位置,以及相应的操作方法。
3 建立文件描述符与file对象的联系
下来看一下read的系统调用过程如下图:
图read的调用过程
上图描述了从用户空间的read()调用到数据从磁盘读出的整个流程。当在用户应用程序调用文件I/O read()操作时,系统调用sys_read()被激发,sys_read()找到文件所在的具体文件 系统,把控制权传给该文件系统,最后由具体文件系统与物理介质交互,从介质中读出数据。
对文件进行读操作时,需要先打开它。在打开一个文件(open)时,会在内存组装一个文件对象,最后对该文件执行的操作方法已在文件对象设置好。所以对文件进行读操作时,VFS在做了一些简单的转换后(由文件描述符得到其对应的文件对象;其核心思想是返回current->files->fd[fd]所指向的文件对象),就可以通过语句file->f_op->read(file, buf, count, pos)轻松调用实际文件系统的相应方法对文件进行读操作了。
图 系统调用在核心空间中的处理层次
上图显示了 read 系统调用在核心空间中所要经历的层次模型。从图中看出:对于磁盘的一次读请求,首先经过虚拟文件系统层(vfs layer),其次是具体的文件系统层(例如 ext2),接下来是 cache 层(page cache 层)、通用块层(generic block layer)、IO 调度层(I/O scheduler layer)、块设备驱动层(block device driver layer),最后是物理块设备层(block device layer)。
· 虚拟文件系统层的作用:屏蔽下层具体文件系统操作的差异,为上层的操作提供一个统一的接口。正是因为有了这个层次,所以可以把设备抽象成文件,使得操作设备就像操作文件一样简单。
· 在具体的文件系统层中,不同的文件系统(例如 ext2 和 NTFS)具体的操作过程也是不同的。每种文件系统定义了自己的操作集合。关于文件系统的更多内容,请参见参考资料。
· 引入 cache 层的目的是为了提高 linux 操作系统对磁盘访问的性能。 Cache 层在内存中缓存了磁盘上的部分数据。当数据的请求到达时,如果在 cache 中存在该数据且是最新的,则直接将数据传递给用户程序,免除了对底层磁盘的操作,提高了性能。
· 通用块层的主要工作是:接收上层发出的磁盘请求,并最终发出 IO 请求。该层隐藏了底层硬件块设备的特性,为块设备提供了一个通用的抽象视图。
· IO 调度层的功能:接收通用块层发出的 IO 请求,缓存请求并试图合并相邻的请求(如果这两个请求的数据在磁盘上是相邻的)。并根据设置好的调度算法,回调驱动层提供的请求处理函数,以处理具体的 IO 请求。
· 驱动层中的驱动程序对应具体的物理块设备。它从上层中取出 IO 请求,并根据该 IO 请求中指定的信息,通过向具体块设备的设备控制器发送命令的方式,来操纵设备传输数据。
设备层中都是具体的物理设备。定义了操作具体设备的规范。