c/c++语言把输入输出的功能从语言分离出来,通过标准库实现。记得在初学c语言时,书上说“stdin,stdout,stderr是c语言标准IO流,stdin负责从终端读入,stdout,stderr负责把信息输出到终端,其中stderr用于处理错误信息”,当时,看到这里我就在想,同样是把信息输出到终端,什么需要专门提供一个处理错误的“stderr”来?这个问题直到我学习过标准库的IO缓冲区之后才明白…
我们平时使用的stdio.h头文件中声明的函数,其实是在Linux的系统调用基础上封装的函数,它们相对系统调用的最大区别是:它们拥有每个进程私有的缓冲区。缓存分为3中:无缓冲,行缓冲,块缓冲。
- 无缓冲:使用fopen(3)函数返回的IO流,写端只要有数据进去,读端就能读到。默认情况下,连接到终端的stderr是无缓冲的,无缓冲意味数据会立即显示在终端。
- 行缓冲:使用fopen(3)函数返回的IO流,写端写入的数据需要等到缓冲区填满或者遇到换行符或者用fflush(3)强制冲洗缓冲区,读端才能读到。默认情况下,连接到终端的stdout是行缓冲的。
- 块缓冲:使用fopen(3)函数打开文件时,就采用的是块缓冲,事先用malloc分配一块空间,把数据先放到这块空间上,当空间填满后或调用fflush(3)才会把数据写到文件上。
看下测试代码:
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<signal.h>
FILE *p = NULL;
void fun(int n){
fflush(p);
fclose(p);
exit(0);
}
int main(){
p = fopen("./temp.txt", "w"); //fopen打开文件,默认是块缓冲的
//setbuf(p,NULL); //setbuf设置p为无缓冲,第一次测试时先注释掉
signal(SIGQUIT,fun); //安装信号处理函数,当按下"ctrl + \"时捕获SIGQUIT信号
if(p == NULL){ //文件没打开就退出
printf("文件打开失败\n");
exit(0);
}
while(1){ //不停的向文件写入1
sleep(1); //如果不sleep下,块缓冲区很快就填满了,看不到测试效果
fwrite("1",2,1,p);
}
fclose(p);
return 0;
}
说明下,第一次测试时把"setbuf(p,NULL);"注释掉,当程序运行起来后,在重新开启一个终端,使用命令“tail -f temp.txt”观察文件,发现什么也没有,原因是采用的块缓冲,需要当缓冲区填满后才会真正的把数据写入文件中;当按下“ctrl + \”时,回调信号处理函数,在处理函数中会使用fflush冲洗块缓冲把数据写入文件中;第二次测试时把"setbuf(p,NULL);"打开,运行程序,再次用命令“tail -f temp.txt”观察文件,发现有数据“1”输出。
总结:
- 缓存区是每个进程私有的;
- 通常我们说“标准IO是带有缓冲区”,而linux系统调用是“不带有缓冲区”的,其实严格来说系统调用也是有缓冲区,但这个缓冲区是在内核中,是进程共享的,使用write(2),read(2)等底层函数时,它们的返回值是一个int类型的文件描述符,当这个内核中缓存准备好了,我们就会说“文件描述符就绪了”。
再说个题外话:认识到内核中缓存区是很重要的,因为对于“同步与异步”,“阻塞与非阻塞”,“为什么采用内存文件映射方式处理大文件会比使用read(2)效率高”等问题,不了解内存中缓冲区就很难理解了。