跳到主要内容Linux 缓冲区和文件系统 | 极客日志C
Linux 缓冲区和文件系统
Linux 缓冲区和文件系统涉及用户空间缓冲机制与存储设备逻辑地址管理。FILE 结构体封装了文件描述符 fd 及缓冲区,缓冲区位于用户空间。C 标准库提供全缓冲、行缓冲和无缓冲三种模式,换行或进程退出会触发刷新。系统调用 write 绕过 C 缓冲区直接写入内核。fork 操作会导致父子进程共享缓冲区,进程结束时的刷新可能引发写时拷贝导致数据重复打印。文件系统方面,固态硬盘基于 NAND 闪存,通过 FTL 映射逻辑地址到物理地址。LBA 连续编号数据块,包含引导块、超级块、块组描述符表、位图、inode 表和数据块等核心结构。inode 记录文件属性,通过多级指针索引数据块,支持大容量存储。
清心3 浏览 1、fd
FILE 是在 C 中封装起来的一个结构体,访问文件的时候都是通过 fd 访问的,自然在 FILE 中是封装了 fd 的。FILE 结构体中,int _file 存放的就是 fd,其他的成员基本都是与缓冲区有关的。
2、缓冲区
(一)有换行有 return 全部打印
(二)无换行无 return 的 C 接口打印
很显然的,我们打印出了所有我们需要的内容,我们再看下一段。
理想状态下我们应该是打印出结果后然后进行 while 一直循环,实际上是一直不会打印,这是为什么呢?是的,待在缓冲区里。
首先我们要知道,缓冲区的大概位置。我们上面贴了一张 FILE 结构体的结构图,我们可以很清楚地看到缓冲区是 FILE 的成员指针指向的一块位置,也就是说缓冲区一定在用户空间而不是内核空间。
(三)无换行无 return 的系统调用接口打印
我们在调用上面三个函数的时候,都是调用的 C 接口,自然都待在缓冲区里了,我们再看下一个程序。
在这个程序中我们直接调用系统调用接口 write,所以它不会经过 C 语言的缓冲区,而是直接打印。
(四)有换行无 return 的 C 接口打印
这个程序和(二)程序的区别就只有换行,这告诉我们,C 语言缓冲区对于终端设备是行缓冲的。C 语言标准库的文件流有三种缓冲模式,分别是全缓冲、行缓冲和无缓冲。
全缓冲 _IOFBF:通常用于对磁盘文件的操作,数据会先被存储在缓冲区中,直到缓冲区被填满或者调用 fflush 函数、关闭文件(fclose)时,才会将缓冲区中的数据写入实际的文件,在全缓冲模式下,不会因为遇到换行符而自动刷新缓冲区。
行缓冲 _IOLBF:常见于标准输入、标准输出等终端设备相关的流,当遇到换行符(\n)时,会自动刷新缓冲区,将缓冲区中的数据写入对应的设备或文件,某些情况下即使没有换行符,缓冲区满时也会刷新。
无缓冲 _IONBF:标准错误输出通常默认是无缓冲的,确保错误信息能够立即显示,在无缓冲模式下,数据会立即写入对应的设备或文件,不会进行缓冲,因此不存在行刷新的概念。
(五)无换行有 return 的 C 接口打印
进程退出 return 的时候,也会对缓冲区进行刷新。
(六)深入理解缓冲区在用户空间
我们打印在显示器上的内容和打印在文件中的内容不一致,只有 write 打印了一遍,其他是按照顺序打印了两遍,我们当然能看出来这是 fork 的锅,接下来我们就深入理解一下谈一谈缓冲区。
首先我们分析第一张结果图,因为显示器是行缓冲的,所以我们 C 接口的打印放到缓冲区中一行就会被打印到屏幕上一行,三条语句执行完之后缓冲区是空的,然后 write 再往上写,所以整个打印出来的顺序也是按照代码中来的。
然后我们分析最后一张图,第一个我们可以肯定的是,打印到文件一定不是行缓冲,那就更不是无缓冲。实际上,由于文件是在存储硬件当中的,由于我们的效率问题,对于这种存储类的缓冲条件都是全缓冲,把缓冲区塞满再写入存储硬件中比塞一点写一点效率高得多,所以前三句 C 接口调用的打印全部在缓冲区中,然后 write 将自己打印,然后我们就碰到了 fork,创建子进程,父子进程此时共享代码段和数据段,因为它们都没有做修改,然后我们就碰到了 return 0,前面我们提到:进程结束也是要清空缓冲区的。此时父或子进程某一个先结束(由调度器决定),其中一个进程清空缓冲区的行为会引起另一个进程的写时拷贝,此时我们就有两份缓冲区,两个进程都结束都要清空缓冲区,自然在缓冲区中的内容要打印两份了(在这里要注意了,不只是子进程修改数据会引起子进程的写时拷贝,父进程对数据做修改时父进程也要发生写时拷贝,被写时拷贝的数据再次发生修改就直接修改了,不发生写时拷贝)。
二、文件系统
文件一般存储在硬盘当中,我们已经学习了动态的文件,也就是进程打开文件访问文件的过程,现在我们来学习一下静态的文件,我们来直接学习一下固态硬盘。
1、固态硬盘
固态硬盘是一种基于 NAND 闪存的存储单元,我们常用的笔记本上的固态硬盘存储单元类型一般都是 TLC 的,三层单元,每个单元存储 3bits,寿命较短成本较低,它通过电荷存储数据,通过高低电平区分 0/1。
NAND 闪存的写入操作只能在已擦除的块上进行,擦除的最小单位就是块,通常为 128KB-4MB,写入的最小单位是页,通常为 4KB,所以它读的速度特别快,可以到微秒级,因为需要先擦除块,写的速度较慢,只能到毫秒级,每个块的擦写次数有限,超过后就会失效,一般 TLC 的擦写上限在 500-1500 次。
这样的性质会带来一些不太好的结果,比如我们要写的内容很小,假设为 4KB,那么我们先要擦除高达 4MB 的块才能进行写入,所以我们通过算法,将写入分散到所有块,避免某些块因为多次擦除而失效。
固态硬盘控制器中的核心逻辑叫做 FTL (Flash Translation Layer) Flash 翻译层,负责将文件系统的逻辑地址映射到物理地址,是不是有点像进程地址通过页表映射到物理地址呢。
接下来我们要学习文件系统的逻辑地址 LBA,因为我们很清楚 FTL 映射到物理地址的过程是与页表映射是相似的,而逻辑地址的组织方式与进程地址可是不同的,虽然是有相似之处的~
2、逻辑地址 LBA
LBA 从 0 开始,按照连续的整数顺序依次为存储设备中的每个数据块编号,存储设备中的每个数据块都对应一个唯一的 LBA 值,比如第一个数据块的 LBA 是 0,第二个是 1,依此类推,我们对应的数据块有 Super Block、Group Descriptor Table、Block Bitmap、inode Bitmap、inode Table、Data blocks 六个,最理想的情况下它们的 LBA 按照我上面写的顺序从 0 到 5。
我们把一块固态硬盘,我们笔记本上有一些品牌比如说某 L 开头的品牌,在我们购买的时候是默认给你带 1T 固态的,一般的品牌就是 512G,我们拿到笔记本之后会对电脑进行分区,C 盘作为系统盘分到最多的内存,G 盘作为游戏盘给到 300 多 G,然后 D 盘用来学习写代码,留个 200G,EF 盘用来存一些其他的东西,这样一套流程下来我们就分好盘了,我们说对硬盘做管理当然也是先描述后组织,Block Group 就是组织和管理磁盘空间的一种重要结构。
在 n 个 Block Group 之前有一个叫做 Boot Block(引导块)的区域,在计算机启动过程中起着至关重要的作用,它是计算机启动过程的起点,没有引导块中的引导代码,计算机就无法知道如何加载操作系统,也就无法正常启动。
(一)数据块 Data Blocks
用来存储数据的块,NAND FLASH 内部的数据块由多个 page 组成,通常大小为 4KB(现在也有 8KB 和 16KB),这个 page 就是我们前面提到的最小写入单位:页。
(二)inode 表 inode Table
inode 全称为索引节点,是一种数据结构,用于存储单个文件的全部属性,一般来说每个文件都有一个 inode。
struct inode {
unsigned long i_block[NUM];
unsigned long i_ind_block;
unsigned long i_dind_block;
unsigned long i_tind_block;
}
其中 inode 编号每个文件都是不同的,我们主要说说数据块指针。
(1)直接块指针
直接块指针的 NUM 一般是 12,它指向的位置是我们可以直接用来存储的位置,如果我们内容比较小(12*4KB = 48KB以内),那么直接块指针可以直接访问这些数据。
(2)一次间接块指针
如果内容大于 48KB,就需要一次间接块指针,一次间接块指针指向一个间接块,这个间接块存储中存储着多个指向数据块的指针,如我们的内容在 (4KB/4b)*4KB = 4MB以内,通过一次间接块指针和直接块指针就可以访问这些数据。
(3)二次间接块指针
二次间接块指针指向一个二次间接块,这个二次间接块存储中存储着多个指向间接块的指针,与一次间接块指针类似,这样我们存储的范围就达到了 (4KB/4b)*(4KB/4b)*4KB = 4GB。
(4)三次间接块指针
同上,最终我们最大的存储范围达到了 (4KB/4b)*(4KB/4b)*(4KB/4b)*4KB = 4TB。
(三)inode 位图 inode Bitmap
我们通过位图来和 inode 一一对应,位图上对应的比特位为 0,那么该 inode 就没有被使用,可以被分配,如果为 1 则被占用,当从 1 变为 0 时,该 inode 又可以被分配了,其实这个过程就是一个删除的过程,一旦一个文件的 inode 无了,那么这文件是真的无了。
在文件系统中,标识文件不是看它的名字,而是看它的 inode,一旦 inode 和文件取消绑定了,那么操作系统就找不到这个文件了,再次写入其他内容的时候也就会被擦除覆盖了,换而言之,删除恒等于可以被覆盖。
(四)块位图 Block Bitmap
我们通过位图来和数据块 page 一一对应,位图上对应的比特位为 0,那么该页 page 就没有被使用,可以被分配,如果为 1 则被占用,当从 1 变为 0 时,该 page 又可以被分配了,如果我们要删除一块空间,只需要将它的对应的位置 0,到再次被写入的时候就会消失了,当然我们的 NAND 闪存是定期擦除的,过一段时间它自己就被擦除了。
(五)块组描述符表 Group Descriptor Table
记录了该块组的详细信息,包括块位图的位置、inode 位图的位置、inode 表的起始位置等,用于定位和管理块组内的各种数据结构。
(六)超级块 Super Block
超级块是文件系统的核心,记录了文件系统的全局信息,如块大小、inode 数量、空闲块数量等,为了防止超级块损坏导致文件系统无法使用,每个块组中可能会包含超级块的副本,不过并非所有块组都有。
微信扫一扫,关注极客日志
微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
相关免费在线工具
- Base64 字符串编码/解码
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
- Base64 文件转换器
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
- Markdown转HTML
将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online
- HTML转Markdown
将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online
- JSON 压缩
通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online
- JSON美化和格式化
将JSON字符串修饰为友好的可读格式。 在线工具,JSON美化和格式化在线工具,online