inode
目录
硬盘是低速设备,其读写单位是扇区,为了避免频繁访问硬盘,操作系统不会有了一扇区数据就去读写一次磁盘,往往等数据积攒到“足够大小”时才一次性访问硬盘,这足够大小的数据就是块,硬盘读写单位是扇区,因此一个块是由多个扇区组成的,块大小是扇区大小的整数倍
块是文件系统的读写单位,因此文件至少要占据一个块
拿 FAT 文件系统来说,FAT 称为文件分配表,在此文件系统中存储的文件,其所有的块被用于链式结构来组织,在每个块的最后存储下一个块的地址,从而块与块之间串联到一起,但通过链式结构来组织文件的弊端是当访问文件中的某个块时,必须要从头开始遍历块结点,软件上算法效率低下,而且每访问一个结点,就要涉及一次硬盘寻道,使得对原本低速的设备访问更加频繁了。
下面讨论下另一种文件组织形式,UINX 操作系统中的索引结构—inode
采用索引结构的文件系统,文件中的块依然可以分散到不连续的零散空间中,保留了磁盘高利用率的优点,更重要的是文件系统为每个文件的所有块建立了一个索引表,索引表就是块地址数组,每个数组元素就是块的地址,数组元素下标是文件块的索引,第n 个数组元素指向文件中的第n 个块,这样访问任意一个块的时候,只要从索引表中获得块地址就可以了,速度大大提升。
包含此索引表的索引结构称为inode,即index node,索引结点,用来索引、跟踪一个文件的所有块。
在UINX文件系统中,一个文件必须对应一个inode,磁盘中有多少文件就有多少inode。
用索引结构的缺点是索引表本身要占用一定的存储空间,文件要是很大时,块就比较多,索引表项就要跟着增多。
UNIX为解决这个问题采取了折中的方法,将一部分块放在索引表中,如果文件很大,将其他块放在另一个索引表,具体做法是:每个索引表中共 15 个索引项,暂时称此索引表为老索引表。老索引表中前 12 个索引项是文件的前 12 个块的地址,它们是文件的直接块,即可直接获得地址的块。若文件大于 12 个块,那就再建立个新的块索引表,新索引表称为一级间接块索引表,表中可容纳 256 个块的地址,各表项都是块的地址,这 256 个块地址需要通过一级间接块索引表才能获得,因此称为“间接块”,这也是一级间接块索引表中包含“间接”二字的原因。此表也要占用一个物理块来存储,该物理块的地址存储到老索引表的第 13 个索引项中。
在 inode 结构中,几乎囊括了一个文件的所有信息,i 结点编号是指此 inode 的序号,这通常是指它在 inode 数组中的下标。权限是指读、写、执行。属主是指文件的拥有者,时间是指创建时间、修改时间、访问时间等。文件大小是指文件的字节尺寸。
只要用于管理、控制文件相关信息的数据结构都被称为 FCB(File Contrl Block),即文件控制块,inode 也是这种结构,因此 inode 是 FCB 的一种。
inode 是文件在文件系统上的元信息(文件本身的元信息是它自己的文件头),要想通过文件系统获得文件的实体,必须先要找到文件的 inode,从这个意义上来说,inode 等同于文件
Linux 中每分区的 inode 数量是固定的,可以用 tune2fs 命令查看 inode 数量。inode 的数量等于文件的数量,为方便管理,分区中所有文件的 inode 通过一个大表格来维护,此表格称为 inode_table。inode_table 本质上就是 inode 数组,数组元素的下标便是文件 inode 的编号
文件系统是如何把文件名和 inode 关联到一起的呢?
在 Linux 中,目录和文件都用 inode 来表示,因此目录也是文件,只是目录是包含文件的文件
既然同一种 inode 既用来表示普通文件,又用来表示目录文件,inode 结构相同,因此区分该 inode 是普通文件,还是目录文件,唯一的地方只能是数据块本身的内容了。如果该 inode 表示的是普通文件,此 inode 指向的数据块中的内容应该是普通文件自己的数据。如果该 inode表示的是目录文件,此 inode 指向的数据块中的内容应该是该目录下的目录项。因此目录中要么是普通文件的目录项,要么是目录文件的目录项。
文件类型中,'d'表示文件是目录,'-'表示文件是普通文件。
目录相当于个文件列表(或者是表格),每个文件在目录中都是一个 entry(条目、项),各个 entry 中的内容包括文件名、文件类型,为了定位文件的数据,entry 中至少还要包括 inode 编号,这个 entry 是目录中各个文件的描述,它称为目录项
目录项中包含文件名、inode 编号和文件类型。作用有两个,一是标识此 Inode 表示的文件是目录,还是普通文件,也就是 inode 所指向数据块中的内容是什么。二是将文件名与 inode 做个绑定关联,这样用户便可以通过文件名来找到文件的实体数据。
有了目录项后,通过文件名找文件实体数据块的流程是。 (1)在目录中找到文件名所在的目录项。 (2)从目录项中获取 inode 编号。 (3)用 inode 编号作为 inode 数组的索引下标,找到 inode。 (4)从该 inode 中获取数据块的地址,读取数据块。
文件的块——>索引表来管理——>保存在inode结构体中——>inode_table来管理
创建文件的本质是创建了文件的文件控制块,即目录项和 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 数组在哪里?大小是多少?
对于文件系统也一样,我们需要在某个固定地方去获取文件系统元信息的配置,这个地方就是超级块,超级块是保存文件系统元信息的元信息。
通常情况下魔数用来确定文件系统的类型的标志,用它来区 别于其他文件系统。比如某个操作系统支持多种文件系统,通常就是根据此魔数先判断文件系统类型,然后调用不同的文件系统驱动程序访问该分区。
超级块是文件系统元信息的“配置文件”,它是在为分区创建文件系统时创建的,所有有关文件系统元信息的配置都在超级块中,因此超级块的位置和大小不能再被“配置”了,必须是固定的,它被固定存储在各分区的第 2 个扇区,通常是占用一个扇区的大小,具体大小与实际文件系统类型为准。
文件描述符
文件描述符所描述的对象是文件的操作。看下它与 inode 的区别和联系:
读写文件的本质是:先通过文件的 inode 找到文件数据块的扇区地址,随后读写该扇区,从而实现了文件的读写
文件每被打开一次,文件读写的偏移量都可以任意指定,即对同一个文件的多次读写都是各自操作各自的,任意一个文件操作的偏移量都不影响其他文件操作的偏移量
偏移量应该记录在哪里呢?
为解决这个问题,Linux 提供了称为“文件结构”的数据结构(也称为 file 结构),专门用于记录与文件操作相关的信息,每次打开一个文件就会产生一个文件结构,多次打开该文件就为该文件生成多个文件结构,各自文件操作的偏移量分别记录在不同的文件结构中,从而实现了“即使同一个文件被同时多次打开,各自操作的偏移量也互不影响”的灵活性
所有的“文件结构”组织到一起形成数组统一管理 该数组称为文件表
总结一下,inode用于描述文件存储相关信息,文件结构用于描述“文件打开”后,文件读写偏移量等信息。文件与 inode一一对应,一个文件仅有一个 inode,一个 inode 仅对应一个文件。一个文件可以被多次打开,因此一个inode 可以有多个文件结构,多个文件结构可以对应同一个 inode。
文件描述符只是个整数,准确地说,它是 PCB 中文件描述符数组元素的下标,只不过此数字并不用来表示“数量”,而是用来表示“位置”,它是位于进程 PCB 中的文件描述符数组的元素的下标,而文件描述符数组元素中的信息又指向文件表中的某个文件结构。
为什么文件描述符是数字,而不是像其他描述符那样,是个具有多个成员属性的复合数据结构?原因有两个。
(1)为了一视同仁,使各进程可打开的文件数是一样的,各进程必须有独立的、大小完全一样的一套文件描述符数组,而不能所有进程共享同一套文件描述符数组,比如 A、B 两个进程都可以用文件描述符9 指向任意文件(相同或不同的文件都可以)。相反,如果所有进程共享同一套文件描述符数组的话,如果进程 A 占用了文件描述符 9,进程 B 只有使用其他空闲的文件描述符
(2)文件结构中包含进程执行文件操作的偏移量,它属于与各个任务单独绑定的资源,因此最好放在PCB 中管理。但当进程打开的文件数增多的时候,文件表(由文件结构组成的数组)占用的空间较大,而 PCB 占用的内存通常就是几个页框,Linux 中的 PCB 也只是 2 页框大小,咱们 PCB 只占用 1 页框。
结合以上两个原因,通常情况下不会把“真正的、庞大的”文件表塞到狭小的 PCB 中,一般只要在PCB 中建立个文件描述符数组就可以了,该数组成员不需要是真正的文件结构(至少包括 3 个成员),出于简单处理,咱们用 int 整型就足够了,用它存储文件表中文件结构的下标。当用户进程打开文件时,文件系统给用户进程返回的是该进程 PCB 中文件描述符数组下标值,也就是文件描述符。
Linux 是如何通过一个简单的数字,也就是文件描述符来找到文件数据块的?
这涉及到以下三个数据结构,它们都是位于内存中的。
(1)PCB 中的文件描述符数组。
(2)存储所有文件结构的文件表。
(3)inode 队列,也就是 inode 缓存。
某进程把文件描述符作为参数提交给文件系统时,文件系统用此文件描述符在该进程的 PCB 中的文件描述符数组中索引对应的元素,从该元素中获取对应的文件结构的下标,用该下标在文件表中索引相应的文件结构,从该文件结构中获取文件的 inode,最终找到了文件的数据块。提示一下,若该 inode 在 inode 队列中不存在,此时会多一个处理过程:文件系统会从硬盘上将该 inode 加载到 inode 队列中,并使文件结构中的 fd_inode 指向它。
PCB 中文件描述符数组是提前在 task_struct 中构建好的,文件表也是提前构建好的全局数据结构,inode 队列也已经构建好了,因此笼统地说,创建文件描述符的过程就是逐层在这三个数据结构中找空位,在该空位填充好数据后返回该位置的地址,比如:
(1)在全局的 inode 队列中新建一 inode(这肯定是在空位置处新建),然后返回该 inode 地址。
(2)在全局的文件表中的找一空位,在该位置填充文件结构,使其 fd_inode 指向上一步中返回的 inode地址,然后返回本文件结构在文件表中的下标值。
(3)在 PCB 中的文件描述符数组中找一空位,使该位置的值指向上一步中返回的文件结构下标,并返回本文件描述符在文件描述符数组中的下标值。
参考《操作系统真象还原》