在内核中,每种设备类型都有与之对应的设备驱动程序,用来处理设备的所有 I/O 请求。设备驱动程序属于内核代码,可执行相关硬件的输入/输出动作。由设备驱动程序提供的 API 是固定的,包含文件系统调用open()
、close()
、read()
/write
、mmap()
以及ioctl()
,即提供了与文件 I/O 一致的接口,从而满足 I/O 操作的通用性。
可将设备划分为以下两种类型:
对于每个设备,系统中都有与之对应的设备专用文件。某些设备是实际存在的,比如键盘、磁盘等;而有些设备则是虚拟的,并不存在相应的硬件设备,但内核可以通过设备驱动程序提供一种抽象设备,其携带的 API 与真实设备无二。
设备文件通常位于/dev
目录下。超级用户可使用mknod
命令创建设备文件,特权级程序(CAP_MKNOD)也可调用mknod()
完成相同的任务。
每个设备文件都有一个主 ID 号与一个辅 ID 号:
主 ID 号:标识设备等级,内核使用主 ID 号查找与该类设备相应的驱动程序;
辅 ID 号:在一般等级中唯一标识特定设备。
常规文件和目录通常放在磁盘设备里(其他设备也能存放文件和目录,比如 CD-ROM、flash 内存等)。
磁盘驱动器由一个或多个高速旋转的盘片组成。通过在磁盘上快速移动读/写磁头,便可获取/修改磁盘表面的磁性编码信息。磁盘表面的物理信息存储在称为磁道的同心圆上,磁道自身又被划分为若干扇区,每个扇区包含一系列物理块,物理块是驱动器可以读/写的最小信息单元。
磁盘 I/O 的时间开销通常包含以下三个部分:
寻道时间:磁头首先需要移动到相应磁道;
旋转延迟:等待相应扇区旋转到磁头下;
传输时间:从所请求的块上传输数据。
可将每块磁盘划分为一个或多个(不重叠的)分区。内核将每个分区视为位于/dev
路径下的单独设备。
磁盘分区可容纳任何类型的信息,通常有:
文件系统:用来存放常规文件;
数据区域:作为裸设备提供直接磁盘访问(一些数据库管理系统会用到);
交换区域:供操作系统的虚拟内存管理使用。
文件系统是常规文件和目录的组织集合,通常一个文件系统用于管理一个磁盘分区。用于创建文件系统的命令是mkfs
。
在文件系统中,用来分配空间的基本单位是逻辑块,亦即文件系统所在磁盘设备上若干连续的物理块。
超级块:紧随引导块之后的一个独立块,包含文件系统有关的参数信息,包括:
i-node 表容量;
文件系统中逻辑块的大小;
文件系统的大小(以逻辑块为单位);
i-node 表:文件系统中的每个目录或文件都对应 i-node 表中唯一一条记录,该记录登记了文件的各种信息;
数据块:存放数据,以构成驻留在文件系统之上的文件和目录。
驻留在文件系统上的每个文件,都对应文件系统 i-node 表中的一个 i-node。对 i-node 的标识,采用的是 i-node 表中的顺序位置,以数字标识。
i-node 所维护的信息有:
文件类型(比如常规文件、目录、符号链接以及设备文件等);
文件属主(UID)
文件属组(GID)
三类用户的访问权限:
属主
属组
其他用户
三个时间戳:
对文件的最后访问时间
对文件的最后修改时间
文件状态的最后改变事件
指向文件的硬链接数
以字节为单位的文件大小
实际分配给文件的块数,以 512 字节块为单位(考虑文件空洞的情况,分配给文件的块数可能低于根据文件大小计算出的块数)
指向文件数据块的指针
文件系统在存储文件时,数据块通常采用离散存储的方式,这样做的好处在于能降低磁盘空间的碎片化程度,使得对磁盘空间的利用更为搞宵。为了定位文件数据块,内核在 i-node 内部维护有一组指针。
在 ext2 中,每个 i-node 包含 15 个指针。其中的前 12 个指针指向文件前 12 个块在磁盘上的位置。接下来是一个指向指针块的指针,提供了文件的第 13 个以及后续数据块的位置。第 14、15 个指针则分别是双重间接指针与三重间接指针,以提供更大文件体积的支持。
虚拟文件系统是一种内核特性,通过为文件系统操作创建抽象层,从而为应用程序屏蔽不同实现的文件系统的具体细节:
VFS 为文件系统提供了一套通用接口:
所有与文件交互的程序都按照这些接口进行操作;
每种文件系统都提供 VFS 接口的实现
VFS 的抽象层仿照 UNIX 文件系统模型。对于不支持的 VFS 操作,底层文件系统会将错误代码传回 VFS 层,表明不支持相应操作,而 VFS 随之会将错误代码传递给应用程序。
对于传统的文件系统,在系统崩溃时,对文件的更新可能只完成了一部分,而文件系统元数据也将处于不一致状态,故为了确保文件系统的完整性,重启时必须对文件系统的一致性进行检查。
问题在于,一致性检查需要遍历整个文件系统,对于大型系统而言,花费的开销将难以容忍。
采用日志文件系统,则无需在系统崩溃后对文件进行漫长的一致性检查。在实际更新数据之前,日志文件系统会将这些更新操作以事务的方式记录于专用的磁盘日志文件中。在事务处理过程中,一旦系统崩溃,系统重启时便可用日志重做(redo)任何不完整的更新,将文件系统恢复到一致性状态。从而大大降低系统崩溃后的复原时间。
同时,日志文件系统的缺点在于增加了文件的更新时间。
Linux 上所有文件系统中的文件都位于单根目录树下,树根就是根目录/
。其他文件系统都挂载在根目录下,被视为整个目录层级的子树。
超级用户可用以下命令来挂载文件系统:
mount device directory
这条命令会将名为device
的文件系统挂载到目录层级中由directory
指定的目录,即文件系统的挂载点。可使用unmount
目录卸载文件系统,然后重新挂载到其他挂载点。
不带任何参数执行mount
命令,可列出当前已挂载的文件系统。
应用程序中通过调用
mount()/unmount()
也可进行文件系统的挂载与卸载。