Unix中fork()多个进程的问题

15

我有一个函数,它会分叉出N个子进程。但是似乎分叉的数量比指定的要多。你能告诉我哪里做错了吗? 谢谢

void forkChildren(int nChildren){
    int i;
    for(i = 1; i <= nChildren; i++){
        pid = fork();
        if(pid == 0)          
            printf("I'm a child: %d PID: %d\n",i, getpid());
    }

} 

在主函数中我调用:

forkChildren(5);

我期望得到以下输出:

I'm a child: 1 PID: 2990
I'm a child: 2 PID: 2991
I'm a child: 3 PID: 2992
I'm a child: 4 PID: 2993
I'm a child: 5 PID: 2994

但是我得到的结果是以下内容:

I'm a child: 1 PID: 2990
I'm a child: 2 PID: 2991
I'm a child: 3 PID: 2992
I'm a child: 4 PID: 2993
I'm a child: 5 PID: 2994
user@computer:~/directory/$ I'm a child: 2 PID: 2999
I'm a child: 3 PID: 3000
I'm a child: 3 PID: 3001
I'm a child: 4 PID: 3002
I'm a child: 5 PID: 3003
I'm a child: 5 PID: 3004
I'm a child: 4 PID: 3005
I'm a child: 5 PID: 3006
I'm a child: 4 PID: 3007
I'm a child: 5 PID: 3008
I'm a child: 3 PID: 3011
I'm a child: 4 PID: 3012
I'm a child: 4 PID: 3010
I'm a child: 5 PID: 3013
I'm a child: 5 PID: 3014
I'm a child: 5 PID: 3015
I'm a child: 4 PID: 3018
I'm a child: 5 PID: 3019
I'm a child: 5 PID: 3020
I'm a child: 5 PID: 3021
I'm a child: 5 PID: 3023
I'm a child: 5 PID: 3025
I'm a child: 5 PID: 3024
I'm a child: 4 PID: 3022
I'm a child: 5 PID: 3026
I'm a child: 5 PID: 3027

2
我现在明白了。我只需要在每个子进程打印完信息后加上exit(0);。 - user69514
4个回答

15

当你使用fork来创建一个新进程时,实际上你会得到两个(几乎)完全相同的进程副本,它们都会继续运行。

因此,子进程将在自己的进程空间中继续循环(在打印输出后),同时父进程也在执行循环。事实上,由于这些子进程也在进行分叉,孙子进程也会从那一点继续。我确信有一个公式可以计算出最终生成的子进程数量(可能是N!之类的东西),但我现在没有精力去算它。最好使用以下解决方法。

区分父进程和子进程的方法在于fork的返回值。

  • 如果返回值为-1,则意味着fork失败,您将在父进程中。
  • 如果返回值为0,则意味着您在子进程中。
  • 如果返回值为正数,则表示您在父进程中,并且该数字是子进程的 PID(因此您可以操作它或等待它)。

以下是一些测试代码:

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

void forkChildren (int nChildren) {
    int i;
    pid_t pid;
    for (i = 1; i <= nChildren; i++) {
        pid = fork();
        if (pid == -1) {
            /* error handling here, if needed */
            return;
        }
        if (pid == 0) {
            printf("I am a child: %d PID: %d\n",i, getpid());
            sleep (5);
            return;
        }
    }
}

int main (int argc, char *argv[]) {
    if (argc < 2) {
        forkChildren (2);
    } else {
        forkChildren (atoi (argv[1]));
    }
    return 0;
}

并输出一些内容以显示正在发生的事情:

pax> forktest 5
I am a child: 1 PID: 4188
I am a child: 2 PID: 4180
I am a child: 3 PID: 5396
I am a child: 4 PID: 4316
I am a child: 5 PID: 4260

pax> _

我正在帮助一个朋友完成一个操作系统作业,上述函数运行良好,谢谢! - Elias Kouskoumvekakis

15

fork()调用会生成一个新的进程,该进程从fork发生的完全相同的位置开始执行。因此,它看起来像是fork "返回两次"。

这里发生的情况是,您的fork()调用会返回两次,因此父进程和子进程都会继续循环并生成新的进程。然后,每个子进程(包括原始父进程和子进程的每个子进程)再次fork,重复加倍进程数量...


不完全是加倍。孩子们仍然在循环中递增i,因此第二代中的每个人只会创建四个而不是五个孙子。这是某种形式的阶乘。但是,除此之外小小的挑剔,答案很好。 - paxdiablo
@paxdiablo:子进程确实会增加i的值,但只是对它自己的本地副本进行操作。这不会对父进程的i产生任何影响。 - Aneesh Dogra

1
在这个练习中,我会使用递归而不是for循环。这样你就可以多次调用fork()指令,但只在进程的两个副本之一上调用。你可以让子进程生成另一个子进程,从而拥有祖父母、曾祖父母等等,或者你可以在父进程端调用fork(),拥有单一的“父亲”和多个孩子。以下是实现后一种解决方案的代码示例:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int nChildren;

void myFork(int n);

int main(int argc, char *argv[]) {

  // just a check on the number of arguments supplied
  if (argc < 2) {
    printf("Usage: forktest <number_of_children>\n");
    printf("Example: forktest 5 - spawns 5 children processes\n");
    return -1;
  }

  nChildren = atoi(argv[1]);
  // starting the recursion...
  myFork(nChildren-1);
  return 0;
}

// the recursive function
void myFork(int n) {
  pid_t pid;

  pid = fork();

  // the child does nothing but printing a message on screen
  if (pid == 0) {
    printf("I am a child: %d PID: %d\n", nChildren-n, getpid());
    return;
  }

  // if pid != 0, we're in the parent
  // let's print a message showing that the parent pid is always the same...
  printf("It's always me (PID %d) spawning a new child (PID %d)\n", getpid(), pid);
  // ...and wait for the child to terminate.
  wait(pid);

  // let's call ourself again, decreasing the counter, until it reaches 0.
  if (n > 0) {
    myFork(n-1);
  }
}

1
每个子进程都会接着循环进行。
换句话说,子进程1被生成后,会继续执行第二次循环等等。
当一个进程被分叉时,当前进程的一个副本被创建:生成的子进程在fork()调用之后继续执行。这就是为什么你必须在逻辑中注意返回代码的原因。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接