深入理解Linux中用户缓冲区,文件系统及inode

1缓冲区

1 .1、现象引入

首先观察下面代码

复制代码
  1 #include<stdio.h>
  2 #include<string.h>
  3 #include<unistd.h>
  4 int main()
  5 {
  6   const char * fstr= "hello fwrite\n";
  7   const char* str = "hello write\n";
  8   printf("hello printf\n"); //stdout-->1
  9 
 10   fprintf(stdout,"hello fprintf\n");// stdout--->1
 11 
 12   fwrite(fstr,strlen(fstr), 1, stdout);
 13 
 14   write(1,str,strlen(str));
 15 
 16   
 17   fork();                                                                                                                       
 18   return 0;
 19 }

运行结果如下:

但是当我们进行重定向操作时文件的内容却变成下面这样:

这就是今天我们所要讲述的用户缓冲区的内容,基于上述现象,我们接下来了解缓冲区以及为何会出现上面这种情况

-------------------------------------------------------------------------------------------------------------------------------- 根据我们上面的代码以及现象我们发现,C标准库中的库函数被打印了两次,而Linux中系统调用的却只打印一次,我们就可以发现,这一定是和我们C语言中库函数以及系统调用相关的,同时我们有和我们所调用的fork函数有关

首先我们需要知道缓冲区是一段空间,用于存放我们需要进行I/O的数据。我们对显示器或者其他硬件设备进行I/O时,都需要经过缓冲区,这就导致我们的缓冲区需要进行刷新。

在一个OS中,包含着系统级别的缓冲区,以及语言级别缓冲区,又叫做用户缓冲区,我们的C语言或者其他高级语言提供的都是用户缓冲区,但是硬件都是由操作系统管理的,所以在我们的各种语言的库函数中,一定调用的系统调用接口。将语言级别的缓冲区数据,放入了系统级别的缓冲区,最后刷新到硬件上。

在我们的Linux中,我们用的C语言的各种库函数也是封装的系统调用,例如:fprintf,printf,fwrite等都封装了我们系统的write函数,将用户缓冲区的数据刷新到系统的缓冲区,C语言的用户缓冲区一般存在于FILE结构体内部

1.2、刷新策略

对于缓存区刷新的策略有三种:

  • 无缓冲:直接刷新,进入缓冲区后立即调用write写入系统缓冲区
  • 行缓冲:直到遇到'\n'才刷新----对于显示器文件一般采用行缓冲
  • 全缓冲:缓冲区满了,才刷新。

注意:当我们进程结束的时候,会自动刷新缓冲区

1.3、那我们为什么需要缓冲区呢?

原因有两点:1、可以增加效率。让我们的C语言减少系统调用的次数

2、利于配合格式化。我们打印出来的数据都是字符数据,利用缓用户冲区可以将我 们各种可变参数变成字符,方便了系统缓冲区的输出。

1.4 现象解释

接下里我们就可以解释我们一开始的现象了:

当我们进行普通运行程序的时候,我们向显示器输出,默认的刷新方式是行缓冲,当我们利用重定向别的文件时,我们的刷新方式就变成了全缓冲,在上述代码中只有当缓冲区满了,或者进程结束的时候才会刷新缓冲区。我们的代码是不可能的让缓冲区填满的,所以我们调用的C语言的库函数中的数据都写入的缓冲区中,但是我们的系统调用write接口,不经过用户缓冲区,直接进入了系统缓冲区,我们调用了fork函数,产生子进程,子进程需要写实拷贝,当我们父进程的缓冲区在进程结束时被刷新,我们子进程同样拥有和父进程一样的缓冲区,所以子进程结束的时候也会被刷新,这就导致了我们的用户缓冲区的数据被刷新了两次,而我们的系统调用只会出现一次,所以就有了我们上述现象的发生。


2、inode(ext2文件系统)

