从一个fork()实例理解全缓冲与行缓冲

来源:互联网 发布:开源微信cms 编辑:程序博客网 时间:2024/05/21 19:38

之前一直对无缓冲、行缓冲、全缓冲不太感冒,

然后最近在《UNIX环境高级编程》上看到这样一个例子,感觉挺好的拿来给没看过的小伙伴们看看:、

#include <unistd.h>#include <stdio.h>int globvar = 6;char buf[] = "a write to stdout\n";int main(){    int var;    pid_t pid;    var = 88;    if (write(STDOUT_FILENO, buf, sizeof(buf) - 1) != sizeof(buf) - 1)        ;    printf("before fork\n");    if ((pid = fork()) < 0)        ;    else if (pid == 0) {        globvar++;        var++;    } else {        sleep(2);    }    printf("pid = %ld, glob = %d, var = %d\n", (long)getpid(), globvar, var);    exit(0);}



为了不干扰代码中的几个打印语句我去掉了函数出错返回的处理语句,程序意思也很明确,先使用不带缓冲的系统调用write()将buf写入stdout,然后再打印一行“before fork”,最后fork一个子进程自增变量并打印,父进程休眠2s后打印, 直接运行输出像是这样:


好像蛮正常,但如果把输出重定向到一个文件,结果不一样了:



明明只有一行的 printf("before fork\n") 却被打印了两次。

首先,我们知道write()这种系统调用是无缓冲的,而标准I/O库提供了缓冲机制以尽可能减少使用read和write调用的次数:当流涉及一个终端时(不包含标准错误流),通常使用行缓冲;否则,默认为全缓冲。


那么原因很明了了,第一次 ./a.out 输出到终端,默认为行缓冲,字符串“before fork” 由于之后的 \n 被立刻输出了。

而 ./a.out > temp.out 输出端重定向到文件时,默认为全缓冲模式,printf("before fork\n") 后字符串“before fork\n”依然留在缓冲区等待输出(缓冲区溢出或程序退出),而I/O缓冲区是用malloc分配在进程的堆区的。


fork()执行后子进程获得了父进程数据空间、堆和栈的副本,当然也获得了缓冲区的一个副本;进程结束时,父进程与子进程的缓冲区数据先后被写到了temp.out中,得到两个“before fork”。


验证一下,我们把 printf("before fork\n") 里面的"\n"改成".",输出到标准输出:./a.out 看看:


由于没有了换行符,在行缓冲模式下,“before fork.”也被留在了缓冲区里,然后同上面一样被输出了两次。

当然我们也可以使用 setbuf 和 setvbuf 修改标准I/O的默认缓冲模式。


另外从该代码可以看出,在fork()之前打开的文件描述符是被父进程与子进程共享的,当然还有该文件的偏移量。

0 0