1

转载

1 目录模型

2 VFS的概念

VFS是Linux中的一个虚拟文件文件系统,也称为虚拟文件系统交换层(Virtual Filesystem Switch)。
它为应用程序员提供一层抽象,屏蔽底层各种文件系统的差异.
如下图所示:
clipboard.png

3 VFS的构建

所谓VFS的构建就是加载实际文件系统的过程,也就是mount被调用的过程。
如下图所示,以mount一个ext2的文件系统为例。
这是一个经过简化的Ext2磁盘结构,只是用于说明用它构建VFS的基本过程。
mount命令的一般形式为:

mount /dev/sdb1 /mnt/mysdb1
/*  /dev/sdb1是设备名,/mnt/mysdb1是挂载点  */

clipboard.png

VFS文件系统的基本结构是dentry结构体与inode结构体。
Dentry代表一个文件目录中的一个点,可以是目录也可以是文件。
Inode代表一个在磁盘上的文件,它与磁盘文件一一对应。
Inode与dentry不一定一一对应,一个inode可能会对应多个dentry项。(hard link)

mount时,linux先找到磁盘分区的super block,然后通过解析磁盘的inode table与file data,构建出自己的dentry列表与inode列表。
需要注意的是,VFS实际上是按照Ext的方式进行构建的,所以两者非常相似(毕竟Ext是Linux的原生文件系统)。
比如inode节点,Ext与VFS中都把文件管理结构称为inode,但实际上它们是不一样的。
Ext的inode节点在磁盘上;VFS的inode节点在内存里。Ext-inode中的一些成员变量其实是没有用的,如引用计数等。保留它们的目的是为了与vfs-node保持一致。这样在用ext-inode节点构造vfs-inode节点时,就不需要一个一个赋值,只需一次内存拷贝即可。
如果是非EXT格式的磁盘,就没有这么幸运了,所以mount非EXT磁盘会慢一些。

4 VFS的结构

构建出VFS文件系统后,下一步是把第一节中提到的目录模型映射到VFS结构体系中。
上文提到了VFS主要由denty与inode构成。
Dentry用于维护VFS的目录结构,每个dentry项就代表着我们用ls时看的的一项(每个目录和每个文件都对应着一个dentry项)。Inode为文件节点,它与文件一一对应。Linux中,目录也是一种文件,所以dentry也会对应一个inode节点。

clipboard.png

下图是上图中的目录模型在VFS中的结构。

clipboard.png

5 Dentry cache

每个文件都要对应一个inode节点与至少一个dentry项。
假设我们有一个100G的硬盘,上面写满了空文件,那个需要多少内存才能重建VFS呢?
文件最少要占用1个block(一般是4K)。假一个dentry与一个inode需要100byte,则dentry与inode需要占用1/40的空间。1G硬盘则需要2.5G空间。最近都开始换装1T硬盘了,需要 25G的内存才能放下inode与dentry,相信没有几台电脑可以承受。

为了避免资源浪费,VFS采用了dentry cache的设计。

当有用户用ls命令查看某一个目录或用open命令打开一个文件时,VFS会为这里用的每个目录项与文件建立dentry项与inode,即“按需创建”。然后维护一个LRU(Least Recently Used)列表,当Linux认为VFS占用太多资源时,VFS会释放掉长时间没有被使用的dentry项与inode项。
需要注意的是:这里的建立于释放是从内存占用的角度看。从Linux角度看,dentry与inode是VFS中固有的东西。所不同的只是VFS是否把dentry与inode读到了内存中。对于Ext2/3文件系统,构建dentry与inode的过程非常简单,但对于其他文件系统,则会慢得多。

6 无dentry时定位文件

因为上面提到的Dentry Cache,VFS并不能保证随时都有dentry项与inode项可用。
下面是无dentry项与inode项时的定位方式。
为了简化问题,这里假设已经找到了dir的dentry项(找到dentry的过程会在后面讲解)。
首先,通过dir对应的dentry0找到inode0节点,有了inode节点就可以读取目录中的信息。其中包含了该目录包含的下一级目录与文件文件列表,包括name与inode号。实际上用ls命令查看的就是这些信息。“ls -i”会显示出文件的inode号。

>  ls -i
975248 subdir0  975247 subdir1   975251 file0

然后,根据通过根据subdir0对应的inode号重建inode2,并通过文件数据(目录也是文件)与inode2重建subdir0的dentry节点:dentry1。

>  ls -i
975311 file1 975312 file2

接着,根据file1对应的inode号重建inode4,并通过文件数据与inode4重建file1的dentry节点。
最后,就可以通过inode4节点访问文件了。
clipboard.png

注意:文件对应的inode号是确定的,只是inode结构体需要重新构造。

7 有dentry时定位文件

8 Symbolic link

9 hard link

10 进程对文件的管理

进程控制块task_struct中有两个变量与文件有关:fs与files。
files中存储着root与pwd两个指向dentry项的指针。用户定路径时,绝对路径会通过root进行定位;相对路径会通过pwd进行定位。
(进程的root不一定是文件系统的根目录。如ftp进程的根目录不是文件系统的根目录,这样才能保证用户只能访问ftp目录下的内容)
fs是一个file object列表,其中每一个节点对应着一个被打开了的文件。

当进程定位到文件时,会构造一个file object,并通过f_inode 关联到inode节点。
文件关闭时(close),进程会释放对应对应file object
file object中的f_mode是打开时选择的权限,f_pos为读写位置。
当打开同一个文件多次时,每次都会构造一个新的file object。每个file object中有独立的f_mode与f_pos。

clipboard.png

11 open的过程

首先建立一个文件管理结构,如下图所示,该进程已经打开了两个文件,接下来我们再打开一个新文件。

clipboard.png

第一步:找到文件;
从上文中能定位到我们文件的inode节点,找到了inode节点也就找到了文件。

clipboard.png

第二步:建立file object;
建立一个新的file object对象,放入file object对象列表,并把它指向inode节点。

clipboard.png

第三步:建立file descriptor
file descriptor就是进程控制块task_struct中files中维护的fd_array。
因为是数组,所以file descriptor实际上已经预先分配好空间了,这里这是需要把某个空闲的file descriptor与file object关联起来。
这个file descriptor在数组中的索引号就是open文件时得到的文件fd。

clipboard.png

12 open与dup

同一个文件是可以open多次的,结构如下图所示。每次open都会建立一个新的file descriptor与file object。然后指向同一个文件的inode节点。下图中,假设open的文件与fd1指向的是同一个文件,则新创建的file object 2与fd1的file object 2会指向同一个inode2节点。

clipboard.png

Linux还提供了dup功能,用于复制file descriptor。使用dup不会建立新的非file object,所以新建立的file descriptor会与原filedescriptor同时指向同一个file object。下图中,我们通过dup(fd1)得到了fd2,则fd2与fd1指向同一个file object2。

clipboard.png

13 fork对打开文件的影响

Dup的操作与fork一个子进程时的操作类似.

下图是已有父进程的文件结构:

clipboard.png

使用fork后的结构如下。同样是没有创建新的file object,因此当对parent process中的fd1进行文件指针的移动时(如读写),child process中的fd1也会受影响。也即是说opened files list不是进程的一部分,因此不会被复制。Opened files list应该是一个全局性的资源链表,进程维护的是一个指针列表fd table,所以被复制的只是指针列表fd table,而不是opened files list。

clipboard.png

14 文件操作函数解析


shiyang6017
158 声望59 粉丝