Linux之内核中的文件系统

来源:互联网 发布:韩语电影翻译软件 编辑:程序博客网 时间:2024/06/05 20:21
  • 文件描述符

一般说起文件和文件系统的时候,都会下意识的想到它们存在于磁盘上。然而,磁盘只能被动的存储,却不能主动去处理文件;要处理文件,只能把它们的数据复制到内存中,交给CPU处理,处理好的数据先写入内存,再传回到磁盘。那么,操作系统如何在内存中管理各种文件呢?即运行时文件系统在内核中的表示。 
我们知道,进程是操作系统分配资源的基本单位,文件也是在进程中被处理的。比如用vim写代码,那么vim这个程序就成为操作系统中的一个进程,vim处理的对象就是一个代码文件。操作系统用PCB来管理进程,PCB从代码的角度看就是task_struct结构体,这个结构体中有一个指针指向files_struct结构体,这就是所谓的文件描述符表,其代码如下:

<code class="hljs cs has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;"><span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">struct</span> files_struct {         atomic_t count;        <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">/* 共享该表的进程数 */</span>         rwlock_t file_lock;     <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">/* 保护以下的所有域,以免在tsk->alloc_lock中的嵌套*/</span>         <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">int</span> max_fds;           <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">/*当前文件对象的最大数*/</span>         <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">int</span> max_fdset;        <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">/*当前文件描述符的最大数*/</span>         <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">int</span> next_fd;          /*已分配的文件描述符加<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">1</span>*/         <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">struct</span> file ** fd;      <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">/* 指向文件对象指针数组的指针 */</span>         fd_set *close_on_exec;  <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">/*指向执行exec(  )时需要关闭的文件描述符*/</span>         fd_set *open_fds;     <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">/*指向打开文件描述符的指针*/</span>         fd_set close_on_exec_init;<span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">/* 执行exec(  )时需要关闭的文件描述符的初值集合*/</span>         fd_set open_fds_init;  <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">/*文件描述符的初值集合*/</span>         <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">struct</span> file * fd_array[<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">32</span>];<span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">/* 文件对象指针的初始化数组*/</span>};</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li><li style="box-sizing: border-box; padding: 0px 5px;">9</li><li style="box-sizing: border-box; padding: 0px 5px;">10</li><li style="box-sizing: border-box; padding: 0px 5px;">11</li><li style="box-sizing: border-box; padding: 0px 5px;">12</li><li style="box-sizing: border-box; padding: 0px 5px;">13</li></ul>

这个表是每个进程私有的,每个进程都有一个,它也称为用户打开文件表,这是为了区别系统打开文件表。我们主要关注最后一个成员:struct file * fd_array[32];,这是一个指针数组,数组的每个成员都指向一个file结构体,file结构体中保存着该进程已经打开的文件的信息。由于一个进程可以打开多个文件,所以用一个指针数组来保存它们的信息。对于在数组中有入口地址的每个文件来说,数组的索引就是文件描述符(file descriptor)。通常,数组的第一个元素(索引为0)是进程的标准输入文件,数组的第二个元素(索引为1)是进程的标准输出文件,数组的第三个元素(索引为2)是进程的标准错误文件。这三个文件是操作系统为每个进程默认打开的,如果进程需要打开其他文件,那么进程描述符将从3开始。文件描述符是系统的一个重要资源,虽然说系统内存有多少就可以打开多少的文件描述符,但是在实际实现过程中内核是会做相应的处理的,一般最大打开文件数会是系统内存的10%(以KB来计算)(称之为系统级限制),查看系统级别的最大打开文件数可以使用sysctl -a | grep fs.file-max命令查看。与此同时,内核为了不让某一个进程消耗掉所有的文件资源,其也会对单个进程最大打开文件数做默认值处理(称之为用户级限制),默认值一般是1024,使用ulimit -n命令可以查看。 
在Linux中,进程是通过文件描述符(file descriptors,简称fd)而不是文件名来访问文件的,文件描述符实际上是一个整数。

  • 虚拟文件系统 
    下面来看一下file结构体,代码如下:
<code class="hljs objectivec has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;"><span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">struct</span> file{ <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">struct</span> list_head        f_list;    <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">/*所有打开的文件形成一个链表*/</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">struct</span> dentry           *f_dentry; <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">/*指向相关目录项的指针*/</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">struct</span> vfsmount         *f_vfsmnt; <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">/*指向VFS安装点的指针*/</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">struct</span> file_operations  *f_op;     <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">/*指向文件操作表的指针*/</span> mode_t f_mode;                     <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">/*文件的打开模式*/</span> loff_t f_pos;                      <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">/*文件的当前位置*/</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">unsigned</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">short</span> f_flags;            <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">/*打开文件时所指定的标志*/</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">unsigned</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">short</span> f_count;            <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">/*使用该结构的进程数*/</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">unsigned</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">long</span> f_reada, f_ramax, f_raend, f_ralen, f_rawin; <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">/*预读标志、要预读的最多页面数、上次预读后的文件指针、预读的字节数以及预读的页面数*/</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">int</span> f_owner;                       <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">/* 通过信号进行异步I/O数据的传送*/</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">unsigned</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">int</span>         f_uid, f_gid; <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">/*用户的UID和GID*/</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">int</span>                 f_error;       <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">/*网络写操作的错误码*/</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">unsigned</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">long</span> f_version;          <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">/*版本号*/</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">void</span> *private_data;              <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">/* tty驱动程序所需 */</span>};</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li><li style="box-sizing: border-box; padding: 0px 5px;">9</li><li style="box-sizing: border-box; padding: 0px 5px;">10</li><li style="box-sizing: border-box; padding: 0px 5px;">11</li><li style="box-sizing: border-box; padding: 0px 5px;">12</li><li style="box-sizing: border-box; padding: 0px 5px;">13</li><li style="box-sizing: border-box; padding: 0px 5px;">14</li><li style="box-sizing: border-box; padding: 0px 5px;">15</li><li style="box-sizing: border-box; padding: 0px 5px;">16</li><li style="box-sizing: border-box; padding: 0px 5px;">17</li><li style="box-sizing: border-box; padding: 0px 5px;">18</li></ul>

