这篇文章上次修改于 765 天前,可能其部分内容已经发生变化,如有疑问可询问作者。

1 索引节点和目录项

索引节点,简称 inode,记录文件的元数据,如 inode 编号,文件大小,修改日期,访问权限,数据位置等。索引节点会占用磁盘空间。索引节点是文件的唯一标志。

目录项,简称 dentry,记录文件的目录,索引节点指针和与其它目录项的关联关系。是一个内存缓存,不占磁盘空间。

磁盘在执行文件系统格式化时,会被分成三个存储区:

  • 超级块:存储整个文件系统的状态。
  • 索引节点区:存储索引节点。
  • 数据块区:存储文件数据。

2 虚拟文件系统

Linux 文件系统四大要素:目录项,索引节点,逻辑块,超级块。

逻辑块:文件系统将连续的扇区组成了逻辑块,然后以逻辑块为最小单元管理数据。常见的逻辑块大小为 4KB,即由连续的 8 个扇区组成。

VFS 定义了一组所有文件系统都支持的数据结构和标准接口。

按照存储位置的不同,文件系统可分为三类:

  • 基于磁盘的文件系统,是直接挂载在磁盘中,如 Ext4 等。
  • 基于内存的文件系统,即虚拟文件系统,如 /proc,/sys 等。
  • 网络文件系统,如 NFS 等。

这些文件系统需要先挂载在 VFS 目录树中的某个子目录(挂载点),然后才能访问其中的文件。

3 文件系统 I/O

根据是否利用标准库缓存,文件 I/O 分为:

  • 缓冲 I/O:利用标准库缓存加速文件访问,标准库内部再调用系统调用。比如,很多很多程序换行时才会真正输出。
  • 非缓冲 I/O:直接调用系统调用。

以上两种方式都会用到系统调用,系统调用后,还会通过页缓存减少磁盘 I/O。根据是否使用页缓存,文件 I/O 分为:

  • 直接 I/O:跳过页缓存,直接跟文件系统交互来访问文件。在系统调用时,需指定 O_DIRECT 标志。
  • 非直接 I/O:文件读写时,先经过页缓存,然后由额外的系统调用写磁盘。

以上两种方式都会跟文件系统交互,也可以绕过文件系统,直接读写磁盘,称为“裸 I/O“。

根据程序是否阻塞自身运行,分为:

  • 阻塞 I/O:程序执行 I/O 操作后,如果没有获得响应,会阻塞当前线程,从而无法执行其它任务。
  • 非阻塞 I/O:程序执行 I/O 操作后,不用等待完成后的响应,可继续执行任务,随后通过轮询或事件通知的形式获取调用结果。比如访问网络套接字时,需指定 O_NONBLOCK 标志。

根据是否等待响应结果,分为:

  • 同步 I/O:程序执行 I/O 后,需要等整个 I/O 操作执行完后,才能获得响应。操作文件时,如果设置 O_SYNC,等文件写入磁盘后,才能返回;如果设置了 O_DSYNC,还需要等文件元数据也写入磁盘后才能返回。
  • 异步 I/O:程序执行 I/O 后,不需要等待响应,继续执行任务。等本次 I/O 结束后,响应会用通知的方式告知应用程序。访问网络时,设置了 O_ASYNC 后,内核会通过 SINGO 或者 SIGPOLL 通知进程文件是否可读写。

4 性能观测

4.1 容量

查看文件系统容量:

$ df -h /dev/sda1 
Filesystem      Size  Used Avail Use% Mounted on 
/dev/sda1        29G  3.1G   26G  11% / 

文件系统使用了 11% 的空间。

查看索引节点使用情况:

$ df -i /dev/sda1 
Filesystem      Inodes  IUsed   IFree IUse% Mounted on 
/dev/sda1      3870720 157460 3713260    5% / 

当发现索引节点空间不足,但磁盘空间比较足时,可能是过多的小文件导致的。

4.2 缓存

可通过 free 或 vmstat 观察页缓存的大小。free 输出的 Cache 是页缓存和可回收 Slab 之和。

$ cat /proc/meminfo | grep -E "SReclaimable|Cached" 
Cached:           748316 kB 
SwapCached:            0 kB 
SReclaimable:     179508 kB 

内核使用 Slab 机制管理目录项和索引节点缓存。查看目录项和索引节点缓存:

$ cat /proc/slabinfo | grep -E '^#|dentry|inode' 
# name            <active_objs> <num_objs> <objsize> <objperslab> <pagesperslab> : tunables <limit> <batchcount> <sharedfactor> : slabdata <active_slabs> <num_slabs> <sharedavail> 
xfs_inode              0      0    960   17    4 : tunables    0    0    0 : slabdata      0      0      0 
... 
ext4_inode_cache   32104  34590   1088   15    4 : tunables    0    0    0 : slabdata   2306   2306      0hugetlbfs_inode_cache     13     13    624   13    2 : tunables    0    0    0 : slabdata      1      1      0 
sock_inode_cache    1190   1242    704   23    4 : tunables    0    0    0 : slabdata     54     54      0 
shmem_inode_cache   1622   2139    712   23    4 : tunables    0    0    0 : slabdata     93     93      0 
proc_inode_cache    3560   4080    680   12    2 : tunables    0    0    0 : slabdata    340    340      0 
inode_cache        25172  25818    608   13    2 : tunables    0    0    0 : slabdata   1986   1986      0 
dentry             76050 121296    192   21    1 : tunables    0    0    0 : slabdata   5776   5776      0 
  • dentry:目录项缓存。
  • inode_cache:VFS 索引节点缓存。
  • 其余是各种文件系统索引节点缓存。

查看占用内存最多的缓存类型:

# 按下c按照缓存大小排序,按下a按照活跃对象数排序 
$ slabtop 
Active / Total Objects (% used)    : 277970 / 358914 (77.4%) 
Active / Total Slabs (% used)      : 12414 / 12414 (100.0%) 
Active / Total Caches (% used)     : 83 / 135 (61.5%) 
Active / Total Size (% used)       : 57816.88K / 73307.70K (78.9%) 
Minimum / Average / Maximum Object : 0.01K / 0.20K / 22.88K 

  OBJS ACTIVE  USE OBJ SIZE  SLABS OBJ/SLAB CACHE SIZE NAME 
69804  23094   0%    0.19K   3324       21     13296K dentry 
16380  15854   0%    0.59K   1260       13     10080K inode_cache 
58260  55397   0%    0.13K   1942       30      7768K kernfs_node_cache 
   485    413   0%    5.69K     97        5      3104K task_struct 
  1472   1397   0%    2.00K     92       16      2944K kmalloc-2048 

可见,目录项和索引节点占用了最多的 Slab 缓存,但加起来只有 23MB 左右。

参考

倪朋飞. Linux 性能优化实战.