Linux ext2文件系统,上图为磁盘文件系统图(内核内存映像肯定有所不同),磁盘是典型的块设备,一块磁盘被分为几个区,每个分区被划分为一个个的block。一个block的大小是由格式化的时候确定的,并且不可以更改。例如mke2fs的-b选项可以设定block大小为1024、2048或4096字节。而上图中启动块(Boot Block)的大小是确定的。

Block Group:ext2文件系统会根据分区的大小划分为数个Block Group。而每个Block Group都有着相同的结构组成,就想我们的省级单位管理市级单位一样,划分为小块,方便管理

超级块(Super Block):存放文件系统本身的结构信息。记录的信息主要有:bolck 和 inode的总量,未使用的block和inode的数量,一个block和inode的大小,最近一次挂载的时间,最近一次写入数据的时间,最近一次检验磁盘的时间等其他文件系统的相关信息。Super Block的信息被破坏,可以说整个文件系统结构就被破坏了

GDT,Group Descriptor Table:块组描述符,描述块组属性信息,里面包含例如下一个inode编号该从哪里开始等等。

块位图(Block Bitmap):Block Bitmap中记录着Data Block中哪个数据块已经被占用,哪个数据块没有被占用

inode位图(inode Bitmap):每个bit表示一个inode是否空闲可用。

inode table (节点表):存放文件属性 如 文件大小,所有者,最近修改时间,权限等

Data block(数据区):存放文件内容

将属性和数据分开存放的想法看起来很简单,但实际上是如何工作的呢?我们通过touch一个新文件来看看如何工作。

复制代码
[root@localhost linux]# touch abc
[root@localhost linux]# ls -i abc
263466 abc

我们将操作简化为下面这张图

创建一个新文件主要有一下4个操作:

  1. 存储属性

内核先找到一个空闲的i节点(这里是263466)。内核把文件信息记录到其中。

  1. 存储数据

该文件需要存储在三个磁盘块,内核找到了三个空闲块:300,500,800。将内核缓冲区的第一块数据复制到300,下一块复制到500,以此类推。

  1. 记录分配情况

文件内容按顺序300,500,800存放。内核在inode上的磁盘分布区记录了上述块列表。

  1. 添加文件名到目录

新的文件名abc。linux如何在当前的目录中记录这个文件?内核将入口(263466,abc)添加到目录文件。文件名和inode之间的对应关系将文件名和文件的内容及属性连接起来。

这里的第四点我们解释一下:对于一个目录来说,它也是一个文件,所以他也有自己的inode编号以及它的文件内容,那么我们的目录的内容是什么呢?他其实存放的就是目录下的文件的inode编号以及对于的文件名称(文件名称不存放在inode属性中)

3**、软硬链接**

硬链接:所谓的硬链接,本质上就是在特定的目录的数据块中增加文件名和指向的文件的inode编号的映射关系,也就是在起别名而已,他们inode编号是相同的。

软链接:是一个独立的文件,有独立的inode编号和数据块,他的数据块中保存的是指向文件的路径。(相当于windos下的快捷方式)

相关推荐
LuckyLay2 小时前
Ubuntu配置多版本Java,自由切换
java·linux·ubuntu
SongYuLong的博客2 小时前
openwrt 启动脚本
linux·运维·服务器·物联网
小旺不正经2 小时前
n8n简介
linux·运维·服务器
cwplh2 小时前
DP 优化一:单调队列优化 DP
算法
Halo_tjn2 小时前
基于Java的相关知识点
java·开发语言·windows·python·算法
CoovallyAIHub2 小时前
英伟达CES 2026炸场:没有新显卡,却掏出了让全球AI公司彻夜难眠的“算力核弹”
深度学习·算法·计算机视觉
阳光九叶草LXGZXJ3 小时前
达梦数据库-学习-43-定时备份模式和删除备份(Python+Crontab)
linux·运维·开发语言·数据库·python·学习
首席拯救HMI官3 小时前
【拯救HMI】HMI容错设计:如何减少操作失误并快速纠错?
大数据·运维·前端·javascript·网络·学习