40.1 思考方式
文件系统两个重要方面:
- 文件系统的数据结构。
- 访问方法。
40.2 整体组织
文件系统必须记录每个文件的元数据的关键部分,并且记录诸如文件包含哪些数据块、文件大小,其所有者和访问权限、访问和修改时间等。文件的inode结构用于存储这些信息。
为了存放inode,还需要在磁盘上留出一些空间inode表,只是保存了一个磁盘上的inode的数组。
位图(bitmap),用于记录分配。一种用于数据区域,另一种用于inode表。每个位用于指示相应的对象/块是空闲(0)还是正在使用(1)。
包含位图的文件系统如下:
此时还留有一块,保留给超级块。超级块包含该特定文件系统的信息,包括例如文件系统中有多少inode和数据块(此例中为80和56)、inode表开始位置(块3)。
在挂载文件系统时,OS首先读取超级块,初始化各种参数,然后将该卷添加到文件系统树中。所以挂载后超级块是在内存中的。
40.3 文件组织:inode
inode是index node(索引节点)的缩写。每个inode都由一个数字隐式引用。
文件中除了纯粹的用户数据外,其他任何信息通常都成为元数据。
多级索引
为了支持更大的文件,必须在inode中引入不同的结构,使用间接指针,指向包含更多指针的块,每个指针指向用户数据。所以如果inode可以有一些固定数量的直接指针和一个间接指针。如果文件变得足够大,则会分配一个间接块(来自磁盘的数据块区域),并将inode的间接指针设置为指向它。
40.4 目录组织
一个目录只包含一个二元组(条目名称,inode号)的列表。对于给定目录中的每个文件或目录,目录的数据块中都有一个字符串和一个数字。对于每个字符串,可能还有一个长度。
40.5 空闲空间管理
当创建一个文件时,必须为该文件分配一个inode。文件系统通过位图搜索一个空闲的内容,并分配给该文件,然后标记为已使用1
。
Linux文件系统在创建新文件并需要数据块的时候,会寻找一系列空闲块,并分配给文件;所以文件系统保证文件的一部分将在磁盘上并且是连续的。
40.6 访问路径:读取和写入
从磁盘读取文件
当发出一个open("/foo/bar",O_RDONLY)
调用时,文件系统遍历路径名。从而找到需要的bar的inode。
所有遍历都从文件系统的根目录开始,记作/。所以文件系统第一次磁盘读取是根目录的inode。要找到inode必须知道i-number,通常在其父目录中。根目录没有父目录,所以根的inode号为2,这是挂载文件系统时就规定好的。所以开始遍历过程时,文件系统会读入inode号为2的块(第一个inode块)。
当inode被读入,文件系统在其中查找指向数据块的指针,数据块包含根目录内容,在其中寻找foo的条目。最终找到时,即确定了下一个需要的foo的inode号。
后面便是递归遍历路径名,直到找到所需的inode。open()
的最后一步就是将bar的inode读入内存,文件系统进行最后的权限检查,在每个进程的打开文件表中,为此进程分配一个文件描述符,并将它返回给用户。
open
导致的I/O量与路径名成正比。对于路径中每个增加的目录,都必须读取它的inode和数据
整个过程的流程图如下。打开文件导致了多次读取,以便最终获得文件的inode。之后读取每个块需要文件系统首先查询inode,然后读取该块,再使用写入更新inode的最后访问时间字段。
写入磁盘
写入文件可能会分配一个块。当写入一个新文件时,每次写入操作需要首先决定将哪个块分配给文件,从而更新磁盘的其他结构(如数据位图和inode)。
因此每次写入文件在逻辑上会导致5个I/O:
- 一个读取数据位图(然后更新以标记新分配的块被使用)
- 一个写入位图(将它的新状态存入磁盘)
- 两次读取
- 写入inode(用新块的位置更新)
- 写入真正的数据块本身
40.7 缓存和缓冲
为了弥补读取和写入文件的代价,大多数文件系统使用系统内存来缓存重要的块。
延迟写入也可以显著提升性能。