侧边栏壁纸
  • 累计撰写 218 篇文章
  • 累计创建 59 个标签
  • 累计收到 5 条评论

进程间通信02:fork 通过分支区分进程

barwe
2022-07-12 / 0 评论 / 0 点赞 / 864 阅读 / 3,135 字
温馨提示:
本文最后更新于 2022-07-14,若内容或图片失效,请留言反馈。部分素材来自网络,若不小心影响到您的利益,请联系我们删除。

fork函数是unistd头文件中提供的一个复制调用进程到子进程的工具。

/* Clone the calling process, creating an exact copy.
   Return -1 for errors, 0 to the new process,
   and the process ID of the new process to the old process.  */
extern __pid_t fork (void) __THROWNL;

POSIX 标准和 unistd.h 头文件

通常我们这样调用fork函数:

pid_t p = fork();

调用fork函数的进程称为 Calling Process,姑且称之为父进程。

调用fork函数会复制父进程创建一个一模一样的子进程,子进程会从程序中调用fork的位置开始执行。

调用fork函数会拿到三个可能的返回值:

  • -1: 克隆子进程失败
  • 0: 在克隆的子进程中也会调用一遍这个fork函数,但是返回 0
  • PID: 在父进程中调用fork函数会返回克隆的子进程的进程号

fork函数创建的子进程与父进程共用同一个程序(同一套代码),那么怎么区分哪些代码是在主进程中执行,哪些代码是在父进程中执行的呢?答案是通过fork()的返回值。

父进程和子进程都会执行fork(),但是能拿到不同的返回值,这是由操作系统实现的:

  • 父进程中拿到的返回值是子进程的进程号
  • 子进程中拿到的返回值是 0

于是就有下面这种写法:

printf("main process pid: %d\n", getpid());
pid_t p = fork();
printf("fork returns %d\n", p);
if (p < 0)
    printf("error in fork!");
else if (p == 0)
    printf("child process pid: %d\n", getpid());
else
    printf("parent process pid: %d\n", getpid());
  • getpid函数在 unistd.h 中声明,用来获取调用此函数的进程的进程号
  • pid_t类型在 unistd.h 中声明,本质上还是一个整数,用来表示进程号,只是在不同的操作系统上可能对应不同的整型,例如 int, long

上述代码的打印结果如下:

main process pid: 30039
fork returns 30040
parent process pid: 30039
fork returns 0
child process pid: 30040

父进程执行到fork()之后复制出一个子进程,并拿到子进程的进程号,然后跳过ifelse if块进入else块打印自己的进程号。同时子进程从fork()处开始执行,其返回值是 0,因此进入else if块,打印自己的进程号。

父进程在调用fork后克隆出一个子进程,然后两个进程“并行”执行,因此后面的打印顺序并不是确定的,有可能子进程先打印结果。

另一个例子是父进程如何克隆出两个子进程?先看这样一段代码

printf("main process pid: %d\n", getpid()); // print1
pid_t p1 = fork(); // fork1
printf("fork returns %d\n", p1);  // print2
if (p1 < 0)
    printf("error in fork!");
else if (p1 == 0)
    printf("child process pid: %d\n", getpid());  // print3
else
    printf("parent process pid: %d\n", getpid());  // print4

pid_t p2 = fork(); // fork2
printf("fork returns %d\n", p2);  // print5
if (p2 < 0)
    printf("error in fork!");
else if (p2 == 0)
    printf("child process pid: %d\n", getpid());  // print6
else
    printf("parent process pid: %d\n", getpid());  // print7

return 0;

打印结果如下:

main process pid: 31329    # main.print1
fork returns 31330         # main.print2
parent process pid: 31329  # main.print4
fork returns 31331         # main.print5
parent process pid: 31329  # main.print7
fork returns 0             # p1.print2
child process pid: 31330   # p1.print3
fork returns 0             # p2.print5
child process pid: 31331   # p2.print6
fork returns 31332         # p1.print5
parent process pid: 31330  # p1.print7
fork returns 0             # p1_p2.print5
child process pid: 31332   # p1_p2.print6

调用关系如下:

image-20220712170529564

可以看到,最后生成了四个进程(一个父进程,两个直接子进程,一个子进程的子进程),而我们期望的只有三个(一个父进程,两个直接子进程)。

下面灰色的 p1_p2 孙子进程显然不是我们向要的,为了避免这个进程生成,我们应该避免让程序第一个 fork 出的子进程不要执行第二个 fork,这样就能保证主进程每次调用fork生成的子进程都不会调用后面的fork生成自己的子进程:

printf("main process pid: %d\n", getpid());
pid_t p1 = fork();
printf("fork returns %d\n", p1);
if (p1 < 0)
    printf("error in fork!");
else if (p1 == 0)
    printf("child process pid: %d\n", getpid());
else
{
    printf("parent process pid: %d\n", getpid());
    pid_t p2 = fork();
    printf("fork returns %d\n", p2);
    if (p2 < 0)
        printf("error in fork!");
    else if (p2 == 0)
        printf("child process pid: %d\n", getpid());
    else
        printf("parent process pid: %d\n", getpid());
}

打印结果如下:

main process pid: 7185
fork returns 7186
parent process pid: 7185
fork returns 0
child process pid: 7186
fork returns 7187
parent process pid: 7185
fork returns 0
child process pid: 7187

可以看到,主进程 7185 只 fork 出了 7186 和 7187 两个子进程。

示例代码参考了 <>https://blog.csdn.net/wanghaobo920/article/details/8018552

0

评论区