子进程可以复制父进程的缓冲区吗?

在阅读《UNIX 高级环境编程》进程控制那块,有个例子是差不多这样的:
在子进程中改变变量,观察父进程之后变量的变化

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include<sys/mman.h>
#include<pthread.h>
#include<semaphore.h>
#include <netinet/in.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<sys/select.h>
#include<sys/epoll.h>
#include<unistd.h>
#include<dirent.h>
#include<pwd.h>
#include<grp.h>
#include<time.h>
#include<errno.h>
#include<signal.h>
#include<fcntl.h>
#include<sys/wait.h>
int glob=6;
char buf[]="write to stdout\n";
int main(int argc,char**argv)
{
    
    
    int var;
    pid_t pid;
    var =88;
    if(write(STDOUT_FILENO,buf,sizeof(buf)-1)!=sizeof(buf)-1){
    
    
        fprintf(stderr,"write error");
        exit(0);
    }
    printf("before fork\n");
    // fflush(stdout);
    // setbuf(stdout,NULL);
    // setvbuf(stdout,(char*)NULL,_IONBF,0);
    if((pid=fork())<0){
    
    
        fprintf(stderr,"fork error");
        exit(0);
    }else if(pid==0){
    
    
        glob++;
        var++;
    }else
        sleep(2);
    printf("pid =%d ,glob =%d ,var=%d\n",getpid(),glob,var);
    return 0;
}

开始我也觉得就这样,因为子进程写时复制父进程的堆栈和数据空间,子进程改变变量父进程的变量没啥可变化的。
但书上忽然话峰一转,提到了标准IO函数写缓冲区的复制问题。

问题是这样的:我们的write(STDOUT_FILENO)在终端打印一次没错,我们的printf(“before fork\n”)在终端也打印一次没错。但如果我们把这个结果以a.out >a.txt的形式
重定向导入到文件中会将这些语句打印几次呢???
答案:write(STDOUT_FILENO)打印一次,printf(“before fork\n”)打印两次
哇这说明了什么呢???书上指出write是不带缓存的,printf是带有一个8192字节的缓存的。令人意外且容易被我们忽视的是:子进程会继承父进程写缓冲区的内容的。那为什么写到终端就打印没有这句话呢,写到文件却出现了呢?
这是因为标准输出是行缓冲的我们写的内容中有个’\n’的换行符刷新了行缓冲,子进程因此得不到此内容;然而重定向是让输出指向文件,并且此时它的缓冲模式成了写入普通文件的缓冲模式:全缓冲(不懂见《UNIX 高级环境编程》第五章)这导致了我们父进程printf打印完了之后缓冲区中的内容还在,并且其中的内容复制给了子进程,子进程也因此会打印出这句话。

好奇宝宝们会问,那怎么才可让子进程不打印这句话呢???
答案是在父进程printf之后用这三个函数其中之一刷新缓冲区即可。

     fflush(stdout);
     setbuf(stdout,NULL);
    setvbuf(stdout,(char*)NULL,_IONBF,0);

大家还可以注意到写缓冲是8192字节,如果我们写超过8192字节的内容那这个缓冲区是否已经被刷新了呢?
答案是肯定的。

如下的实验:我在printf(“before fork\n”)添加很多空格,到这个字符串有300个字节为止。接着我们再打印它个30次,3000×30=9000=8192;这意味这全缓冲下我们在打印了300×28=8400字节之后缓冲区的内容全部被刷新出去了。那文件中我们可以打印句字符串呢???父进程的30+还在缓冲区继承给子进程的(30-28)=32。 答案的确如此。

此时我们用这条命令a.out |grep before | wc
猜猜看得到的行数的是多少?对是32!这里的 |同样重定向了标准输出到可执行文件grep中。如果大家之前没看这篇文章肯定以为会是30吧!!!惊不惊喜?

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include<sys/mman.h>
#include<pthread.h>
#include<semaphore.h>
#include <netinet/in.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<sys/select.h>
#include<sys/epoll.h>
#include<unistd.h>
#include<dirent.h>
#include<pwd.h>
#include<grp.h>
#include<time.h>
#include<errno.h>
#include<signal.h>
#include<fcntl.h>
#include<sys/wait.h>
int glob=6;
char buf[]="write to stdout\n";
int main(int argc,char**argv)
{
    
    
    int var;
    pid_t pid;
    var =88;
    if(write(STDOUT_FILENO,buf,sizeof(buf)-1)!=sizeof(buf)-1){
    
    
        fprintf(stderr,"write error");
        exit(0);
    }
for(int i=0;i<30;i++)
    printf("before fork                                                                                                                                                                                                                                                                                               \n");

    // fflush(stdout);
    // setbuf(stdout,NULL);
    // setvbuf(stdout,(char*)NULL,_IONBF,0);
    if((pid=fork())<0){
    
    
        fprintf(stderr,"fork error");
        exit(0);
    }else if(pid==0){
    
    
        glob++;
        var++;
    }else
        sleep(2);
    printf("pid =%d ,glob =%d ,var=%d\n",getpid(),glob,var);
    return 0;
}

发现了吧,自己做写小实验是可以有很大收获的。

猜你喜欢

转载自blog.csdn.net/adlatereturn/article/details/105691795