• fork()函数与僵尸进程


    1.进程与 fork() 函数

    进程定义为程序执行的一个实例。一个可执行程序执行起来就是一个进程,再执行起来一次它就又是一个进程,多个进程可以共享同一个可执行文件。

    在一个进程中,可以用 fork() 创建一个子进程,当该子进程创建时,它从 fork() 指令的下一条(或者说从 fork() 的返回处)开始执行与父进程相同的代码。也就是说,fork() 函数产生了一个和当前进程完全一样的新进程,并和当前进程一样从 fork() 函数里返回,即原来一条执行通路(父进程),现在变成两条(父进程 + 子进程)。

    fork() 之后,是父进程 fork() 之后的代码先执行还是子进程 fork() 之后的代码先执行是不一定的,这个跟内核调度算法有关。

    #include 
    #include 
    #include 
    #include 
    
    // 信号处理函数
    void sig_usr(int signo)
    {
        printf("收到了SIGUSR1信号,进程id=%d!\n", getpid());
    }
    
    int main(int argc, char* const* argv)
    {
        pid_t pid;
    
        printf("进程开始执行!\n");
        
        // 系统函数,第一个参数是个信号,第二个参数是个函数指针,代表一个针对该信号的捕捉处理函数
        if (signal(SIGUSR1, sig_usr) == SIG_ERR)
        {
            printf("无法捕捉SIGUSR1信号!\n");
            exit(1);
        }
        
        pid = fork(); // 创建一个子进程
    
        // 要判断子进程是否创建成功
        if (pid < 0)
        {
            printf("子进程创建失败,很遗憾!\n");
            exit(1);
        }
    
        // 现在父进程和子进程同时开始运行了
        for (;;)
        {
            sleep(1);
            printf("休息1秒,进程id=%d!\n", getpid());
        }
    
        printf("再见了!\n");
    
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44

    在这里插入图片描述

    2.内存空间和返回值

    fork() 产生新进程的速度非常快,fork() 产生的新进程并不复制原进程的内存空间,而是和原进程(父进程)一起共享一个内存空间,但这个内存空间的特性是“写时复制”,也就是说,原来的进程和 fork() 出来的子进程可以同时自由地读取内存,但如果子进程或者父进程对内存进行修改的话,那么这个内存就会复制一份给该进程单独使用,以免影响到共享这个内存空间的其他进程使用。

    fork() 会返回两次,父进程中返回一次,子进程中返回一次,而且 fork() 在父进程中返回的值和在子进程中返回的值是不同的,子进程的 fork() 返回值是 0 0 0,父进程的 fork() 返回值是新建立的子进程的 PID。在下面代码中,因为全局量 g_mygbltest 的值发生改变,导致父、子进程内存被单独分开,所以每个的 g_mygbltest 值也不同。

    #include 
    #include 
    #include 
    #include 
    
    int g_mygbltest = 0;
    
    int main(int argc, char* const* argv)
    {
        pid_t pid;
    
        printf("进程开始执行!\n");
    
        pid = fork(); // 创建一个子进程
    
        // 要判断子进程是否创建成功
        if (pid < 0)
        {
            printf("子进程创建失败,很遗憾!\n");
            exit(1);
        }
    
        // 走到这里,fork()成功,执行后续代码的可能是父进程,也可能是子进程
    
        if (pid == 0)
        {
            // 这里是子进程,因为子进程的fork()返回值是0,下面是专门针对子进程的处理代码
            while (1)
            {
                g_mygbltest++;
                sleep(1);
                printf("真是太高兴了,我是子进程的,我的进程id=%d,g_mygbltest=%d!\n", getpid(), g_mygbltest);
            }
        }
        else
        {
            // 这里是父进程,因为父进程的fork()返回值大于0(实际返回的是子进程的PID),下面是专门针对父进程的处理代码
            while (1)
            {
                g_mygbltest++;
                sleep(5);
                printf("......,我是父进程的,我的进程id=%d,g_mygbltest=%d!\n", getpid(), g_mygbltest);
            }
        }
    
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47

    在这里插入图片描述

    3.一个和 fork() 执行有关的逻辑判断

    #include 
    #include 
    #include 
    #include 
    
    int main(int argc, char* const* argv)
    {
        // 我们假定fork()都会成功,所以不判断返回值了
        fork();
        fork();
        
        for (;;)
        {
            sleep(1);
            printf("休息1秒,进程id=%d!\n", getpid());
        }
    
        printf("再见了!\n");
        
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    在这里插入图片描述

    #include 
    #include 
    #include 
    #include 
    
    int main(int argc, char* const* argv)
    {
        // 我们假定fork()都会成功,所以不判断返回值了
        ((fork() && fork()) || (fork() && fork()));
    
        for (;;)
        {
            sleep(1);
            printf("休息1秒,进程id=%d!\n", getpid());
        }
    
        printf("再见了!\n");
        
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    || 或运算:有 1 1 1 1 1 1,全 0 0 0 0 0 0

    && 与运算:全 1 1 1 1 1 1,有 0 0 0 0 0 0

    ||&& 这两个运算具有短路特性。

    在这里插入图片描述

    在这里插入图片描述

    4.fork() 失败的可能性

    系统中进程太多。

    缺省情况,最大的PID为 32767

    如下图所示,每个用户有个允许开启的进程总数为 7774

    #include 
    #include 
    #include 
    #include 
    
    int main(int argc, char* const* argv)
    {
        printf("每个实际用户ID的最大进程数=%ld\n", sysconf(_SC_CHILD_MAX));
        
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    在这里插入图片描述

  • 相关阅读:
    让图片完美适应:掌握 CSS 的object-fit与object-position
    UE4(Unreal Engine4)虚幻引擎视口布局
    Redisson—分布式服务
    Re:从零开始的C++世界——(一)入门基础
    数据挖掘实战(4)——聚类(Kmeans、MiniBatchKmeans、DBSCAN、AgglomerativeClustering、MeanShift)
    【YOLOv5/v7改进系列】改进池化层为YOLOv9的SPPELAN
    PeopleCode中Date函数的用法
    leetcode做题笔记2216. 美化数组的最少删除数
    深入理解 Netty FastThreadLocal
    c语言练习95:练习使用双向链表(实现增删改查)
  • 原文地址:https://blog.csdn.net/qq_42815188/article/details/127443972