file结构体并不与实际的文件一一对应,例如,当一个进程多次打开同一文件的时候,都会分配一个不同的file结构体以及相应的文件描述符,尽管这些file结构体最终都指向同一个实际物理文件。可以看出,内存中的文件和磁盘中的文件实现方式还是不太一样的,内存中的文件是动态的,因为要不停的读写,所以只是一份拷贝,所有的操作针对的只是这份拷贝,操作完成后,把结果写回到磁盘文件中;写回这个动作执行之前,所有的改动只存在与内存中,并不会实时反应到磁盘上,可以用vim和cat同时操作一个文件来验证一下。懂了这个道理之后,再看为啥每次打开文件要分配不同的file结构体:因为每次打开意味着要对同一个文件进行不同的操作,并希望不同操作的结果都能写回到磁盘文件中;如果使用同一个file,那么就有可能后面的操作覆盖掉前面的操作。 
覆盖的可能性来自于file结构体的成员loff_t f_pos;它表示文件当前的读写位置。每个文件都有一个32位的数字来表示下一个读写的字节位置,这个数字叫做文件位置。每次打开一个文件,除非明确要求,否则文件位置都被置为0,即文件的开始处,此后的读或写操作都将从文件的开始处执行,可以通过执行系统调用LSEEK(随机存储)对这个文件位置进行修改。所以,如果两次操作是针对同一文件的不同位置,使用同一个file结构体将使它们拥有同样的读写位置,这显然无法达到目的。这还是同一进程的不同操作,更何况不同进程的不同操作呢?于是,每次打开文件几乎都要分配一个新的file结构体。可以说,file结构体主要保存的就是这个读写位置。 
上面使用了“几乎”,那就表示有例外情况:生成一个新进程时,子进程要共享父进程的所有信息,包括file结构体,其成员unsigned short f_count;就表示了当前有多少进程在使用这个结构体,只有当它的值为0时,才从内存中销毁这它。 
file结构体中的第一个成员是 struct list_head f_list;它使file结构形成一个双链表,称为系统打开文件表,其最大长度是NR_FILE,在fs.h中定义为8192。 
那么文件描述符表、系统打开文件表和实际文件之间的关系可以如下图: 
这里写图片描述
然后我们看看file结构体如何指向最终的实际文件,这要涉及到第二个成员:struct dentry *f_dentry; 其代码如下:

<code class="hljs objectivec has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;"><span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">struct</span> dentry {atomic_t d_count;        <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">/* 目录项引用计数器 */</span><span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">unsigned</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">int</span> d_flags;    <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">/* 目录项标志 */</span><span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">struct</span> inode  * d_inode;   <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">/* 与文件名关联的索引节点 */</span><span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">struct</span> dentry * d_parent;       <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">/* 父目录的目录项 */</span><span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">struct</span> list_head d_hash;        <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">/* 目录项形成的哈希表 */</span><span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">struct</span> list_head d_lru;         <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">/*未使用的 LRU 链表 */</span><span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">struct</span> list_head d_child;       <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">/*父目录的子目录项所形成的链表 */</span><span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">struct</span> list_head d_subdirs;     <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">/* 该目录项的子目录所形成的链表*/</span><span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">struct</span> list_head d_alias;       <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">/* 索引节点别名的链表*/</span><span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">int</span> d_mounted;                  <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">/* 目录项的安装点 */</span><span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">struct</span> qstr d_name;             <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">/* 目录项名(可快速查找) */</span><span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">unsigned</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">long</span> d_time;           <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">/* 由 d_revalidate函数使用 */</span><span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">struct</span> dentry_operations  *d_op; <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">/* 目录项的函数集*/</span><span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">struct</span> super_block * d_sb;      <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">/* 目录项树的根 (即文件的超级块)*/</span><span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">unsigned</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">long</span> d_vfs_flags;  <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">void</span> * d_fsdata;                <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">/* 具体文件系统的数据 */</span><span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">unsigned</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">char</span> d_iname[DNAME_INLINE_LEN]; <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">/* 短文件名 */</span>};</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li><li style="box-sizing: border-box; padding: 0px 5px;">9</li><li style="box-sizing: border-box; padding: 0px 5px;">10</li><li style="box-sizing: border-box; padding: 0px 5px;">11</li><li style="box-sizing: border-box; padding: 0px 5px;">12</li><li style="box-sizing: border-box; padding: 0px 5px;">13</li><li style="box-sizing: border-box; padding: 0px 5px;">14</li><li style="box-sizing: border-box; padding: 0px 5px;">15</li><li style="box-sizing: border-box; padding: 0px 5px;">16</li><li style="box-sizing: border-box; padding: 0px 5px;">17</li><li style="box-sizing: border-box; padding: 0px 5px;">18</li><li style="box-sizing: border-box; padding: 0px 5px;">19</li></ul>

这个结构体保存了路径信息,包括文件名。我在介绍ln命令时说过,操作系统根据inode号寻找文件,而人类根据文件名,故需要从这个结构体中找到二者之间的对应关系。我们可以利用这两个成员:struct inode * d_inode;和struct qstr d_name;。前者指向了与真正的文件相关的inode结构体,它保存着从磁盘分区的inode读上来信息。至此,进程中文件描述符指向了最终的磁盘文件,我猜file结构体最终要依靠inode结构体写回磁盘。 
完。

0 0