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

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

然后最近在《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()之前打开的文件描述符是被父进程与子进程共享的,当然还有该文件的偏移量。

最后感谢和推荐一下牛客网,前段时间找工作时发现这个平台,在上面认识了不少大神和小伙伴,

然后网页底端有个“模拟终端”,简单的Linux学习用浏览器就可以,上面的shell界面就是在这个上面跑的,

再也不用默默地等虚拟机开机了(*^__^*) ~


以下是使用C语言创建5个进程,其中两个进程为生产者进程,3个进程为消费者进程的示例代码: ```c #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/wait.h> #include <sys/types.h> #include <fcntl.h> #include <string.h> #include <ctype.h> #define BUFFER_SIZE 5 int buffer[BUFFER_SIZE]; // 缓冲区 int in = 0; // 生产者进程写入缓冲区的位置 int out = 0; // 消费者进程读取缓冲区的位置 // 用于生成随机大小写字母 char rand_uppercase_letter() { return 'A' + rand() % 26; } char rand_lowercase_letter() { return 'a' + rand() % 26; } // 生产者进程函数,往缓冲区中写入大写字母 void producer_uppercase(int fd) { while (1) { char c = rand_uppercase_letter(); sleep(rand() % 3); // 随机睡眠一段时间 int item = toupper(c); printf("Producer uppercase: %c\n", item); fflush(stdout); write(fd, &item, sizeof(item)); // 写入管道 } } // 生产者进程函数,往缓冲区中写入小写字母 void producer_lowercase(int fd) { while (1) { char c = rand_lowercase_letter(); sleep(rand() % 3); // 随机睡眠一段时间 int item = tolower(c); printf("Producer lowercase: %c\n", item); fflush(stdout); write(fd, &item, sizeof(item)); // 写入管道 } } // 消费者进程函数,从缓冲区中读取字符并输出 void consumer(int fd) { while (1) { int item; read(fd, &item, sizeof(item)); // 读取管道 sleep(rand() % 3); // 随机睡眠一段时间 printf("Consumer: %c\n", item); fflush(stdout); } } int main() { int fd[2]; pipe(fd); // 创建管道 int i; for (i = 0; i < 2; i++) { if (fork() == 0) { close(fd[0]); // 关闭读端 if (i == 0) { producer_uppercase(fd[1]); // 生产者进程写入大写字母 } else { producer_lowercase(fd[1]); // 生产者进程写入小写字母 } close(fd[1]); // 关闭写端 exit(0); } } for (i = 0; i < 3; i++) { if (fork() == 0) { close(fd[1]); // 关闭写端 consumer(fd[0]); // 消费者进程从缓冲区中读取字符并输出 close(fd[0]); // 关闭读端 exit(0); } } close(fd[0]); // 关闭管道的读端和写端 close(fd[1]); for (i = 0; i < 5; i++) { wait(NULL); // 等待子进程结束 } return 0; } ``` 在上面的示例代码中,我们首先创建了一个长度为5的缓冲区,使用管道来进生产者和消费者之间的通信。然后创建了两个生产者进程和三个消费者进程,并且分别在生产者和消费者进程的合适位置加入了一些随机睡眠时间,以便程序输出结果更具可读性。最后使用wait()函数等待子进程的结束。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值