zoukankan      html  css  js  c++  java
  • 系统编程-进程-当fork遇到管道,可能碰撞出什么?

    第一部分

    1. 直接上代码

    #include <stdio.h>
    #include <unistd.h>
    #include <stdlib.h>
    
    int globvar = 6;
    char buf[] = "a write to stdout!
    ";
    
    void son_process_end_func(void)
    {
        printf("son process end!
    ");
    }
    int main(void)
    {
        int var;
        pid_t pid;
    
        var = 88;
        if ( write(STDOUT_FILENO, buf, sizeof(buf)-1)  !=  sizeof(buf)-1 ){
            printf("write error!! 
    ");
        return -1;
        }
            printf("before fork!!, pid = %d
    ", getpid() );
        /* 父进程中,fork返回新创建子进程的进程ID. 子进程中,fork返回0. 出现错误,fork返回负值. */
        if ((pid = fork()) < 0){
            printf("fork error!! 
    ");
        }
        else if (pid == 0){
           atexit(son_process_end_func);
            globvar++;
            var++;
           printf("son: pid = %d 
    ", getpid() );
        }
        else{
            sleep(2);
           printf("father: pid = %d 
    ", getpid() );
        }
    
        printf("pid = %d, glob = %d, var = %d 
    ", getpid(), globvar, var);
        exit(0);
    }

     

    2. 编译运行记录

    root@lmw-virtual-machine:/home/lmw/MINE/Linux_C_exercise/fork/test1#
    root@lmw-virtual-machine:/home/lmw/MINE/Linux_C_exercise/fork/test1# gcc fork.c -o ab
    root@lmw-virtual-machine:/home/lmw/MINE/Linux_C_exercise/fork/test1# ./ab > out.txt   注:这里使用了管道进行重定向
    root@lmw-virtual-machine:/home/lmw/MINE/Linux_C_exercise/fork/test1# cat out.txt
    a write to stdout!
    before fork!!, pid = 10425
    son: pid = 10426
    pid = 10426, glob = 7, var = 89
    son process end!          // 调用了进程终止函数,表明子进程正在终止
    before fork!!, pid = 10425 // 难点解释:看第22行的printf使用,由于标准I/O库带缓冲,
    father: pid = 10425     //  重定向则会将缓存中的内容也拷贝到子进程中一份,当子进程结束,该缓存就会被再次输出。
    pid = 10425, glob = 6, var = 88
    root@lmw-virtual-machine:/home/lmw/MINE/Linux_C_exercise/fork/test1#
    root@lmw-virtual-machine:/home/lmw/MINE/Linux_C_exercise/fork/test1#
    root@lmw-virtual-machine:/home/lmw/MINE/Linux_C_exercise/fork/test1# ./ab
    a write to stdout!
    before fork!!, pid = 10430
    son: pid = 10431
    pid = 10431, glob = 7, var = 89
    son process end!     // 调用了进程终止函数,表明子进程正在终止
    father: pid = 10430
    pid = 10430, glob = 6, var = 88
    root@lmw-virtual-machine:/home/lmw/MINE/Linux_C_exercise/fork/test1#
    root@lmw-virtual-machine:/home/lmw/MINE/Linux_C_exercise/fork/test1#

     

    小结:

    fork遇到管道并不是问题的重点,它俩没碰撞出啥东西。

    不要被本博文的标题给误导咯(因为我也是在实验中一步步加深自己的理解),哈哈,结论:

    若fork前标准I/O库函数也在场,那么由于使用IO库函数导致的缓存中的内容会被拷贝给子进程一份。

    准确地说,fork前缓存中的内容在父进程的堆空间中,fork后子进程会复制父进程的堆空间。

    如果体会不深,自己跑一遍上面的实验代码,感受感受就是了。

     

    知识点补充:
    atexit注册多个进程终止处理函数,先注册的后执行(先进后出,和栈一样)
    atexit()用于注册进程结束后所执行的函数
    return、exit和_exit的区别:
    return和exit效果一样,都是会执行进程终止处理函数,
    但是用_exit终止进程时并不执行atexit注册的进程终止处理函数。

     

    贴个实验代码的图(看图更方便)

     

    第二部分

     在第一部分的代码基础上只增加一句代码:fflush(stdout),

     完整的代码如下

    #include <stdio.h>
    #include <unistd.h>
    #include <stdlib.h>
    
    int globvar = 6;
    char buf[] = "a write to stdout!
    ";
    
    void son_process_end_func(void)
    {
        printf("son process end!
    ");
    }
    int main(void)
    {
        int var;
        pid_t pid;
    
        var = 88;
        if ( write(STDOUT_FILENO, buf, sizeof(buf)-1)  !=  sizeof(buf)-1 ){
            printf("write error!! 
    ");
        return -1;
        }
        printf("before fork!!, pid = %d
    ", getpid() );
    fflush(stdout); // 注意该行代码产生的效果
    /* 父进程中,fork返回新创建子进程的进程ID. 子进程中,fork返回0. 出现错误,fork返回负值. */ if ((pid = fork()) < 0){ printf("fork error!! "); } else if (pid == 0){ atexit(son_process_end_func); globvar++; var++; printf("son: pid = %d ", getpid() ); } else{ sleep(2); printf("father: pid = %d ", getpid() ); } printf("pid = %d, glob = %d, var = %d ", getpid(), globvar, var); exit(0); }

    我们再次编译运行:

    root@lmw-virtual-machine:/home/lmw/MINE/Linux_C_exercise/fork/test1# gcc fork.c -o ab
    root@lmw-virtual-machine:/home/lmw/MINE/Linux_C_exercise/fork/test1# ./ab > out.txt
    root@lmw-virtual-machine:/home/lmw/MINE/Linux_C_exercise/fork/test1# cat out.txt
    a write to stdout!
    before fork!!, pid = 14030
    son: pid = 14031
    pid = 14031, glob = 7, var = 89
    son process end!
    father: pid = 14030
    pid = 14030, glob = 6, var = 88
    root@lmw-virtual-machine:/home/lmw/MINE/Linux_C_exercise/fork/test1#

    现在我们通过管道得到的打印结果,和本博客第一部分中直接./ab 运行的结果,是一样的。 

    分析:

      增加了一行代码,使用fflush(stdout)刷新了缓冲区,所以fork前缓冲区内是空的,我们使用管道重定位的时候,子进程就不会从缓冲区复制数据了。

           此时的out.txt内的内容将和直接运行 ./ab 一样。

    小结: fflush是从内存缓冲区将数据写到内核缓冲,针对用户空间。

               fsync再将内核缓冲写到磁盘,针对内核空间。

        由此可见,当fork遇到管道的时候,子进程内会复制fork前的用户空间的缓冲区的数据,例如使用了C标准库的IO函数scanf、printf时,因为标准输入和标准输出通常是带缓冲的。

        PS: 而标准错误输出通常是无缓冲的,这样用户程序产生的错误信息可以尽快输出到设备。  

        当fork遇到管道的时候,在fork前,使用标准输入和标准错误的情形,本博客未做实验,读者可以自行尝试一下。

     根据本实验的运行结果来看,推测:虽然printf是行缓冲,但是执行代码 printf("before fork!!, pid = %d ", getpid() ) 时,并没有立即刷新用户空间的缓冲区到内核,而当我们使用fflush时,才刷新到了内核。

    .

    /************* 社会的有色眼光是:博士生、研究生、本科生、车间工人; 重点大学高材生、普通院校、二流院校、野鸡大学; 年薪百万、五十万、五万; 这些都只是帽子,可以失败千百次,但我和社会都觉得,人只要成功一次,就能换一顶帽子,只是社会看不见你之前的失败的帽子。 当然,换帽子决不是最终目的,走好自己的路就行。 杭州.大话西游 *******/
  • 相关阅读:
    树系列学习--树的定义(-)
    idea live template高级知识, 进阶(给方法,类,js方法添加注释)(二)
    mysql 查询所有子节点的相关数据
    maven util 类 添加 service
    idea live template高级知识, 进阶(给方法,类,js方法添加注释)
    idea live template
    eclipse 好用的插件总结
    Spirng+SpringMVC+Maven+Mybatis+MySQL项目搭建
    Mac OS 的命令行 总结
    jsp,jquery,spring mvc 实现导出文件
  • 原文地址:https://www.cnblogs.com/happybirthdaytoyou/p/14217723.html
Copyright © 2011-2022 走看看