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个操作:
- 存储属性
内核先找到一个空闲的i节点(这里是263466)。内核把文件信息记录到其中。
- 存储数据
该文件需要存储在三个磁盘块,内核找到了三个空闲块:300,500,800。将内核缓冲区的第一块数据复制到300,下一块复制到500,以此类推。
- 记录分配情况
文件内容按顺序300,500,800存放。内核在inode上的磁盘分布区记录了上述块列表。
- 添加文件名到目录
新的文件名abc。linux如何在当前的目录中记录这个文件?内核将入口(263466,abc)添加到目录文件。文件名和inode之间的对应关系将文件名和文件的内容及属性连接起来。
这里的第四点我们解释一下:对于一个目录来说,它也是一个文件,所以他也有自己的inode编号以及它的文件内容,那么我们的目录的内容是什么呢?他其实存放的就是目录下的文件的inode编号以及对于的文件名称(文件名称不存放在inode属性中)
3**、软硬链接**
硬链接:所谓的硬链接,本质上就是在特定的目录的数据块中增加文件名和指向的文件的inode编号的映射关系,也就是在起别名而已,他们inode编号是相同的。

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