【TLPI读书笔记】 十四、文件系统
设备专用文件
设备专用文件与系统的某个设备相对应。在内核中,每种设备类型都有与之相对应的设备驱动程序,用来处理设备的所有I/O请求。如前面所讲,每个驱动程序所提供的接口一致,即通用I/O模型。
某些设备是实际存在的,比如鼠标、磁盘。而另一些设备则是虚拟的,亦即并不存在相应硬件,但内核会(通过设备驱动程序)提供一种抽象设备,其所携带API与真实设备无异。
可将设备划分为以下两种类型:
- 字符型设备基于每个字符来处理数据。终端和键盘都属于字符型设备
- 块设备则每次处理一块数据。块的大小取决于设备类型,但通常为512字节的倍数。磁盘等设备属于块设备
设备ID
每个设备文件都有主、辅ID号各一。主ID号标识一般的设备等级,内核会使用主ID号查找与该类设备相应的驱动程序。辅ID号能够在一般等级中唯一标识特定设备。
磁盘和分区
磁盘驱动器
硬盘驱动器是一种机械装置,由一个或多个高速旋转的盘片组成。通过在磁盘上快速移动的读/写磁头,便可获取/修改磁盘表面的磁性编码信息。磁盘表面信息物理上存储于称为磁道的一组同心圆上。磁道自身又被划分为若干扇区,每个扇区则包含一系列物理块。物理块的容量一般为512字节(或512的倍数),代表了驱动器可读/写的最小信息单元。
磁盘分区
可将每块磁盘划分为一个或多个(不重叠)分区。内核则将每个分区视为位于/dev
路径下的单独设备。
磁盘分区可容纳任何类型的信息,但通常只会包含以下之一:
- 文件系统
- 数据区域
- 交换区域
文件系统
文件系统是对常规文件和目录的组织集合。
ext2 文件系统
ext2(扩展文件系统二世)是Linux上使用最为广泛的文件系统,也是原始Linux文件系统————ext的继任者。ext2的源码篇幅不大,是其他几种文件系统实现的原型。近年来随着各种日志文件系统的兴起,该文件系统的使用也日趋减少。本文采用ext2介绍通用文件系统。
文件系统结构
在文件系统中,用来分配空间的基本单位是逻辑块,亦即文件系统所在磁盘设备上若干连续的物理块。
文件系统由以下几部分组成:
- 引导块
- 超级块
- i节点表
- 数据块
i节点
针对驻留于文件系统上的每个文件,文件系统的i节点表会包含一个i节点(索引节点的简称)。对于i节点的标识,采用的是i节点表中的顺序位置,以数字表示。
i节点所维护的信息如下所示:
- 文件类型
- 文件属主
- 文件属组
- 3类用户的访问权限:属主、属组以及其他用户
- 3个时间戳:对文件的最后访问时间、对文件的最后修改时间,以及文件状态的最后改变时间(大多Linux不记录文件创建时间)
- 指向文件的硬链接数量
- 文件的大小,以字节为单位
- 实际分配给文件的块数量,以512字节块为单位。
ext2中的i节点和数据块指针
类似于大多数UNIX文件系统,ext2文件系统在存储文件时,数据块不一定连续,甚至不一定按顺序存放。为了定位文件数据块,内核在i节点内维护有一组指针。
在ext2中,每个i节点包含15个指针。其中的前12个指针指向文件前12个块在文件系统中的位置。接下来,是一个指向指针块的指针,提供了文件的第13个以及后续数据块的位置。指针块中指针的数量取决于文件系统中块的大小。对于巨型文件,指针会产生更深一层递进,如双重间接指针、三重间接指针。
虚拟文件系统(VFS
)
Linux所支持的各种文件系统,其实现细节均不同。如果每个与文件系统打交道的程序都需要理解各种文件系统的具体细节,那么编写与各类文件系统交互的程序将近乎于不可能完成的任务。
虚拟文件系统(VFS)是一种内核特性,通过为文件系统操作创建抽象层来解决上述问题。
- VFS针对文件系统定义了一套通用接口。所有与文件交互的程序都会按照这一接口来进行操作
- 每种文件系统都会提供VFS接口的实现
VFS接口的操作与涉及文件系统和目录的所有常规系统调用相对应,这些系统调用有如前面学的通用I/O模型,此外还有其他文件操作的相关系统调用。
日志文件系统
ext2文件系统是传统UNIX文件系统的优秀典范,自然也受制于其短板:系统崩溃后,为确保文件系统的完整性,重启必须对文案系统进行一致性检查。问题在于一致性检查需要遍历整个文件系统,在大型文件系统上该操作会历时数小时。
采用日志文件系统,则无需在系统崩溃后对文件进行漫长的一致性检查。在实际更新元数据前,日志文件系统会将更新操作记录于专用的磁盘日志文件中。一旦系统崩溃重启时便可利用日志重做任何不完整的更新,同时为文件系统恢复一致性状态。此处重点是维护的是元数据(i节点)
单根目录层级和挂载点
与其他UNIX系统一样,Linux上所有文件系统中的文件都位于单根目录树下,树根就是根目录“/
”。其他文件系统都挂载在根目录之下,被视为整个目录层级的子树。
文件系统的挂载和卸载
系统调用mount()
和umount()
运行特权级进程以挂载或卸载文件系统。
以下3个文件,包含了当前已挂账或可挂载的文件系统信息:
- 通过Linux专有的虚拟文件
/proc/mounts
,可查看当前已挂账文件系统列表。数据准确 mount(8)
和umount(8)
命令会自动维护/etc/mtab
文件,该文件所包含的信息与/proc/mounts
的内容相类似,只是略微详细一些。数据可能不准确/etc/fstab
(由系统管理员手动维护)包含了对系统支持的所有文件系统的描述。
挂载文件系统:mount()
mount()
系统调用将由source指定设备所包含的文件系统,挂载到由target指定的目录下
#include <sys/mount.h>
int mount(const char *source, const char *target, const char *fstype, unsigned long mountflags, const void *data);
Return 0 on success, or -1 on error
参数
source
:指定设备target
:指定挂载至目录fstype
:指定文件系统类型mountflags
:掩码,由0个或多个标志进行或(OR)操作而得出data
:是一个指向信息缓冲区的指针,对其信息的解释取决于文件系统
返回值
- 成功:返回
0
- 失败:返回
-1
- 成功:返回
卸载文件系统:umount()
umount()
系统调用用于卸载已挂载的文件系统
#include <sys/mount.h>
int umount(const char *target);
Return 0 on success, or -1 on error
参数
target
:指定待卸载文件系统的挂载点
返回值
- 成功:返回
0
- 失败:返回
-1
- 成功:返回
umount2()
系统调用是umount()
的扩展版。通过flags参数,umount2()
可对卸载操作施以更精密的控制
#include <sys/mount.h>
int umount2(const char *target, int flags);
Return 0 on success, or -1 on error
参数
target
:指定待卸载文件系统的挂载点flags
:掩码,由0个或多个标志进行或(OR)操作而得出
返回值
- 成功:返回
0
- 失败:返回
-1
- 成功:返回
高级挂载特性
在多个挂载点挂载文件系统
内核版本2.4之前,一个文件系统只能挂载于单个挂载点。2.4开始,可以将一个文件系统挂载于文件系统内的多个位置。由于每个挂载点下的目录子树内容都相同,在一个挂载点下对目录子树所做的改变,同样可见诸于其他挂载点。
多次挂载同一挂载点
内核版本2.4之前,一个挂载点只能使用一次。2.4开始,Linux允许针对同一挂载点执行多次挂载。,每次挂载都会隐藏之前可见于挂载点下的目录子树。卸载最后一次挂载时,挂载点下上次挂载的内容会再次显现。
在现有且在用的挂载点上执行新的挂载操作是此类堆叠挂载的用法之一。
基于每次挂载的挂载标志
内核版本2.4之前,文件系统和挂载点之间是一一对应的关系。2.4开始,这一特性不再适用。故而前面所述的某些mountflag标志值可以基于每次挂载来设置。
绑定挂载
始于内核版本2.4,Linux支持了创建绑定挂载。绑定挂载是指在文件系统目录层级的另一处挂载目录或文件。这将导致文件或目录在两处同时可见。
绑定挂载有点类似硬链接,但存在两方面差异:
- 绑定挂载可以跨越多个文件系统挂载点,甚至不拘于chroot监禁区(jail)
- 可针对目录执行绑定挂载
递归绑定挂载
默认情况下,如果使用MS_BIND
为某个目录创建了绑定挂载,那么只会将该目录挂载到新位置。假设源目录下还存在子挂载,则不会讲这些子挂载复制到挂载target之下。
版本2.4.11添加了MS_REC
标志,其会将子挂载复制到挂载目录下,此之谓递归绑定挂载。
虚拟内存文件系统:tmpfs
到目前为止,已论及的所有文件系统均驻留于磁盘之上。然而,Linux同样支持驻留于内存中的虚拟文件系统。对应用程序来说,此类文件系统与任何其他文件系统别无二致。不过二者还是存在一个重要差别:由于不涉及磁盘访问,虚拟文件系统的文件操作速度极快。
Linux有很多基于内存的文件系统,最复杂的tmpfs
文件系统与内核版本2.4中首度出现。较之于其他内存的文件系统,其独特之处在于它属于虚拟文件系统。这意味着该文件系统不仅使用RAM,而且在RAM耗尽的情况下,还会利用交换空间。tmpfs
由于数据驻留于内存,所以断点后数据即丢失,其名称正是得名于此。
获得与文件系统有关的信息:statvfs()
statvfs()
和fstatvfs()
库函数能够获得与已挂载文件系统有关的信息。
#include <sys/statvfs.h>
int statvfs(const char *pathname, struct statvfs *statvfsbuf);
int fstatvfs(int fd, struct statvfs *statvfsbuf);
Both return 0 on success, or -1 on error
两者唯一区别就是其标识文件系统的方式,一个为路径一个为文件描述符
参数
pathname
:文件系统挂载点路径fd
:文件系统挂载点文件描述符statvfsbuf
:存储关乎文件系统信息的缓冲区,为statvfs
结构:struct statvfs { unsigned long f_bsize; /* Filesystem block size */ unsigned long f_frsize; /* Fragment size */ fsblkcnt_t f_blocks; /* Size of fs in f_frsize units */ fsblkcnt_t f_bfree; /* Number of free blocks */ fsblkcnt_t f_bavail; /* Number of free blocks for unprivileged users */ fsfilcnt_t f_files; /* Number of inodes */ fsfilcnt_t f_ffree; /* Number of free inodes */ fsfilcnt_t f_favail; /* Number of free inodes for unprivileged users */ unsigned long f_fsid; /* Filesystem ID */ unsigned long f_flag; /* Mount flags */ unsigned long f_namemax; /* Maximum filename length */ };
返回值
- 成功:返回
0
- 失败:返回
-1
- 成功:返回
总结
本章首先介绍了文件系统相关知识,然后介绍了挂载与卸载操作的系统调用,最后提出了虚拟文件系统的概念和获取文件系统信息的库函数。