Skip to content

系统级I/O

《深入理解计算机系统》 - Randal E. Bryant - 第三版 第十章,以及《Linux/UNIX 系统编程手册》 - Michael Kerrisk 文件系统相关章节(4,5,13~18)的读书笔记,本文中的所有代码可在GitHub仓库中找到

虚拟文件系统

在Linux系统中基本上把所有内容都看作文件,除了我们普通意义理解的文件之外,目录、字符设备、块设备、FIFO或管道、套接字、符号链接等都被视为是一个“文件”。

vfs

如上图所示,虚拟文件系统(VFS)VFS是一个抽象层,其向上提供了统一的文件访问接口,而向下则兼容了各种不同的文件系统。不仅仅是诸如Ext2、Ext4、XFS和Btrfs等常规意义上的文件系统,还包括伪文件系统和设备等等。另外,VFS实现了一部分公共的功能,例如:页缓存和inode缓存等,从而避免多个文件系统重复实现的问题。

Linux上所有文件系统中的文件都位于单根目录树下,树根就是根目录/。其他的文件系统都挂载在根目录下。mount device directory命令会将名为device的文件系统挂接到目录层级中由directory所指定的目录,即文件系统的挂载点。mount命令可以列出当前已挂载的文件系统,例如:

> mount
proc on /proc type proc (rw)
sysfs on /sys type sysfs (rw)
tmpfs on /run type tmpfs (rw)
udev on /dev type devtmpfs (rw)
/dev/sda6 on / type ext4 (rw)
/dev/sda8 on /home type ext3 (rw,acl,user_xattr)
/dev/sda1 on /windows/C type vfat (rw,noexec,nosuid,nodev)
/dev/sda9 on /home/mtk/test type reiserfs (rw)

mount

除了上图中驻留在磁盘上的文件系统,Linux同样支持驻留于内存中的虚拟文件系统:proc,sysfs,tmpfs,devtmpfs等。/proc/filesystems可以查看当前为内核所知的文件系统类型。

常规文件系统

常规文件系统是对常规文件和目录的组织集合,由mkfs命令创建,其类型有:

  • 传统的ext2文件系统
  • 各种原始UNIX文件系统,如,Minix、System V以及BSD文件系统
  • 微软的FAT、FAT32以及NTFS文件系统
  • ISO 9660 CD-ROM文件系统
  • Apple Macintosh的HFS
  • 一系列网络文件系统,如,SUN的NFS、IBM和微软的SMB、Novell的NCP等
  • 一系列日志文件系统,包括ext3、ext4、Reiserfs、JFS、XFS以及Btrfs等
    • 系统崩溃重启后,会利用日志重做任何不完整的更新,恢复文件系统的一致性状态

磁盘和分区

常规文件和目录通常都存放在磁盘设备里(比如,CD-ROM、flash内存卡以及虚拟磁盘等)。每块磁盘被划分为一个或多个分区。内核将每个分区视为位于/dev路径下的单独设备。磁盘分区一般存放三种信息:

  • 文件系统
    • 用来存放常规文件
  • 数据区域
    • 可做为裸设备对其进行访问,如数据库管理系统就使用了该技术
  • 交换区域
    • 供内核的内存管理使用,如Linux系统中的/proc/swaps可查看已激活的交换区域信息

disk_partition

上图显示了磁盘分区和文件系统之间的关系。文件系统由以下几部分组成:

  • 引导块
    • 位于文件系统首部,只包含用来引导操作系统的信息
  • 超级块
    • 包含与文件系统有关的参数信息,如:
      • i节点表容量
      • 文件系统中逻辑块的大小
      • 以逻辑块计,文件系统的大小
  • i节点表
    • 文件系统中的每个文件或目录在i节点表中都对应着唯一一条记录,这条记录登记了关乎文件的各种信息
  • 数据块
    • 用于存放数据

i节点

每个文件都在i节点表中有自己的i节点(索引节点的简称),i节点由数字标识,可通过ls -li命令查看。i节点维护文件的属性信息,包括:

  • 文件类型
    • 可通过ls -l最首部的字母获得
  • 访问权限
  • 指向文件的硬链接数
    • 可通过ls -l第二列的数字获得
  • 文件属主/组
  • 文件的大小
  • 时间戳
  • 实际分配给文件的块数量
    • 可通过ls -s命令获得
  • 指向文件数据块的指针

ext2_fs

上图显示了ext2文件系统的i节点结构。每个i节点包含15个指针。其中,前12个指针指向文件前12个块在数据块中的位置。接下来,是一个指向指针块的指针,提供了文件的第13个以及后续数据块的位置。指针块中指针的数量取决于文件系统中块的大小,可能在256(块容量为1024字节) ~ 1024(块容量为4096字节)之间。

双重间接指针以及三重间接指针,满足了更大文件的需求。例如,对于大小为4096字节的块而言,文件大小可略高于1024*1024*1024*4096字节(4TB)。这是由三重间接指针所指向的范围决定的,直接、间接或双重间接指针的范围相对来说,就微不足道了。

共享文件

Linux内核用三个相关的数据结构来表示打开的文件:

  • 进程级的文件描述符表(open file descriptor)
    • 该表的每一条目都记录了对打开文件句柄的引用
  • 系统级的打开文件表(open file table)
    • 该表的每个条目称为打开文件句柄,存储了一个与打开文件相关的全部信息,包括:
      • 文件偏移量
      • 打开文件时所使用的状态标志
      • 对该文件i-node对象的引用
  • 文件系统的i-node表
    • 每个文件的i-node信息包括:
      • 文件类型和访问权限
      • 文件的其他属性,包括文件大小、时间戳等信息

下图显示了文件描述符、打开的文件句柄以及i-node之间的关系。

fd_inode

  • 一个进程中多个文件描述符指向同一个打开的文件句柄
    • 如图中,进程A的fd1fd20
    • 可能是通过调用dup()dup2()fcntl(...,F_DUPFD,...)形成的,参考例子fd_share
  • 多个进程中值相同的文件描述符指向同一个打开的文件句柄
    • 如图中,进程A的fd2和进程B的fd2
    • 可能是在调用fork()后形成的,参考例子fd_fork
  • 不同的打开文件句柄指向i-node表中的相同条目
    • 如图中,打开文件表的i-node指针1976
    • 可能是对同一个文件发起open()调用后形成的,参考例子fd_open

由上图可知,对于指向同一打开文件句柄的多个文件描述符,它们共享同一文件偏移量,打开的文件标志等属性。如果其中一个文件描述符修改了上述文件属性,会影响其他文件描述符,除了文件描述符标志(close-on-exec标志),这一标志是文件描述符所私有的。