参考资料:阿秀的笔记
文件系统
- 1. 文件系统的基本组成
- 2. 文件的使用
- 3.文件如何存储
- 3.1 目录怎么存储
- 4.Linux继承于Unix系统的Unix文件实现方式
- 4.1 Linux Ext 2/3 文件系统
- 4.2 Linux Ext 4 文件系统
- 4.3 磁盘空闲空间的管理机制
- 4.3.1 空闲表法
- 4.3.2 空闲链表法
- 4.3.3 位图法(Linux系统使用的方法)
- 5. 文件系统的结构
- 6.文件怎么取别名
- 7.文件怎么读写I/O
- 7.1 缓冲与非缓冲 I/O
- 7.2 直接与非直接 I/O
- 7.3 阻塞与非阻塞 I/O VS 同步与异步 I/O
1. 文件系统的基本组成
Linux 「一切皆文件」不仅普通的文件和目录,就连块设备、管道、socket 等,也都是统一交给文件系统管理的。
如何唯一标识文件?
索引节点 inode,记录了文件的元信息,比如 inode 编号、文件大小、访问权限、创建时间、修改时间、数据在磁盘的位置等等。存储在硬盘中占用磁盘空间
如何组织文件之间的关系?
目录项directory entry,记录文件的名字、索引节点指针以及与其他目录项的层级关联关系。通过多个目录项的关联就能形成目录结构。目录项是由内核维护的一个数据结构,不存放于磁盘,而是缓存在内存。
注意,一个文件可以有多个别名,所以目录项和索引节点的关系是多对一,即多个目录项中的索引节点可以指向同一个文件。
Linux中目录和目录项关系是什么?
目录是个文件,持久化存储在磁盘,而目录项是内核一个数据结构,缓存在内存。由于查询目录频繁从磁盘读效率低,所以内核会把已经读过的目录用目录项这个数据结构缓存在内存,下次再次读到相同的目录时,只需从内存读就可以,大大提高了文件系统的效率。目录项可以视为一个表示目录的数据结构。
文件系统如何把文件存储在磁盘?
磁盘读写的最小单位是扇区 512B,文件系统把多个扇区组成了一个逻辑块。Linux 中的逻辑块大小为 4KB。
磁盘为什么要分为三个储存分区?
便于数据组织和管理,磁盘进行格式化时分成三个存储区域,分别是超级块、索引节点区和数据块区:
- 超级块,用来存储文件系统的详细信息,比如块个数、块大小、空闲块等等;
- 索引节点区,用来存储索引节点;
- 数据块区,用来存储文件或目录数据。
超级块和索引节点区数据会在需要时加载到内存中:
-
超级块:当文件系统挂载时进入内存;
-
索引节点区:当文件被访问时进入内存
文件系统的实现确实包含在内核中,但并不是所有文件系统信息都一开始就加载到内核空间中,而是根据需要进行加载和缓存,以提高性能和节省内存。文件系统首先要先挂载到某个目录才可以正常使用,比如 Linux 系统在启动时,会把文件系统挂载到根目录。
文件种类很多导致文件系统也很多,文件系统如何提供用户易用性?
Linux系统会根据不同的需求和限制选择适合特定文件类型的文件系统,来更好地满足用户的需求,并提供更好的性能、可靠性和功能特性。
Linux 支持的文件系统也不少,根据存储位置的不同,可以把文件系统分为三类:
- 磁盘的文件系统把数据存储在磁盘中,比如 Ext 2/3/4、XFS 等。
- 内存的文件系统,数据占用内存空间,比如 /proc 和 /sys 文件系统,读写这类文件,实际上是读写内核中相关的数据。
- 网络的文件系统,用来访问其他计算机主机数据的文件系统,比如 NFS、SMB 等等。
Linux为了对用户提供一个统一的接口,在用户层与文件系统层引入了中间层,虚拟文件系统(Virtual File System,VFS)。
2. 文件的使用
VFS为不同的文件系统提供了一个抽象层次,隐藏了文件系统的具体实现细节。这使得内核可以支持多种不同的文件系统,而用户程序不需要关心文件系统的具体细节。VFS提供了文件描述符来进行读取、写入、关闭文件等操作。提供了虚拟的目录结构,用于管理所有已挂载的文件系统(Linux支持可插拔的文件系统模块,可以在运行时加载和卸载不同类型的文件系统,而不需要重新编译内核)。
从用户角度来看文件的使用过程:
3.文件如何存储
3.1 目录怎么存储
目录是一个特殊的文件。每个文件在存储时都用一个inode表(相当于索引了)来存放文件元信息,包含文件地址(指向的是数据块)等。
目录的数据块里面保存的是目录里面一项一项的文件信息。
保存目录的格式改成哈希表,对文件名进行哈希计算,把哈希值保存起来,如果我们要查找一个目录下面的文件名,可以通过名称取哈希。如果哈希能够匹配上,就说明这个文件的信息在相应的块里面。
Linux 系统的 ext 文件系统就是采用了哈希表,来保存目录的内容,这种方法的优点是查找非常迅速,插入和删除也较简单,不过需要一些预备措施来避免哈希冲突。
目录查询是通过在磁盘上反复搜索完成,需要不断地进行 I/O 操作,开销较大。所以,为了减少 I/O 操作,把当前使用的文件目录缓存在内存,以后要使用该文件时只要在内存中操作,从而降低了磁盘操作次数,提高了文件系统的访问速度。
4.Linux继承于Unix系统的Unix文件实现方式
本节中的4.1和4.2讲述了Linux文件的存储如何实现,本质是针对已经写入数据的数据块的组织和管理。4.3讲述空闲空间管理,针对数据块应该放在硬盘上的哪个位置?最简单的思想就是将硬盘所有的区扫描一遍,找个空的地方随便放,但这样显然效率低下,所以引入了针对磁盘的空闲空间也是要引入管理的机制。
4.1 Linux Ext 2/3 文件系统
在实际中采用了多级组合方式:
第一级组合相当于索引:文件头中的inode(索引节点) 是一个数据结构,记录了文件在文件系统中的基本信息,包括文件的物理位置。从效果上相当于一个索引数据块,存放指向文件数据块的指针列表。
通过第一级索引块来存放第二级索引块:
- 10 个指向数据块的指针;(如果存放文件所需的数据块小于 10 块,则采用直接查找的方式)
- 第 11 个指向索引块的指针;(如果存放文件所需的数据块超过 10 块,则采用一级间接索引方式)
- 第 12 个指向二级索引块的指针;(如果前面两种方式都不够存放大文件,则采用二级间接索引方式)
- 第 13 个指向三级索引块的指针;(如果二级间接索引也不够存放大文件,这采用三级间接索引方式)
优点:解决大文件的存储
缺点:对于大文件的访问,需要大量的查询,效率比较低。
4.2 Linux Ext 4 文件系统
对Ext3文件系统的改进和扩展:
- 支持更大的文件大小(最大16TB)和更大的分区大小(最大1EB),这使得Ext4更适合于处理大型文件和存储设备。
- 改进的文件分配算法和数据结构,以提高文件的分配和管理效率。例如,采用了多级索引、延迟分配等技术来减少磁盘碎片化,并优化了空闲块管理算法。
延迟分配(Delayed Allocation)是一种文件系统优化技术,旨在减少磁盘碎片化并提高文件写入性能。它的基本思想是推迟文件系统实际分配磁盘空间的时间,直到需要写入数据时才进行实际的分配。这种技术在磁盘写入过程中可以使得文件系统更有效地组织数据,从而提高文件系统的性能。
4.3 磁盘空闲空间的管理机制
4.3.1 空闲表法
针对连续空间存放的文件存储方法,可以使用空闲表法:为所有空闲空间建立一张表,表内容包括空闲区的第一个块号和该空闲区的块个数。
当请求分配磁盘空间时,系统依次扫描空闲表里的内容,直到找到一个合适的空闲区域为止。当用户撤销一个文件时,系统回收文件空间。这时,也需顺序扫描空闲表,寻找一个空闲表条目并将释放空间的第一个物理块号及它占用的块数填到这个条目中。
使用场景:建立连续文件。少量的空闲区。
4.3.2 空闲链表法
空闲链表法只要在主存中保存一个指针,令它指向第一个空闲块。当创建文件需要一块或几块时,就从链头上依次取下一块或几块。反之,当回收空间时,把这些空闲块依次接到链头上。
其特点是简单,但不能随机访问,工作效率低,因为每当在链上增加或移动空闲块时需要做很多 I/O 操作,同时数据块的指针消耗了一定的存储空间。空闲表法和空闲链表法都不适合用于大型文件系统,因为这会使空闲表或空闲链表太大。
4.3.3 位图法(Linux系统使用的方法)
在 Linux 文件系统就采用了位图的方式来管理空闲空间,不仅用于数据空闲块的管理,还用于 inode 空闲块的管理,因为 inode 也是存储在磁盘的,自然也要有对其管理。
位图法利用二进制的一位来表示磁盘中一个盘块的使用情况,磁盘上所有的盘块都有一个二进制位与之对应。当值为 0 时,表示对应的盘块空闲,值为 1 时,表示对应的盘块已分配。它形式如下:
1111110011111110001110110111111100111 ...
5. 文件系统的结构
数据块的位图是放在磁盘块里,一个块 4KB,每位表示一个数据块,共可以表示 4 ∗ 1024 ∗ 8 = 2 15 4 * 1024 * 8 = 2^{15} 4∗1024∗8=215个空闲块,,由于 1 个数据块是 4K 大小,那么最大可以表示的空间为 2 1 5 ∗ 4 ∗ 1024 = 2 27 2^15 * 4 * 1024 = 2^{27} 215∗4∗1024=227 个 byte,也就是 128M。
提到 Linux 是用位图的方式管理空闲空间,用户在创建一个新文件时,Linux 内核会通过 inode 的位图找到空闲可用的 inode,并进行分配。如果采用「一个块的位图 + 一系列的块」,外加「一个块的 inode 的位图 + 一系列的 inode 的结构」能表示的最大空间也就 128M。
所以Linux系统引入了块组结构。下图给出了 Linux Ext2 整个文件系统的结构和块组的内容,文件系统都由大量块组组成,在硬盘上相继排布:
Ext2 文件系统的结构和块组的内容中
- 引导块(boot block)是指存储引导加载程序(boot loader)的区域。
- 块组(Block Group):结构
- 超级块,包含的是文件系统的重要信息,比如 inode 总个数(每个文件都对应一个 Inode)、块总个数(存储文件内容的区域个数)、每个块组的 inode 个数(文件系统将 Inode 分为若干块组,每个块组包含一定数量的 Inode)、每个块组的块个数等等。
- 块组描述符,包含文件系统中各个块组的状态,比如块组中空闲块和 inode 的数目等,每个块组都包含了文件系统中「所有块组的组描述符信息」。
- 数据位图和 inode 位图, 用于表示对应的数据块或 inode 是空闲的,还是被使用中。
- inode 列表,包含了块组中所有的 inode,inode 用于保存文件系统中与各个文件和目录相关的所有元数据。
- 数据块,包含文件的有用数据。
6.文件怎么取别名
硬链接(Hard Link) :多个目录项中的「索引节点」指向一个文件,也就是指向同一个 inode,但是 inode 是不可能跨越文件系统的,每个文件系统都有各自的 inode 数据结构和列表,所以硬链接是不可用于跨文件系统的。
由于多个目录项都是指向一个 inode,那么只有删除文件的所有硬链接以及源文件时,系统才会彻底删除该文件。
软链接(Symbolic Link):软链接相当于重新创建一个文件,这个文件有独立的 inode,但是这个文件的内容是另外一个文件的路径,所以访问软链接的时候,实际上相当于访问到了另外一个文件,所以软链接是可以跨文件系统的,甚至目标文件被删除了,链接文件还是在的,只不过指向的文件找不到了而已。
7.文件怎么读写I/O
7.1 缓冲与非缓冲 I/O
指在进行输入/输出(I/O)操作时数据的处理方式不同。
缓冲 I/O 是指在进行 I/O 操作时,系统会先将数据从磁盘读取到内存缓冲区中,然后再从缓冲区中读取或写入数据到文件。这样的方式可以减少实际 I/O 操作的次数,提高效率。
例如,C/C++ 中的标准 I/O 函数(如 fread()、fwrite()、fgets()、fputs() 等)都是采用缓冲 I/O 的方式。
非缓冲 I/O 是指在进行 I/O 操作时,数据直接从磁盘中读取或写入到目标文件,没有经过缓冲区的中间处理。如在通信领域或一些特殊的硬件操作中。
例如,Unix/Linux 中的 read() 和 write() 等系统调用就是非缓冲 I/O 的方式。
7.2 直接与非直接 I/O
关于数据传输时是否涉及的中间缓冲区
直接 I/O 是指数据在进行输入/输出操作时,数据可以直接从用户空间(例如应用程序)直接传输到设备或文件系统,而无需经过内核缓冲区的中转。
例如在 Linux 中可以使用 O_DIRECT 标志来指示直接 I/O。程序直接将数据从用户空间写入到文件系统的某个位置,跳过内核空间的缓冲区,直接写入磁盘。
非直接 I/O 是指数据在进行输入/输出操作时,数据必须先从用户空间复制到内核空间的缓冲区中,然后再从内核空间的缓冲区传输到设备或文件系统。
大多数操作系统默认采用非直接 I/O 的方式。内核可以更好地管理数据的传输,因此可能更加稳定可靠。
7.3 阻塞与非阻塞 I/O VS 同步与异步 I/O
阻塞与非阻塞 I/O:描述了程序在进行 I/O 操作时是否会暂停执行
- 阻塞 I/O 是指程序在进行 I/O 操作时,如果无法立即得到结果,程序会暂停执行(阻塞),直到得到结果为止。
- 非阻塞 I/O 是指程序在进行 I/O 操作时,如果无法立即得到结果,程序不会暂停执行,而是会立即返回一个状态指示暂时无法完成操作,然后程序可以继续执行其他任务。
与异步 I/O 不同的是,非阻塞 I/O 下的程序并不会主动等待 I/O 操作完成的通知或结果。 相反,它会立即返回一个状态,告诉程序当前的 I/O 操作是否完成,然后程序可以根据这个状态继续执行其他任务,或者进行其他操作,而不需要等待 I/O 操作完成。
- 阻塞 I/O 的优点是简单易用,但可能会造成程序在等待 I/O 完成时浪费大量的时间。非阻塞 I/O 则可以让程序更加灵活地处理 I/O 操作,但需要额外的代码来处理返回的状态。
同步与异步 I/O:述了程序在发起 I/O 操作后是否需要等待操作完成。
- 同步 I/O 是指程序在发起 I/O 操作后,需要等待操作完成才能继续执行后续的任务,即发起 I/O 操作的线程需要主动等待 I/O 操作完成。
- 异步 I/O 是指程序发起 I/O 操作后,不需要等待操作完成,而是可以继续执行后续的任务,当操作完成后会得到通知,或者由另一个线程来处理完成的操作。
- 同步 I/O 的优点是编程模型简单,易于理解和控制,但可能会造成程序在等待 I/O 操作完成时浪费大量的时间。异步 I/O 则可以让程序更加高效地利用系统资源,但需要额外的处理机制来处理操作完成的通知。