目录
预备知识
观察现象
第一:携带\n,不使用fork(),打印到显示器
第二:携带\n,使用fork(),打印到显示器
第三:携带\n,使用fork(),打印到文件里
第四:不携带\n,使用fork(),打印到显示器
解释现象
携带\n,不使用fork(),打印到显示器 -- 解释
携带\n,使用fork(),打印到显示器 -- 解释
携带\n,使用fork(),打印到文件中 -- 解释
预备知识
有三种缓冲方式:全缓冲、行缓冲和无缓冲。
全缓冲(Fully Buffered):数据写入到缓冲区中,当缓冲区被填满或者达到一定条件时,才会刷新缓冲区。当内容写入到文件中时,也使用无缓冲。
行缓冲(Line Buffered):每当遇到换行符的时候,缓冲区就会刷新,将内容写入到文件中。
无缓冲(Unbuffered):数据直接刷新,写多少刷新多少。
观察现象
第一:携带\n,不使用fork(),打印到显示器
第二:携带\n,使用fork(),打印到显示器
第三:携带\n,使用fork(),打印到文件里
第四:不携带\n,使用fork(),打印到显示器
解释现象
如上图所示,C语言自己为自己提供了一个缓冲区,这个是用户级缓冲区。
操作系统在内核中也提供了一个缓冲区,这个是系统缓冲区。
为什么C语言要自己提供一个缓冲区,直接用系统的不就好了吗?
因为直接每次都进行实际的I/O操作(如磁盘读写、网络传输)可能会导致性能下降,因此引入缓冲区可以减少实际I/O操作的次数,提高程序的执行效率。
具体来说:
-
性能优化:使用缓冲区可以减少系统调用的次数,避免频繁的I/O操作,从而提高程序的性能和效率。将数据暂时存储在缓冲区中,一次性地进行输出,比多次逐个字符地输出要更快。
-
灵活性:C语言为程序员提供了对缓冲区的控制权,可以手动刷新缓冲区、设置缓冲区大小等,使得程序在不同场景下能够灵活地进行优化。
-
错误处理:使用缓冲区可以使得程序更容易进行错误处理。在数据写入缓冲区时,程序可以检查写入是否成功、缓冲区是否溢出等,从而更好地处理可能出现的错误情况。
携带\n,不使用fork(),打印到显示器 -- 解释
当我们使用C语言的接口时,例如:printf/fprintf/fwrite等函数时,写入的内容会暂时放入到用户级缓冲区中。
正如之前所说,打印到显示器中时使用的是行缓冲,如果遇到\n,就会立即刷新。
所以在“携带\n,不使用fork(),打印到显示器”中时,才会C语言调用接口和系统调用接口都只打印一次。因为是行缓冲,每句语句到最后都直接刷新打印了,不会有延时。
携带\n,使用fork(),打印到显示器 -- 解释
在“携带\n,使用fork(),打印到显示器”中时,C语言的接口打印一次,系统调用接口也打印一次。因为携带了\n,是行缓冲,任何一个语句写入到缓冲区,因为最后携带了\n,所以在下一个语句到缓冲区的时候,前面的会被刷新出去。所以在最后fork()的时候,子进程拷贝了父进程的数据和代码,但是父进程的缓冲区是空的,所以子进程的缓冲区也是空的,所以只打印一次。write()函数是系统调用,直接放入到系统的缓冲区,所以不受影响。
具体情况如下所示:
重定向:刷新方式发生更改,
数据写入时,由刷新改为暂存
携带\n,使用fork(),打印到文件中 -- 解释
在“携带\n,用fork(),打印到文件中”时,C式接口打印两次,系统调用接口打印一次。因为打印到文件中时,缓冲方式由行缓冲转换为全缓冲。所以父进程中就会把C式接口中的内容全部放入到C语言的缓冲区中,当使用fork的时候,子进程拷贝父进程的内容,也会拷贝父进程的缓冲区。当程序结束,要刷新缓冲区。刷新缓冲区的本质就是写入,就会发生写时拷贝,所以到系统缓冲区时,C式的接口就会打印两次,系统调用接口不受影响。
过程如图所示: