23 | 基础篇:Linux 文件系统是怎么工作的?
下载APP
关闭
渠道合作
推荐作者
23 | 基础篇:Linux 文件系统是怎么工作的?
2019-01-11 倪朋飞 来自北京
《Linux性能优化实战》
课程介绍
讲述:冯永吉
时长14:20大小13.14M
你好,我是倪朋飞。
通过前面 CPU 和内存模块的学习,我相信,你已经掌握了 CPU 和内存的性能分析以及优化思路。从这一节开始,我们将进入下一个重要模块——文件系统和磁盘的 I/O 性能。
同 CPU、内存一样,磁盘和文件系统的管理,也是操作系统最核心的功能。
磁盘为系统提供了最基本的持久化存储。
文件系统则在磁盘的基础上,提供了一个用来管理文件的树状结构。
那么,磁盘和文件系统是怎么工作的呢?又有哪些指标可以衡量它们的性能呢?
今天,我就带你先来看看,Linux 文件系统的工作原理。磁盘的工作原理,我们下一节再来学习。
索引节点和目录项
文件系统,本身是对存储设备上的文件,进行组织管理的机制。组织方式不同,就会形成不同的文件系统。
你要记住最重要的一点,在 Linux 中一切皆文件。不仅普通的文件和目录,就连块设备、套接字、管道等,也都要通过统一的文件系统来管理。
为了方便管理,Linux 文件系统为每个文件都分配两个数据结构,索引节点(index node)和目录项(directory entry)。它们主要用来记录文件的元信息和目录结构。
索引节点,简称为 inode,用来记录文件的元数据,比如 inode 编号、文件大小、访问权限、修改日期、数据的位置等。索引节点和文件一一对应,它跟文件内容一样,都会被持久化存储到磁盘中。所以记住,索引节点同样占用磁盘空间。
目录项,简称为 dentry,用来记录文件的名字、索引节点指针以及与其他目录项的关联关系。多个关联的目录项,就构成了文件系统的目录结构。不过,不同于索引节点,目录项是由内核维护的一个内存数据结构,所以通常也被叫做目录项缓存。
换句话说,索引节点是每个文件的唯一标志,而目录项维护的正是文件系统的树状结构。目录项和索引节点的关系是多对一,你可以简单理解为,一个文件可以有多个别名。
举个例子,通过硬链接为文件创建的别名,就会对应不同的目录项,不过这些目录项本质上还是链接同一个文件,所以,它们的索引节点相同。
索引节点和目录项纪录了文件的元数据,以及文件间的目录关系,那么具体来说,文件数据到底是怎么存储的呢?是不是直接写到磁盘中就好了呢?
实际上,磁盘读写的最小单位是扇区,然而扇区只有 512B 大小,如果每次都读写这么小的单位,效率一定很低。所以,文件系统又把连续的扇区组成了逻辑块,然后每次都以逻辑块为最小单元,来管理数据。常见的逻辑块大小为 4KB,也就是由连续的 8 个扇区组成。
为了帮助你理解目录项、索引节点以及文件数据的关系,我画了一张示意图。你可以对照着这张图,来回忆刚刚讲过的内容,把知识和细节串联起来。
不过,这里有两点需要你注意。
第一,目录项本身就是一个内存缓存,而索引节点则是存储在磁盘中的数据。在前面的 Buffer 和 Cache 原理中,我曾经提到过,为了协调慢速磁盘与快速 CPU 的性能差异,文件内容会缓存到页缓存 Cache 中。
那么,你应该想到,这些索引节点自然也会缓存到内存中,加速文件的访问。
第二,磁盘在执行文件系统格式化时,会被分成三个存储区域,超级块、索引节点区和数据块区。其中,
超级块,存储整个文件系统的状态。
索引节点区,用来存储索引节点。
数据块区,则用来存储文件数据。
虚拟文件系统
目录项、索引节点、逻辑块以及超级块,构成了 Linux 文件系统的四大基本要素。不过,为了支持各种不同的文件系统,Linux 内核在用户进程和文件系统的中间,又引入了一个抽象层,也就是虚拟文件系统 VFS(Virtual File System)。
VFS 定义了一组所有文件系统都支持的数据结构和标准接口。这样,用户进程和内核中的其他子系统,只需要跟 VFS 提供的统一接口进行交互就可以了,而不需要再关心底层各种文件系统的实现细节。
这里,我画了一张 Linux 文件系统的架构图,帮你更好地理解系统调用、VFS、缓存、文件系统以及块存储之间的关系。
通过这张图,你可以看到,在 VFS 的下方,Linux 支持各种各样的文件系统,如 Ext4、XFS、NFS 等等。按照存储位置的不同,这些文件系统可以分为三类。
第一类是基于磁盘的文件系统,也就是把数据直接存储在计算机本地挂载的磁盘中。常见的 Ext4、XFS、OverlayFS 等,都是这类文件系统。
第二类是基于内存的文件系统,也就是我们常说的虚拟文件系统。这类文件系统,不需要任何磁盘分配存储空间,但会占用内存。我们经常用到的 /proc 文件系统,其实就是一种最常见的虚拟文件系统。此外,/sys 文件系统也属于这一类,主要向用户空间导出层次化的内核对象。
第三类是网络文件系统,也就是用来访问其他计算机数据的文件系统,比如 NFS、SMB、iSCSI 等。
这些文件系统,要先挂载到 VFS 目录树中的某个子目录(称为挂载点),然后才能访问其中的文件。拿第一类,也就是基于磁盘的文件系统为例,在安装系统时,要先挂载一个根目录(/),在根目录下再把其他文件系统(比如其他的磁盘分区、/proc 文件系统、/sys 文件系统、NFS 等)挂载进来。
文件系统 I/O
把文件系统挂载到挂载点后,你就能通过挂载点,再去访问它管理的文件了。VFS 提供了一组标准的文件访问接口。这些接口以系统调用的方式,提供给应用程序使用。
就拿 cat 命令来说,它首先调用 open() ,打开一个文件;然后调用 read() ,读取文件的内容;最后再调用 write() ,把文件内容输出到控制台的标准输出中:
文件读写方式的各种差异,导致 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,需要你在系统调用中,指定 O_DIRECT 标志。如果没有设置过,默认的是非直接 I/O。
不过要注意,直接 I/O、非直接 I/O,本质上还是和文件系统交互。如果是在数据库等场景中,你还会看到,跳过文件系统读写磁盘的情况,也就是我们通常所说的裸 I/O。
第三,根据应用程序是否阻塞自身运行,可以把文件 I/O 分为阻塞 I/O 和非阻塞 I/O:
所谓阻塞 I/O,是指应用程序执行 I/O 操作后,如果没有获得响应,就会阻塞当前线程,自然就不能执行其他任务。
所谓非阻塞 I/O,是指应用程序执行 I/O 操作后,不会阻塞当前的线程,可以继续执行其他的任务,随后再通过轮询或者事件通知的形式,获取调用的结果。
比方说,访问管道或者网络套接字时,设置 O_NONBLOCK 标志,就表示用非阻塞方式访问;而如果不做任何设置,默认的就是阻塞访问。
第四,根据是否等待响应结果,可以把文件 I/O 分为同步和异步 I/O:
所谓同步 I/O,是指应用程序执行 I/O 操作后,要一直等到整个 I/O 完成后,才能获得 I/O 响应。
所谓异步 I/O,是指应用程序执行 I/O 操作后,不用等待完成和完成后的响应,而是继续执行就可以。等到这次 I/O 完成后,响应会用事件通知的方式,告诉应用程序。
举个例子,在操作文件时,如果你设置了 O_SYNC 或者 O_DSYNC 标志,就代表同步 I/O。如果设置了 O_DSYNC,就要等文件数据写入磁盘后,才能返回;而 O_SYNC,则是在 O_DSYNC 基础上,要求文件元数据也要写入磁盘后,才能返回。
再比如,在访问管道或者网络套接字时,设置了 O_ASYNC 选项后,相应的 I/O 就是异步 I/O。这样,内核会再通过 SIGIO 或者 SIGPOLL,来通知进程文件是否可读写。
你可能发现了,这里的好多概念也经常出现在网络编程中。比如非阻塞 I/O,通常会跟 select/poll 配合,用在网络套接字的 I/O 中。
你也应该可以理解,“Linux 一切皆文件”的深刻含义。无论是普通文件和块设备、还是网络套接字和管道等,它们都通过统一的 VFS 接口来访问。
性能观测
学了这么多文件系统的原理,你估计也是迫不及待想上手,观察一下文件系统的性能情况了。
接下来,打开一个终端,SSH 登录到服务器上,然后跟我一起来探索,如何观测文件系统的性能。
容量
对文件系统来说,最常见的一个问题就是空间不足。当然,你可能本身就知道,用 df 命令,就能查看文件系统的磁盘空间使用情况。比如:
你可以看到,我的根文件系统只使用了 11% 的空间。这里还要注意,总空间用 1K-blocks 的数量来表示,你可以给 df 加上 -h 选项,以获得更好的可读性:
不过有时候,明明你碰到了空间不足的问题,可是用 df 查看磁盘空间后,却发现剩余空间还有很多。这是怎么回事呢?
不知道你还记不记得,刚才我强调的一个细节。除了文件数据,索引节点也占用磁盘空间。你可以给 df 命令加上 -i 参数,查看索引节点的使用情况,如下所示:
索引节点的容量,(也就是 Inode 个数)是在格式化磁盘时设定好的,一般由格式化工具自动生成。当你发现索引节点空间不足,但磁盘空间充足时,很可能就是过多小文件导致的。
所以,一般来说,删除这些小文件,或者把它们移动到索引节点充足的其他磁盘中,就可以解决这个问题。
缓存
在前面 Cache 案例中,我已经介绍过,可以用 free 或 vmstat,来观察页缓存的大小。复习一下,free 输出的 Cache,是页缓存和可回收 Slab 缓存的和,你可以从 /proc/meminfo ,直接得到它们的大小:
话说回来,文件系统中的目录项和索引节点缓存,又该如何观察呢?
实际上,内核使用 Slab 机制,管理目录项和索引节点的缓存。/proc/meminfo 只给出了 Slab 的整体大小,具体到每一种 Slab 缓存,还要查看 /proc/slabinfo 这个文件。
比如,运行下面的命令,你就可以得到,所有目录项和各种文件系统索引节点的缓存情况:
这个界面中,dentry 行表示目录项缓存,inode_cache 行,表示 VFS 索引节点缓存,其余的则是各种文件系统的索引节点缓存。
/proc/slabinfo 的列比较多,具体含义你可以查询 man slabinfo。在实际性能分析中,我们更常使用 slabtop ,来找到占用内存最多的缓存类型。
比如,下面就是我运行 slabtop 得到的结果:
从这个结果你可以看到,在我的系统中,目录项和索引节点占用了最多的 Slab 缓存。不过它们占用的内存其实并不大,加起来也只有 23MB 左右。
小结
今天,我带你梳理了 Linux 文件系统的工作原理。
文件系统,是对存储设备上的文件,进行组织管理的一种机制。为了支持各类不同的文件系统,Linux 在各种文件系统实现上,抽象了一层虚拟文件系统(VFS)。
VFS 定义了一组所有文件系统都支持的数据结构和标准接口。这样,用户进程和内核中的其他子系统,就只需要跟 VFS 提供的统一接口进行交互。
为了降低慢速磁盘对性能的影响,文件系统又通过页缓存、目录项缓存以及索引节点缓存,缓和磁盘延迟对应用程序的影响。
在性能观测方面,今天主要讲了容量和缓存的指标。下一节,我们将会学习 Linux 磁盘 I/O 的工作原理,并掌握磁盘 I/O 的性能观测方法。
思考
最后,给你留一个思考题。在实际工作中,我们经常会根据文件名字,查找它所在路径,比如:
今天的问题就是,这个命令,会不会导致系统的缓存升高呢?如果有影响,又会导致哪种类型的缓存升高呢?你可以结合今天内容,自己先去操作和分析,看看观察到的结果跟你分析的是否一样。
欢迎在留言区和我讨论,也欢迎把这篇文章分享给你的同事、朋友。我们一起在实战中演练,在交流中进步。
分享给需要的人,Ta购买本课程,你将得20元
生成海报并分享
赞 24
提建议
© 版权归极客邦科技所有,未经许可不得传播售卖。 页面已增加防盗追踪,如有侵权极客邦将依法追究其法律责任。
上一篇
22 | 答疑(三):文件系统与磁盘的区别是什么?
下一篇
24 | 基础篇:Linux 磁盘I/O是怎么工作的(上)
精选留言(75)
- coyang2019-01-11课后题: 这个命令,会不会导致系统的缓存升高呢? --> 会的 如果有影响,又会导致哪种类型的缓存升高呢? --> /xfs_inode/ proc_inode_cache/dentry/inode_cache 实验步骤: 1. 清空缓存:echo 3 > /proc/sys/vm/drop_caches ; sync 2. 执行find : find / -name test 3. 发现更新top 4 项是: OBJS ACTIVE USE OBJ SIZE SLABS OBJ/SLAB CACHE SIZE NAME 37400 37400 100% 0.94K 2200 17 35200K xfs_inode 36588 36113 98% 0.64K 3049 12 24392K proc_inode_cache 104979 104979 100% 0.19K 4999 21 19996K dentry 18057 18057 100% 0.58K 1389 13 11112K inode_cache find / -name 这个命令是全盘扫描(既包括内存文件系统又包含本地的xfs【我的环境没有mount 网络文件系统】),所以 inode cache & dentry & proc inode cache 会升高。 另外,执行过了一次后再次执行find 就机会没有变化了,执行速度也快了很多,也就是下次的find大部分是依赖cache的结果。展开
作者回复: 赞
154 - 白华2019-01-11课后题:我找了一个目录下的文件,用的这个命令find / -type f -name copyright 然后slabtop观察,发现dentry的SLABS和SIZE有了明显的提高,所以引起了目录项缓存的升高。在开始的时候dentry有一定的大小,我认为是缓存了/目录下系统基本的目录,但是系统后面下载、创建的内容是没有缓存的,使用查找命令会把这些都查找到然后缓存起来,所以使用find查找大量内容时候会造成性能下降。 前面看老男孩视频时候了解了inode和block。inode存储这些数据属性信息的,包含不限于文件大小、文件类型、文件权限、拥有者、硬链接数、所属组、修改时间,还包含指向文件实体的指针功能(inode节点---block的对应关系),但是inode惟独不包含文件名。文件名不在inode里,在上级目录的block里;Block来存储实际数据用的,例如照片、视频等普通文件数据。 今天看到了dentry,定义是用来记录文件的名字、索引节点指针以及与其他目录项的关联关系。但是和老男孩老师讲的有所区别,希望老师帮我解惑展开共 3 条评论21
- 石维康2019-01-11阻塞 I/O 和非阻塞 I/O的概念和同步和异步 I/O的区别是什么?
作者回复: 这个在答疑里统一回复吧
共 4 条评论16 - 小成2019-02-01请问老师,除了目录项以外还有哪些地方保存有文件名,下一节讲到目录项是一个内存缓存,那么不会保存文件名到磁盘上面?
作者回复: 目录项是一个缓存,不是持久化存储。目录也是一个文件,这个特殊文件保存了该目录的所有文件名与inode的对应关系
15 - 伟忠2019-01-12机器上 df 查看占用了 200G,但 du 查看发现只有 90G,看网上的办法用 lsof | grep delete 查看,但没有找到,请问老师,这个可能是什么原因呢?
作者回复: 可能是文件本身已经删除了,但其描述符还被进程占用着,可以查找无效的文件描述符看看
共 7 条评论14 - 肘子哥2019-02-23有个疑惑,如果目录项存在内存中是不是意味着内存故障后,目录就无法访问了呢?
作者回复: 不会的,还可以从磁盘的持久化数据中重建
共 2 条评论12 - ThinkerWalker2019-04-16关于白华提到的: "前面看老男孩视频时候了解了inode和block。inode存储这些数据属性信息的,包含不限于文件大小、文件类型、文件权限、拥有者、硬链接数、所属组、修改时间,还包含指向文件实体的指针功能(inode节点---block的对应关系),但是inode惟独不包含文件名。文件名不在inode里,在上级目录的block里;Block来存储实际数据用的,例如照片、视频等普通文件数据。" 我想说,目录项本质上是缓存,缓存是为了加速文件查找和访问的,所以说和老师这里所说的dentry不冲突,没有目录项的时候查找一个文件需要从/一级一级查找. 是这样的吧?倪老师?展开9
- Net Scotte2020-08-31这里的slab不是很容易理解,我查了一下,slab是Linux的内存管理机制,用于解决小对象(具有相同数据结构和大小的内存单元)的内存管理问题,slabinfo中的每一项都是一种cache(即前文说的目录项缓存、索引节点缓存等),一个cache包括多个slab,slab又包含多个objects,已经分配的称为active object8
- 董文荣2019-01-21课后题: Q:$ find / -name file-name 这个命令,会不会导致系统的缓存升高呢?如果有影响,又会导致哪种类型的缓存升高呢? A:分析 1)、"/"代表文件系统的根目录,目录项已经缓存在cached。(通过下面的测试,怀疑应该只是部分目录项的内容缓存在cache中,待验证) 2)、因为会匹配值“file-name“,会将索引节点读入缓存进行匹配。 因此会导致cached增长。以下是三组测试对比,给出了执行find命令前后,cached变化的对比。 命令之前前后,slabtop的执行前后对比: Active / Total Objects (% used) : 184412 / 240169 (76.8%) Active / Total Size (% used) : 42926.19K / 59199.82K (72.5%) OBJS ACTIVE USE OBJ SIZE SLABS OBJ/SLAB CACHE SIZE NAME 11088 2313 20% 0.57K 198 56 6336K radix_tree_node 10450 9515 91% 0.58K 190 55 6080K inode_cache 27510 12695 46% 0.19K 655 42 5240K dentry 4710 1003 21% 1.06K 157 30 5024K xfs_inode Active / Total Objects (% used) : 1795399 / 1809652 (99.2%) Active / Total Size (% used) : 1004316.02K / 1007573.47K (99.7%) OBJS ACTIVE USE OBJ SIZE SLABS OBJ/SLAB CACHE SIZE NAME 708420 708420 100% 1.06K 23614 30 755648K xfs_inode 787878 787878 100% 0.19K 18759 42 150072K dentry free命令在find命令执行前后结果对比: [root@localhost ~]# free -m total used free shared buff/cache available Mem: 1824 200 1534 15 89 1500 Swap: 2047 196 1851 [root@localhost ~]# free -m total used free shared buff/cache available Mem: 1824 480 105 15 1238 1161 Swap: 2047 196 1851 vmstat在find命令执行前后对比: [root@localhost /]# vmstat 2 procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu---- r b swpd free buff cache si so bi bo in cs us sy id wa st 2 0 201208 1570636 136 92168 0 0 5 5 16 19 27 3 70 0 0 0 0 201208 1511788 136 149564 0 0 1702 0 491 599 2 6 90 1 0 0 0 201208 1509428 136 149716 0 0 1106 0 478 801 0 2 98 0 0 注:发表长度限制,省略部分测试显示展开
作者回复: 原理分析加实践👍
共 2 条评论7 - 成为祝福2019-01-16老师好,请问在slabtop中的inode_cache和ext4_inode_cache有什么区别呢?如果每个文件系统都有inode_cache,整个vfs的有效命名空间都映射到了对应的文件系统,vfs为什么还需要inode_cache呢?
作者回复: 一个是VFS 虚拟文件系统的缓存,另一个则是具体的文件系统实现的缓存。 一个更好理解的例子是:操作系统有文件的缓存,而应用程序还会自己来分配内存缓存数据
4 - DJH2019-01-11请教三个问题。 1. 目录项是维护在内核中的一个内存数据结构,包括文件名。 我的问题是:文件名不是也应该存储在磁盘上么?不可能仅仅存在于内存吧? 2. 缓冲 I/O,是指利用标准库缓存来减速文件的访问... 我的问题时:减速文件访问的原因是什么? 3. 阻塞/非阻塞与同步/非同步的区别是什么?展开
作者回复: 1. 目录项是表示目录之间的树状关系,而文件名则会存储到数据部分。 2. 不好意思,是个笔误,当然是加速。谢谢指出 3. 这个在文章中有简单的介绍,回来在答疑篇中再展开一些
4 - Ansyear2021-11-05课后答案: 文件名以及文件之间的目录关系,都放在目录项缓存中。而这是一个基于内存的数据结构,会根据需要动态构建。所以, 1.查找文件时,Linux 就会动态构建不在缓存中的目录项结构,导致 dentry 缓存升高。 2.find还需要找到文件的位置信息,而这些信息存放在inode的元数据中,所以会将索引节点也读入缓存进行匹配,因此会导致inode_cached增长。 而dentry缓存和inode_cached又都是包含在cached的一部分,所以通过free命令查看cached会升高展开3
- Mr.Strive.Z.H.L2019-01-27老师您好: 关于目录项有一个疑惑: 通过目录项找到inode节点,从而访问具体的文件内容。其中inode和文件数据块都会被持久化,而目录项竟然不会被持久化,只是放在内存中进行缓存。 那么是否在每次开机时,内核都会自动构建文件系统完整的目录项,然后进行缓存??展开
作者回复: 按需构建很少一部分目录项就可以了,不需要所有的目录项
3 - Geek_9815f12020-07-17你要记住最重要的一点,在 Linux 中一切皆文件。不仅普通的文件和目录,就连块设备、套接字、管道等,也都要通过统一的文件系统来管理。 老师,上次听你 讲块设备和 文件系统的区别: 说块设备读写是绕过文件系统的。 现在是 块设备也通过统一的文件系统来管理。 这有矛盾吗?
作者回复: 这儿说的是VFS
共 3 条评论2 - hola2019-02-23“索引节点和文件一一对应,它跟文件内容一样,都会被持久化存储到磁盘” 是一对一关系吗,那么看我的服务器 $ df -i / 文件系统 Inode 已用(I) 可用(I) 已用(I)% 挂载点 /dev/mapper/VolGroup-lv_root 655360 98862 556498 16% / 意思是这个下面理论最多存放655360个文件 对吗展开
作者回复: 嗯
2 - 圣诞使者2019-01-11课后题,我感觉不会,应该只有用了目录的执行(x)权限内核才会缓存dentry,find只是用了目录的读(r)权限。2
- ninuxer2019-01-11打卡day24 阻塞非阻塞,同步异步再次mark下: 根据应用程序是否阻塞自身线程的运行,可以把文件 I/O 分为阻塞 I/O 和非阻塞 I/O; 根据是否等待响应结果,可以把文件 I/O 分为同步和异步 I/O2
- xzyeah2019-01-11老师,我的理解,不会引起内存升高,因为文件名存在于目录项,目录项本身就存在于内存缓存。共 1 条评论2
- Geek_9815f12020-07-20你要记住最重要的一点,在 Linux 中一切皆文件。不仅普通的文件和目录,就连块设备、套接字、管道等,也都要通过统一的文件系统来管理。 上一章不是说过 块设备绕过文件系统, 现在又说统一由文件系统管理
作者回复: 这儿说的“虚拟文件系统(VFS)”,而不是具体的某一个“文件系统”(比如XFS等)
1 - 流水2020-03-27评论区很多同学在问目录项问题,主要是因为把目录项和目录文件搞混了吧,目录项是内核的缓存对象,目录文件是特殊的文件,也有自己的inode,文件内容里记录了其他文件名及inode的映射关系1