因为IO相关函数与系统调用接口对应,并且库函数封装系统调用,所以本质上,访问文件都是通过fd访问的。所以C库当中的FILE结构体内部,必定封装了fd。
往标准输出写入有三种方法:
- printf(str1);//C库函数
- fwrite(str2,1,strlen(str2),stdout);//C库函数
- write(1,str3,strlen(str3));//系统调用
来段代码研究下:
运行出结果:
但是如果对进程实现输出重定向呢?结果此时变成了:
我们发现 printf 和 fwrite (库函数)都输出了2次,而 write 只输出了一次(系统调用)。为什么呢?肯定和fork有关!先来了解下缓冲区。
缓冲方式有三种
- 无缓冲 (write)
- 行缓冲(写入显示器):遇到 \n 或者缓冲区写满就刷新,执行真正的IO操作(printf)
- 全缓冲(写入硬盘):碰到1或者缓冲区写满才刷新(fwrite)
- 一般C库函数写入文件时是全缓冲的,而写入显示器是行缓冲。
- printf、fwrite 库函数会自带缓冲区(进度条例子就可以说明),当发生重定向到普通文件时,数据的缓冲方式由行缓冲变成了全缓冲。
- 而我们放在缓冲区中的数据,就不会被立即刷新,甚至fork之后
- 进程退出之后,会统一刷新,写入文件当中。
- 但是fork的时候,父子数据会发生写时拷贝,所以当你父进程准备刷新的时候,子进程也就有了同样的一份数据,随即产生两份数据。
- write 没有变化,说明没有所谓的缓冲。
此时再来说说为什么 printf 和 fwrite (库函数)都输出了2次,而 write 只输出了一次(系统调用)?
解析:因为在fork之前调用write,它的数据只写到标准输出一次,而它没有缓冲区的概念,所以即使文件重定向到文件数据也是直接被输出。fork出的子进程并没有得到父进程的代码数据。而printf和fwrite都是库函数,他们自带缓冲区,并且,重定向后行缓冲变为全缓冲,即使遇到换行符也不输出,直到缓冲区写满,此时fork便有了两份数据。
综上: printf fwrite 库函数会自带缓冲区,而 write 系统调用没有带缓冲区。另外,我们这里所说的缓冲区,
都是用户级缓冲区。其实为了提升整机性能,OS也会提供相关内核级缓冲区,不过不再我们讨论范围之内。
那这个缓冲区谁提供呢? printf fwrite 是库函数, write 是系统调用,库函数在系统调用的“上层”, 是对系统
调用的“封装”,但是 write 没有缓冲区,而 printf fwrite 有,足以说明,该缓冲区是二次加上的,又因为是
C,所以由C标准库提供。