苦李  

 inode简介

  硬盘是低速设备,其读写单位是扇区,为了避免频繁访问硬盘,操作系统不会有了一扇区数据就去读写一次磁盘,往往等数据积攒到“足够大小”时才一次性访问硬盘,这足够大小的数据就是块,硬盘读写单位是扇区,因此一个块是由多个扇区组成的,块大小是扇区大小的整数倍。块是文件系统的读写单位,因此文件至少要占据一个块,当文件体积大于1个块时,文件肯定被拆分成多个块来存储,那么问题来了,这多个块该如何组织到一起?

  UNIX 文件系统比较先进,它将文件以索引结构来组织,避免了访问某一数据块需要从头把其前所有数据块再遍历一次的缺点。采用索引结构的文件系统,文件中的块依然可以分散到不连续的零散空间中,保留了磁盘高利用率的优点,更重要的是文件系统为每个文件的所有块建立了一个索引表,索引表就是块地址数组,每个数组元素就是块的地址,数组元素下标是文件块的索引,第 n个数组元素指向文件中的第 n个块,这样访问任意一个块的时候,只要从索引表中获得块地址就可以了,速度大大提升。包含此索引表的索引结构称为inode,即 index node,索引结点,用来索引、跟踪一个文件的所有块。

  inode 是文件索引结构组织形式的具体体现,必须为每个文件都单独配备一个这样的元信息数据结构。在 UINX文件系统中,一个文件必须对应一个 inode,磁盘中有多少文件就有多少 inode。

 

 

   用索引结构的缺点是索引表本身要占用一定的存储空间,文件要是很大时,块就比较多,索引表项就要跟着增多,难道要让索引表就变得很大吗?

  UNIX 为解决这个问题采取了折中的方法,将一部分块放在索引表中,如果文件很大,将其他块放在另一个索引表,具体做法是:每个索引表中共 15 个索引项,暂时称此索引表为老索引表。老索引表中前 12 个索引项是文件的前 12 个块的地址,它们是文件的直接块,即可直接获得地址的块。 若文件大于 12个块,那就再建立个新的块索引表,新索引表称为一级间接块索引表,表中可容纳 256 个块的地址,各表项都是块的地址,这 256 个块地址需要通过一级间接块索引表才能获得,因此称为“间接块”。此表也要占用一个物理块来存储,该物理块的地址存储到老索引表的第 13 个索引项中。有了一级间接块索引表,文件最大可达 12+256=268 个块。要是文件超过 268 个块怎么办?我们可以再建立二级间接块索引表,此表中各表项存储的是一级间接块索引表,然后在老索引表中第 14个索引项存储二级间接块索引表所在块的地址。有了二级间接块索引表, 文件最大可达( 12+256+256*256)个块再不够的话,可以再建立三级间接块索引表,表中各表项存储 的是二级间接块索引表,然后在二级间接块索引表中建立一级 间接块索引表,三级间接块索引表所在块的地址记录在老索引表的第 15 个索引项中。有了三级间接块索引表,文件最大可 达( 12+256+256*256+256*256*256)个块。如果超过了这个限度,那就没办法了,正常情况下也不会出现这种情况,真要是有这样的超大文件出现,那就只能用 mv 命令将其切割成多个小文件了。

 

 

 

  在 inode 结构中,几乎囊括了一个文件的所有信息,i 结点编号是指此 inode 的序号,这通常是指它在 inode 数组中的下标。权限是指读、写、执行。属主是指文件的拥有者,时间是指创建时间、修改时间、访问时间等。文件大小是指文件的字节尺寸。下面这些连续的各种块指针及索引表指针是文件所有块的索引,也就是指向文件的实体部分。文件系统为实现文件管理方案,必然会创造出一些辅助管理的数据结构,只要用于管理、控制 文件相关信息的数据结构都被称为 FCB (File Contd Block),即文件控制块, inode 也是这种结构,因此 inode 是 FCB 的一种。

  inode 的数量等于文件的数量,为方便管理,分区中所有文件的 inode 通过一个大表格来维护,此表格 称为 inode table,在计算机中表格都可以用数组来表示,因此 inode table 本质上就是 inode 数组,数组元素 的下标便是文件 inode 的编号。

 

 目录项与目录简介

  我们是通过文件名来访问文件的,inode 中却没有文件名,其实可以在 inode 中存储文件名,只是如果这样做的话,需要改变文件系统的设计,而且也不是现代文件系统的做法,原因是文件系统对文件是用 inode 来描述的,只要给出 inode,文件系统便能够找到文件实体的数据块,因此文件名对操作系统来说并不重要,从这也能够看出,inode是文件系统需要的东西,并不是给用户准备的。那么文件系统是如何把文件名和 inode关联到一起的呢?

  无论文件在哪个路径,它肯定要位于某个目录中(至少要有个根目录),因此文件名应该存储在和目录相关的地方。在 Linux 中,目录和文件都用 inode 来表示,因此目录也是文件,只是目录是包含文件的文件 。 为了表述清楚这两种文件,我们这里称目录为目录文件, 一般意义上的文件称为普通文件。既然inode同事表示普通文件和目录文件,那么唯一能区分两者的只有数据块了。如果该 inode 表示的是普通文件,此 inode 指向的数据块中的内容应该是普通文件自己的数据。如果该 inode 表示的是目录文件,此 inode指向的数据块中的内容应该是该目录下的目录项。

  什么目录项?不管文件是普通文件,还是目录文件,它总会存在于某个目录中,所有的普通文件或目录文件都存在于根目录'/'之下,根目录是所有目录的父目录。

  目录相当于个文件列表(或者是表格),每个文件在目录中都是一个 entry(条目、项),各个 entry 中的内容包括文件名、文件类型,为了定位文件的数据, entry中至少还要包括 inode编号,这个 entry 是目录中各个文件的描述,它称为目录项,目录项中至少要包括文件名、文件类型及文件对应的inode编号。以下是一个目录项中最基本的属性:

 

  有了目录项后,通过文件名找文件实体数据块的流程是

  1. 在目录中找到文件名所在的目录项 
  2. 从目录项中获取 inode 编号
  3. 用 inode编号作为 inode数组的索引下标,找到 inode
  4. 从该 inode 中获取数据块的地址,读取数据块

 

  总结一下

  1. 每个文件都有自己单独的 inode, inode 是文件实体数据块在文件系统上的元信息。
  2. 所有文件的 inode集中管理,形成 inode数组,每个 inode 的编号就是在该 inode数组中的下标。
  3. inode 中的前 12 个直接数据块指针和后 3 个间接块索引表用于指向文件的数据块实体。
  4. 文件系统中并不存在具体称为“目录”的数据结构,同样也没有称为“普通文件”的数据结构,统一用同一种 inode表示。 inode表示的文件是普通文件,还是目录文件,取决于 inode所指向数据块中的实际内容是什么,即数据块中的内容要么是普通文件本身的数据,要么是目录中的目录项。

  5. 目录项仅存在于 inode 指向的数据块中,有目录项的数据块就是目录,目录项所属的 inode 指向的所有数据块便是目录。
  6. 目录项中记录的是文件名、文件 inode 的编号和文件类型,目录项起到的作用有两个, 一是粘合文件名及 inode,使文件名和 inode 关联绑定,二是标识此 inode 所指向的数据块中的数据类型(比如是普通文件,还是目录,还有更多的类型)。
  7. inode 是文件的“实质”,但它并不能直接引用,必须通过文件名找到文件名所在的目录项,然后从该目录项中获得 inode 的编号,然后用此编号到 inode 数组中去找相关的 inode,最终找到文件的数据块。

 

 

 

  要想找到文件(普通文件或目录文件)的数据块,必须找到文件的 inode。 inode 之所以被引用(找到),是因为在文件名所在的目录项中有记录它的编号,但是目录项是在目录文件的数据块中,而数据块必须通过 inode 才能找到...... 寻找过程似乎陷入了死循环。这个问题只要我们有一个初始固定的目录就解决了,其实就是根目录'/',它是所有目录的父目录,每个分区都有自己的根目录,创建文件系统之后它的位置就是固定不变的,也就是说,在文件系统的设计中,根目录所在数据块的地址是被“写死”的,查找任意文件时,都直接到根目录的数据块中找相关的目录项,然后递归查找,最终可以找到任意子目录中的文件。

 

 超级块与文件系统布局

  超级块是干吗的呢?我们己经知道每个文件都有个 inode,所有的 inode都放在 inode数组中,但是inode 数组在哪里?大小是多少?还有根目录的地址是固定写死的,但每个分区都有自己的根目录,其地址并不统一 ,也许分区 a 的根目录在本分区的第 500 扇区,分区 b 的根目录在本分区的第 2012 扇区,虽然固定,但不统一。既然各分区根目录的位置并不统一,这就说明该地址必然放在某处保存,在各分区中,该处的地址必然是固定且统一 ,以备随时读取。为了避免访问越界,根目录也应该有个大小。 这就像项目的配置文件,虽然很多东西在一开始都可以被固定下来,但依然要将它保存在配置文件中,对于文件系统也一样,我们需要在某个固定地方去获取文件系统元信息的配置,这个地方就是超级块,超级块是保存文件系统元信息的元信息。

 

  超级块一般有如下内容:

 

  超级块是文件系统元信息的“配置文件”,它是在为分区创建文件系统时创建的,所有有关文件系统元信息的配置都在超级块中,因此超级块的位置和大小不能再被“配置”了,必须是固定的,它被固定存储在各分区的第 2个扇区,通常是占用一个扇区的大小。

 

  简单介绍一下文件系统布局:

 

  操作系统引导块就是操作系统引导记录OBR所在的地址,即操作系统引导扇区,它位于各分区最开始的扇区,根据文件系统类型的不同,引导程序可能占用多个扇区,这多个扇区组成一个数据块。在操作系统引导块后面的依次是超级块、空闲块的位图、 inode 位图、 inode 数组、根目录、空闲块区域。根目录和空闲块区域是真正用于存储数据的区域,除了这两部分,其他几个部分占用的扇区数取决于分区的容量大小,或者是在创建文件系统的过程中手动设置。

 

 文件描述符

  文件描述符即 file descriptor,但凡叫“描述符”的数据结构都用于描述一个对象,文件描述符所描述的对象是文件的操作。

  读写文件的本质是先通过文件的 inode 找到文件数据块的扇区地址,随后读写该扇区,从而实现了文件的读写。几乎所有的操作系统都允许一个进程同时、多次、打开同一个文件(并不关闭),同样该文件也可以被多个不同的进程同时打开。为实现文件任意位置的读写,执行读写操作时可以指定偏移量作为该文件内的起始地址,此偏移量相当于文件内的指针。也就是说,该文件每被打开一次,文件读写的偏移量都可以任意指定,即对同一个文件的多次读写都是各自操作各自的,任意一个文件操作的偏移量都不影响其他文件操作的偏移量。

  由于一般文件的体积都较大,而内存缓冲区较小,所以文件的读写井不是一次性从头到尾操作整个文件,往往是通过连续多次的小数据量读写完成的,下一次读写的位置必须以上一次的读写位置为起始,因此,文件系统需要把任意时刻的偏移量记录下来。问题来了,偏移量应该记录在哪里呢?为了解决这个问题, Linux 提供了称为“文件结构”的数据结构(也称为File 结构),专门用于记录与文件操作相关的信息,每次打开一个文件就会产生一个文件结构,多次 打开该文件就为该文件生成多个文件结构,各自文件操作的偏移量分别记录在不同的文件结构中,从而实现了“即使同一个文件被同时多次打开,各自操作的偏移量也互不影响”的灵活性。文件结构如下:

 

  Linux 把所有的“文件结构”组织到一起形成数组统一管理,该数组称为文件表。

  在 Linux 中,我们读写函数文件时都是通过操作文件描述符来完成的。举个例子,拿 open 函数来说,其原型为 int open(const char *pathname, int flags), pathname 是待打开的文件路径及文件名, flag 是打开标识,调用它之后,系统会返回文件 pathname 的文件描述符。重要的是返回值类型为 int,这说明返回值是个整型数字。 它是 PCB 中文件描述符数组元素的下标,只不过此数字并不用来表示“数量”,而是用来表示“位置”,它是位于进程 PCB 中的文件描述符数组的元素的下标,而文件描述符数组元素中的信息又指向文件表中的某个文件结构。

  在 Linux 中每个进程都有单独的、完全相同的一套文件描述符,因此它们与其他进程的文件描述符互不干涉,这些文件描述符被组织成文件描述符数组统一管理。前面说过了,打开一个文件时会产生一个文件结构,这要是该任务同时打开 N 多个文件,文件表可就大了。通常情况下为避免文件表占用过大的内存空间,文件结构的数量必须是有限的,这就是进程可打开的最大文件数有限的原因(在 Linux 中可用 ulimit 命令来修改)。文件描述符数组中的前 3 个都是标准的文件描述符,如文件描述符 0 表示标准输入, 1 表示标准输出, 2 表示标准错误。

  通常情况下不会把“真正的、庞大的”文件表塞到狭小的 PCB 中, 一般只要在 PCB 中建立个文件描述符数组就可以了,该数组成员不需要是真正的文件结构(至少包括 3 个成员),出于简单处理,咱们用 int 整型就足够了,用它存储文件表中文件结构的下标,如果您愿意的话,可以用位图或链表为任务支持更多,甚至无限的最大打开文件数。当用户进程打开文件时,文件系统给用户进程返回的是该进程 PCB 中文件描述符数组下标值,也就是文件描述符。

  Linux通过文件描述符查找文件数据块的过程,这涉及到以下三个数据结构,它们都是位于内存中的。

  1. PCB 中的文件描述符数组。
  2. 存储所有文件结构的文件表。
  3. inode 队列,也就是 inode 缓存 。

 

  某进程把文件描述符作为参数提交给文件系统时,文件系统用此文件描述符在该进程的 PCB 中的文件描述符数组中索引对应的元素,从该元素中获取对应的文件结构的下标,用该下标在文件表中索引相应的文件结构,从该文件结构中获取文件的 inode,最终找到了文件的数据块 。 若该 inode 在 inode 队列中不存在,此时会多一个处理过程,文件系统会从硬盘上将该 inode 加载到 inode 队列中,并使文件结构中的fd_inode 指向它。

 

posted on 2022-02-14 22:39  苦李  阅读(248)  评论(0)    收藏  举报