问小白 wenxiaobai
资讯
历史
科技
环境与自然
成长
游戏
财经
文学与艺术
美食
健康
家居
文化
情感
汽车
三农
军事
旅行
运动
教育
生活
星座命理

fork函数详解:进程创建与管理的关键技术

创作时间:
作者:
@小白创作中心

fork函数详解:进程创建与管理的关键技术

引用
CSDN
1.
https://blog.csdn.net/2401_87556630/article/details/143648951

fork函数的基本概念

fork函数是Unix/Linux系统中用于创建新进程的重要系统调用。其返回值有三种情况:

  • 在父进程中,fork返回新创建子进程的进程ID;
  • 在子进程中,fork返回0;
  • 如果出现错误,fork返回一个负值;

此外,还有两个相关的函数:

  • getppid():获取当前进程的父进程ID;
  • getpid():获取当前进程的ID;

注意:在fork函数执行完毕后,如果创建新进程成功,则会出现两个进程,一个是子进程,一个是父进程。在子进程中,fork函数返回0,在父进程中,fork返回新创建子进程的进程ID。我们可以通过fork返回的值来判断当前进程是子进程还是父进程。

fork函数会复制已有的进程及其进程控制块(PCB),并为新进程分配一个PID。子进程的PID通常是父进程PID加1。

一个简单的fork示例

让我们通过一个简单的代码示例来理解fork函数的使用:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main(int argc, char const *argv[])
{
    pid_t pid;
    // 判断1
    if ((pid=fork()) < 0)
    {
        perror("fork error");
    }
    // 判断2
    else if (pid == 0) // 子进程
    {
         printf("child getpid()=%d\n", getpid());
    }
    // 判断3
    else if(pid > 0) // 父进程
    {
        printf("parent getpid()=%d\n", getpid());
    }
    return 0;
}

运行结果如下:

parent getpid()=13725
child getpid()=13726

这个例子展示了fork函数的一个重要特性:在调用fork后,fork函数后面的代码会执行两遍。这是因为fork函数创建了一个新的子进程,父进程和子进程都会继续执行fork后面的代码。

成功fork的执行流程

让我们梳理一下fork函数成功执行的流程:

  1. 执行pid=fork(),如果成功,pid将获得一个非0正值。如果失败,返回-1。
  2. 因为pid>0,所以进入判断3,这是在父进程中。
  3. 父进程的代码执行完毕后,程序会再次执行fork后面的代码,此时pid的值变为0,进入判断2,这是在子进程中。

注意:这里的pid_t类似于一个类型,就像int型一样,pid_t定义的变量都是进程号类型。这个语句的意思是定义了一个pid_t类型的变量pid,fork()函数返回一个进程号,这个进程号赋给了pid。pid_t在头文件types.hsys/types.h)中定义。pid_t实际上是一个short类型变量,表示的是内核中的进程表的索引。

更多fork示例

让我们尝试分析下面的代码:

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>

int main()
{
    pid_t fpid; // fpid表示fork函数返回的值
    int count=0;
    fpid=fork();
    if(fpid<0)
        printf("error in fork!");
    else if(fpid==0)
    {
        printf("我是子进程,id:%d\n",getpid());
        count++;
    }
    else
    {
        printf("我是父进程,id:%d\n",getpid());
        count++;
    }
    printf("统计结果是:%d\n",count);
    exit(0);
}

父子进程的调用流程

下面我们讲解一下fork调用的细节:

int main(){
    fork(); // fork1
    fork(); // fork2
    printf("love\n");
    return 0;
}

上述代码会打印4次"love",创建了4个进程(1个父进程,3个子进程)。假设我们的main进程pid是1001,注意看左边的1,2,4进程其实都是main进程1001。进程3,6是同一个进程1002。所有一共有1001,1002,1003,1004四个进程。也就是只要数叶子节点就行了。其中1个是main进程,其它3个是子进程。有多少个进程就输出多少次hello字符串。也就是只有4,5,6,7执行了printf。

多个fork示例

eg1:

int main()
{
    int n=2;
    for(;i<n;i++)
    {
        fork();
        printf("A\n"); // 遇到\n会自动刷新缓冲区
    }
    exit(0);
}

eg2:

int main()
{
    int n=2;
    for(;i<n;i++)
    {
        fork();
        printf("-"); // 不会刷新缓冲区
    }
    exit(0);
}

eg3:

int main()
{
    fork()||fork();
    printf("A\n");
    exit(0);
}

结果打印3个A,共创建3个进程。fork()给子进程返回一个零值,而给父进程返回一个非零值。在main这个主进程中,首先执行fork()|| fork(), 左边的fork()返回一个非零值,根据||的短路原则,前面的表达式为真时,后面的表达式不执行,故包含main的这个主进程创建了一个子进程,由于子进程会复制父进程,而且子进程会根据其返回值继续执行,就是说,在子进程中,fork()||fork()这条语句 左边表达式的返回值是0, 所以||右边的表达式要执行,这时在子进程中又创建了一个进程,即main进程->子进程->子进程,一共创建了3个进程。

eg4:

int main()
{
    fork()&&fork();
    printf("A\n");
    exit(0);
}

结果输出3个A,创建3个进程。

注意事项

父子进程相同:

  • 刚刚fork后,data段,text段,堆,栈,环境变量,全局变量,宿主目录位置,进程工作目录,信号处理方式

父子进程不同:

  • 进程id,返回值,各自父进程,进程创建时间,闹钟,未决信号

父子进程共享:

  • 文件描述符
  • mmap映射区
  • 读时共享,写时复制-----------------全局变量
© 2023 北京元石科技有限公司 ◎ 京公网安备 11010802042949号