前言
文件系统是操作系统中负责管理持久数据的子系统,将用户的文件保存在硬盘等硬件设备中,即使断电了数据也不会丢失。
对于用户而言,文件是存储的最小单位,再少的数据也需要以文件的形式存储在外部存储器中。以硬盘为例,磁盘读写的最小单位是扇区,扇区的大小只有 512B
大小,文件系统把多个扇区组成了一个逻辑块,每次读写的最小单位就是逻辑块(数据块),Linux 中的逻辑块大小为 4KB
,也就是一次性读写 8 个扇区,这将大大提高了磁盘的读写效率。
类似于内存分页,硬盘分为一个个扇区用于存储文件,文件存储的方式主要有连续存储、链式存储和索引式存储三种,现在常用的为索引式存储方式。
文件存储
文件存储主要分为连续空间存储和非连续空间存储两种。
连续空间存储
文件存放在磁盘连续的物理空间中。这种模式下,文件的数据都是紧密相连,读写效率很高,因为一次磁盘寻道就可以读出整个文件。但是有磁盘空间碎片和文件长度不易扩展的缺陷。
非连续空间存储
非连续空间存储方式主要分为链表方式和索引方式,而链表方式又分为隐式链表和显式链表两种。
链表方式
隐式链表
文件头要包含第一块和最后一块的位置,并且每个数据块里面留出一个指针空间,用来存放下一个数据块的位置,这样一个数据块连着一个数据块,从链头开始就可以顺着指针找到所有的数据块,所以存放的方式可以是不连续的。
隐式链表的存放方式的缺点在于无法直接访问数据块,只能通过指针顺序访问文件,以及数据块指针消耗了一定的存储空间。
显式链表
把用于链接文件各数据块的指针,显式地存放在内存的一张链接表中,该表在整个磁盘仅设置一张,每个表项中存放链接指针,指向下一个数据块号。
由于查找记录的过程是在内存中进行的,因而不仅显著地提高了检索速度,而且大大减少了访问磁盘的次数。但也正是整个表都存放在内存中的关系,它的主要的缺点是不适用于大磁盘。
索引方式
索引的实现是为每个文件创建一个索引数据块(在磁盘中),里面存放的是指向文件数据块的指针列表,另外,文件头需要包含指向索引数据块的指针,这样就可以通过文件头知道索引数据块的位置,再通过索引数据块里的索引信息找到对应的数据块。
索引的方式优点在于:
- 文件的创建、增大、缩小很方便;
- 不会有碎片的问题;
- 支持顺序读写和随机读写;
由于索引数据也是存放在磁盘块的,如果文件很小,明明只需一块就可以存放的下,但还是需要额外分配一块来存放索引数据,所以缺陷之一就是存储索引带来的开销。
如果文件很大,大到一个索引数据块放不下索引信息,这时可以通过两种方式处理大文件的存放:
链表 + 索引的组合:这种组合称为「链式索引块」,它的实现方式是在索引数据块留出一个存放下一个索引数据块的指针,于是当一个索引数据块的索引信息用完了,就可以通过指针的方式,找到下一个索引数据块的信息。
还有另外一种组合方式是索引 + 索引的方式,这种组合称为「多级索引块」,实现方式是通过一个索引块来存放多个索引数据块,一层套一层索引。
空闲空间管理
空闲表法
空闲表法就是为所有空闲空间建立一张表(在磁盘中),表内容包括空闲区的第一个块号和块个数,这种方式是连续分配的。
当请求分配磁盘空间时,系统依次扫描空闲表里的内容,直到找到一个合适的空闲区域为止;当删除文件时,也需顺序扫描空闲表,寻找一个空闲表条目并将释放空间的第一个物理块号及它占用的块数填到这个条目中。
这种方法仅当有少量的空闲区时才有较好的效果。因为,如果存储空间中有着大量的小的空闲区,则空闲表变得很大,这样查询效率会很低。另外,这种分配技术适用于建立连续文件。
空闲链表法
也可以使用「链表」的方式来管理空闲空间,每一个空闲块里有一个指针指向下一个空闲块,这样也能很方便地找到空闲块并管理起来。
当创建文件需要一块或几块时,就从链头上依次取下一块或几块。当回收空间时,把这些空闲块依次接到链头上。
这种技术只要在主存中保存一个指针,令它指向第一个空闲块。其特点是简单,但不能随机访问,工作效率低,因为每当在链上增加或移动空闲块时需要做很多 I/O 操作,同时数据块的指针消耗了一定的存储空间。
空闲表法和空闲链表法都不适合用于大型文件系统,因为这会使空闲表或空闲链表太大。
位图法
位图是利用二进制的一位来表示磁盘中一个盘块的使用情况,磁盘上所有的盘块都有一个二进制位与之对应。
当值为 0 时,表示对应的盘块空闲,值为 1 时,表示对应的盘块已分配。
Linux 文件系统就采用了位图的方式来管理空闲空间。用户在创建一个新文件时,Linux 内核会通过 inode 的位图找到空闲的 inode,并进行分配。要存储数据时,会通过块的位图找到空闲的块,并分配。
数据块的位图是放在磁盘块里的,假设是放在一个块里,一个块 4KB,每位表示一个数据块,共可以表示 4 * 1024 * 8 = 2^15
个空闲块,由于 1 个数据块是 4KB 大小,那么最大可以表示的空间为 2^15 * 4 * 1024 = 128MB
,即一个数据块地位图可以表示 128MB 大小的空间的使用情况。
Linux的文件系统
Linux 文件系统会为每个文件分配两个数据结构:索引节点(index node)和目录项(directory entry),它们主要用来记录文件的元信息和目录层次结构。
- 索引节点,也就是 inode,用来记录文件的元信息,比如 inode 编号、文件大小、访问权限、创建时间、修改时间、数据在磁盘的位置等等。索引节点是文件的唯一标识,它们之间一一对应,也同样都会被存储在硬盘中,所以索引节点同样占用磁盘空间。
- 目录项,也就是 dentry,用来记录文件的名字、索引节点指针以及与其他目录项的层级关联关系。多个目录项关联起来,就会形成目录结构,但它与索引节点不同的是,目录项是由内核维护的一个数据结构,不存放于磁盘,而是缓存在内存。
索引节点中记录了除文件名外的所有文件信息,文件名是存在目录项中的。
由于索引节点唯一标识一个文件,而目录项记录着文件的名字,所以目录项和索引节点的关系是多对一,也就是说,一个文件可以有多个别名。比如硬链接的实现就是多个目录项中的索引节点指向同一个文件。
索引节点是存储在硬盘上的数据,为了加速文件的访问,通常会把索引节点加载到内存中。
每个索引节点的大小,一般是 128B 或256B,且总数在格式化时就已经确定,一般是每 1KB 或每 2KB 设置一个索引节点。
由于每个文件都必须有一个 inode,因此有可能发生 inode 已经用光,但是硬盘还未存满的情况,此时就无法在硬盘上创建新文件。
磁盘进行格式化的时候,会被分成三个存储区域,分别是超级块、索引节点区和数据块区。
- 超级块:用来存储文件系统的详细信息,比如块个数、块大小、空闲块等等。
- 索引节点区:用来存储索引节点;
- 数据块区:用来存储文件或目录数据;
目录
目录是一种特殊的文件,普通文件的块里面保存的是文件数据,而目录文件的块里面保存的是目录里面一项一项的文件信息。
在目录文件的块中,最简单的保存格式就是列表,就是一项一项地将目录下的文件信息(如文件名、文件 inode、文件类型等)列在表里。
列表中每一项就代表该目录下的文件的文件名和对应的 inode,通过这个 inode,就可以找到真正的文件。
通常,第一项是「.
」,表示当前目录;第二项是「..
」,表示上一级目录,接下来就是一项一项的文件名和 inode。
如果一个目录有超级多的文件,我们要想在这个目录下找文件,按照列表一项一项的找,效率就不高了。
于是,保存目录的格式改成哈希表,对文件名进行哈希计算,把哈希值保存起来,如果我们要查找一个目录下面的文件名,可以通过名称取哈希。如果哈希能够匹配上,就说明这个文件的信息在相应的块里面。
Linux 系统的 ext 文件系统就是采用了哈希表,来保存目录的内容,这种方法的优点是查找非常迅速,插入和删除也较简单,不过需要一些预备措施来避免哈希冲突。
目录查询是通过在磁盘上反复搜索完成,需要不断地进行 I/O 操作,开销较大。所以,为了减少 I/O 操作,把当前使用的文件目录缓存在内存,以后要使用该文件时只要在内存中操作,从而降低了磁盘操作次数,提高了文件系统的访问速度。
链接
有时候我们希望给某个文件取个别名,那么在 Linux 中可以通过硬链接(Hard Link) 和软链接/符号链接(Soft/Symbolic Link) 的方式来实现,它们都是比较特殊的文件,但是实现方式也是不相同的。
硬链接
硬链接是多个目录项中的「索引节点」指向一个文件,也就是指向同一个 inode,但是 inode 是不可能跨越文件系统的,每个文件系统都有各自的 inode 数据结构和列表,所以硬链接是不可用于跨文件系统的。由于多个目录项都是指向一个 inode,那么只有删除文件的所有硬链接以及源文件时,系统才会彻底删除该文件。
软链接
软链接相当于重新创建一个文件,这个文件有独立的 inode,但是这个文件的内容是另外一个文件的路径,所以访问软链接的时候,实际上相当于访问到了另外一个文件,所以软链接是可以跨文件系统的,甚至目标文件被删除了,链接文件还是在的,只不过指向的文件找不到了而已。
权限
Linux 中用十个字符表示文件的权限。
第一个字符表示文件的类型:
- -:普通文件。
- d:目录文件,目录也是一种特殊的文件。
- l : 符号链接文件,实际上它指向另一个文件。
- b:块设备文件,例如硬盘的存储设备等。
- c:字符设备文件,如键盘等。
- s:套接字文件,此主要跟网络程序有关。
- p:管道文件。
第 2~10 个字符当中的每三个为一组,左边三个字符表示所有者权限,中间三个字符表示与所有者同一组的用户的权限,右边三个字符是其他用户的权限。每一组的权限顺序都是 rwx,即读、写、执行。每个字符代表的意义如下:
- r(Read,读权限):对文件而言,具有读取文件内容的权限;对目录而言,可以列出其中的内容。
- w(Write,写权限):对文件而言,具有新增、修改文件内容的权限;对目录而言,可以在该目录中创建、删除文件。
- x(eXecute,执行权限):对文件而言,具有执行文件的权限;对目录而言,可以搜索和访问该目录。
- -:表示不具有该项权限。
另外,还可以用数字表示各个权限,一般 r 表示 4;w 表示 2;x 表示 1,每组各自的权限是需要累加的,比如 rwx 就表示 4 + 2 + 1 = 7, rw- 就表示4 + 2 + 0 = 6。
例如:
-rw-rw-r-x(665):代表该文件为普通文件;文件所有者与同组用户对文件具有读写的权限;而其他用户仅具有读取和执行的权限。
drwx–x–x(711):代表该文件为目录文件;目录所有者具有读写与进入目录的权限;其他用户近能进入该目录,却无法读取其中的文件。
由于文件的名称是存储在目录项中的,所以只有目录的读权限但没有文件的读权限的话,可以看到目录下的文件的名称,但是无法进一步读取文件内容。