【Linux系统编程十六】:基础IO3--用户级缓冲区
- 一.用户级缓冲区
- 二.缓冲区刷新策略
- 1.验证:
- 三.缓冲区意义
一.用户级缓冲区
我们首先理解上面的代码,分别使用printf和fprintf,fwrite往1号文件描述符里输出,也就是往显示器上输出,然后输出完后,就将显示器文件关闭。
我们将发现最后什么都不会输出,这是为什么呢?
printf,fprintf,fwrite里不是封装了系统调用接口write吗?它不是将内容输出到显示器上了吗?为什么显示器上不显示呢?
因为我们输出的内容并没有到显示器上,而是放在了一个叫缓冲区的地方。
并且这个缓冲区绝不在内核里。为什么呢?如果这个缓冲区在内核里,那么只要当内容放进缓冲区里,最后程序结束肯定会刷新出来,而这里并没有刷新出来,说明这个缓存区不是内核里的缓冲区。那这个缓冲区是什么呢?
其实这个缓存区是在内核外面,是属于用户级别的缓冲区,它是C语言给我提供的缓冲区!
1.我们输出的内容首先会存放在C语言提供的缓冲区里,当有合适条件时,就会调用wirte接口将内容刷新写入到内核里。
2.而这个合适的条件就是指缓冲区的刷新策略,比如显示器文件当遇到\n时就会将当前和之前的内容全部刷新出去:刷新的本质就是调用wirte将内容写入到内核里:
3.所以这也验证了exit和_exit的区别:exit在退出之前会刷新缓冲区。而_exit在退出之前不会刷新缓冲区。不是_exit不想刷,而是_exit它是系统调用接口,在内核里面,而我们所说的缓冲区在内核外面,它怎么刷新呢?所以exit作为库函数封装了_exit,并且在调用_exit之前,肯定调用了fflush接口,刷新了缓冲区。
所以:目前在我们所学习中,只需要考虑用户级缓冲区,而不是内核里的缓冲区,内核里的缓冲区有操作系统来刷新。
二.缓冲区刷新策略
目前我们认为,只要将数据刷新到内核里面,数据就可以被硬件获取。中间的过程不要关心,由操作系统来解决。但是我们要理解缓冲区是如何刷新的,刷新策略有哪些:
缓冲区刷新问题:
1.无缓冲:直接刷新。
2.行缓冲:遇到\n就刷新,没有遇到就不刷新。—显示器文件刷新策略
3.全缓冲:缓冲区满了才刷新。—普通文件刷新策略
4.特殊:当进程退出时,也会刷新缓冲区。
【问题】缓冲区在哪里呢?
缓冲区在FILE结构体里:
FILE里除了封装了文件描述符,还有对应打开文件的缓冲区字段和维护信息:
所以对应缓冲区,我们就可以看作是在用户层malloc申请的一块资源空间。
1.验证:
这里前三个调用库函数,最后一个调用系统调用write接口。最后再使用fork函数。
往显示器里输出:
显示器文件采取的刷新策略是行刷新,所以遇到换行,就会将缓冲区内容刷新。
过程也很简单:首先将要输入的内容放入缓冲区,如果有合适的条件就会调用write接口将数据写入到内核里:
fork创建的子进程,创建完后,后面也没有代码,就退出了。
而我们如果改成下面的操作就会发生不同的效果:
当我们将输出的内容重定向到log.txt文件时,再打印log.txt内容却发现打印了7条语句!这是为什么呢?
1.肯定和fork有关/肯定跟重定向有关
2.重定向后,就是往普通文件输出了,而不是往显示器文件里输出。所以缓冲区刷新策略就发生改变。
3.普通文件的刷新策略是全缓冲,也就是当缓冲区满了再刷新。
4.我们要理解缓冲区是用户级的,可以看成进程申请malloc的一块资源。
5.当我们从缓冲区内容刷新到操作系统里时,这个过程就相当于是写入数据了,而往父进程里写入数据,会发生写时拷贝,会将缓冲区拷贝一份给子进程。而当子进程创建出来后没有代码可用,就退出了,父进程也退出了。但要注意进程退出之前会将缓冲区的内容刷新,所以最后会将刷新两个一个的缓冲区内容到内核里。
6.witre是系统调用接口,直接就将数据写入到操作系统里,不会放入到缓冲区里。
根据以上的理解,我们就知道为什么第一个出现的是系统调用write输出的内容了,因为重定向后变成全缓冲,前面的三个库函数接口走完缓冲区里的内容还是不会刷新,要等缓冲区满了才会刷新,而系统调用直接就写入到内核里了。
并且也能理解为什么库函数的输出的内容输出了两次,因为重定向导致刷新策略改变和写时拷贝的发生。
三.缓冲区意义
缓冲区的存在可以解决用户效率的效率问题,可以赞上多个数据然后统一刷新,让我们的C语言接口变得更快。因为有些数据只是拷贝到缓冲区里,但并没有真正的写入操作系统,拷贝完后,后面的操作就跳过返回了。
还有一个作用那就是【配合格式化输出!没有缓冲区,C语言是无法完成格式化的输出的,需要有缓冲区来配合实现。