在 C 中使用带有命令行参数的 fork() 的并行进程

Parellel processes using fork() with command line parameters in C

我正在尝试创建一个程序,该程序在命令行中接收许多要执行的计数并并行执行它们。

我有一个 count.c 文件用于计数:

int main(int argc, char** argv)
{
   assert(argc>1);

   int length= atoi(argv[1]); 
   assert(length>0);

   int pid = getpid(); 

   printf("%d : %s\n", pid, "start");

    for (unsigned int i=length; i>0; i--)
    {
      printf("%d : %d\n", pid, i);

      sleep(1); 
    }

    printf("%d : %s\n", pid, "done");


    return 0;
}

所以如果我在 bash 中输入“./count 5”,程序从 5 数到 1。

我还有另一个 multiple.c 文件:

int main(int argc, char** argv)
{
   assert(argc>2);
   unsigned int i;

   char * nameExec = (char*) malloc(sizeof(char)*(strlen(argv[1])-1));

   char * time;

   int number = argc-2; // number of counting to do

    // name of programm
    for (i=2; i<strlen(argv[1]); i++)
    {
      nameExec[i-2]=argv[1][i];
    }
    nameExec[i-2]='[=11=]';


    if(number==1) //  one counting there is no fork needed
    {
      execl(argv[1], nameExec, argv[2], NULL);
    } else
    {
      for (unsigned int i=2; i<number+1; i++) // if we have 2 counts to do, then we need 1 fork, if there is 3 counts to do then there is 2 forks...
      {
        if(fork()==0) // child process
        {
          time = argv[i];
        } else
        {
          time = argv[i+1];
          wait(NULL); // father process waits for child
        }

      }

      execl(argv[1], nameExec, time, NULL);

    }

    return 0;
}

我想用这个程序做的是我在命令行中输入我输入例如“./multiple ./count 5 4 3”然后它开始3 个并行计数(3 个并行进程)。

我做过的测试: 如果我输入 ./multiple ./count 5 4 它会进行两次计数,一次从 5 开始,另一个从 4 开始,但不是同时进行,一个接一个 。 如果我输入 ./multiple ./count 5 4 3 它会进行 4 次计数,一次从 4 开始,然后一次从 3 开始,再一次从 4 开始,再一次从3.

我真的不理解这种行为, 据我了解,fork()用于复制进程,execl放弃当前进程并开始执行另一个。

请帮忙!

(此外,我正在尝试了解 fork() 和 execl() 的用法,因此我想找到使用这两个函数来解决我的问题的方法)。

警告:这已损坏,但会给您思路...

注意:没有 child 会执行 second 循环,因为 execl [由 child 执行] 通常永远不会 return [除非错误]

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

    --argc;
    ++argv;

    assert(argc >= 2);

    char *nameExec = *argv++;
    --argc;

    for (;  argc > 0;  --argc, ++argv) {
        char *time = *argv;
        pid_t pid = fork();
        if (pid == 0)
            execl(nameExec,time,NULL);
    }

    while (wait(NULL) >= 0);

    return 0;
}

您的原始代码 运行 是 child 顺序处理而不是并发处理,因为您在循环内调用了 wait()

您不需要复制程序名称。您可以直接使用 argv[1](或简单地将其分配给 nameExec)或使用 nameExec = &argv[1][2];.

跳过前几个字符

理解代码中循环的操作非常棘手;当我试图将我的大脑包裹在它周围时,它让我尖叫了几次。我将简单地从头开始编写代码 — 有两种变体。

变体 1

更容易理解的变体是 parent(初始)进程为每个计数器启动一个 child,然后等待直到没有 children。它报告 children 退出时的 PID 和退出状态;在不打印 'in memoriam'.

的情况下简单地收集尸体是可行的
/* SO 6021-0236 */
/* Variant 1: Original process forks children and waits for them to complete */
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <unistd.h>

int main(int argc, char **argv)
{
    assert(argc > 2);

    /* Launch children */
    for (int i = 2; i < argc; i++)
    {
        if (fork() == 0)     // child process
        {
            execl(argv[1], argv[1], argv[i], (char *)0);
            fprintf(stderr, "failed to execute %s\n", argv[1]);
            exit(EXIT_FAILURE);
        }
    }

    /* Wait for children */
    int corpse;
    int status;
    while ((corpse = wait(&status)) > 0)
    {
        printf("%d: PID %d exited with status 0x%.4X\n",
               (int)getpid(), corpse, status);
    }

    return 0;
}

我重命名了你的计数器程序,所以源文件是 counter23.c,程序是 counter23,唯一的其他重要变化是删除了 [=22] 中冒号前的 space =] 输出.

我调用了上面的源码multiple43.c,编译成multiple43.

$ multiple43 count23 1
54251: start
54251: 1
54251: done
54250: PID 54251 exited with status 0x0000
$ multiple43 count23 3 4 5
54261: start
54261: 5
54260: start
54260: 4
54259: start
54259: 3
54261: 4
54260: 3
54259: 2
54261: 3
54260: 2
54259: 1
54261: 2
54260: 1
54259: done
54258: PID 54259 exited with status 0x0000
54261: 1
54260: done
54258: PID 54260 exited with status 0x0000
54261: done
54258: PID 54261 exited with status 0x0000
$

在三个 children 的 运行 中,可以看到三个都在同时产生输出。

这是我认为您应该使用的变体,除非有明确要求做其他事情。

变体 2

另一个变体或多或少地近似于您的代码(尽管近似不是很好),因为原始进程本身也执行计数器程序。因此,如果原始进程的周期少于其他进程,它会在其他进程完成之前终止(参见 3 4 55 4 3 示例之间的区别)。不过,它会同时 运行 计数器。

/* SO 6021-0236 */
/* Variant 2: Original process launches children, the execs itself */
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <unistd.h>

int main(int argc, char **argv)
{
    assert(argc > 2);

    /* Launch children */
    for (int i = 3; i < argc; i++)
    {
        if (fork() == 0)     // child process
        {
            execl(argv[1], argv[1], argv[i], (char *)0);
            fprintf(stderr, "failed to execute %s\n", argv[1]);
            exit(EXIT_FAILURE);
        }
    }

    execl(argv[1], argv[1], argv[2], (char *)0);
    fprintf(stderr, "failed to execute %s\n", argv[1]);
    return(EXIT_FAILURE);
}

此代码被 multiple53.c 编译为 multiple53

$ multiple53 count23 3 4 5
54269: start
54268: start
54267: start
54269: 5
54268: 4
54267: 3
54269: 4
54268: 3
54267: 2
54268: 2
54267: 1
54269: 3
54268: 1
54267: done
54269: 2
$ 54268: done
54269: 1
54269: done

$ multiple53 count23 5 4 3
54270: start
54272: start
54270: 5
54272: 3
54271: start
54271: 4
54270: 4
54272: 2
54271: 3
54272: 1
54270: 3
54271: 2
54271: 1
54272: done
54270: 2
54270: 1
54271: done
54270: done
$

空行出现是因为我点击了 return — 提示出现在 3 行之前,但随后是 54268 和 54269 的更多输出。我认为这不太可能是我们想要的。

检测变体 0

为了尝试理解原始代码,我在做了一些小改动后对其进行了检测(保存在 multiple31.c 中并编译为 multiple31):

/* SO 6021-0236 */
/* Original algorithm with instrumentation */
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/wait.h>
#include <unistd.h>

int main(int argc, char **argv)
{
    assert(argc > 2);
    char *nameExec = argv[1];
    char *time;
    int number = argc - 2;

    if (number == 1)
    {
        printf("%d: name = %s; time = %s\n", (int)getpid(), nameExec, argv[2]);
        execl(argv[1], nameExec, argv[2], NULL);
    }
    else
    {
        for (int i = 2; i <= number; i++)  // Idempotent change in condition
        {
            printf("%d: i = %d; number = %d\n", (int)getpid(), i, number);
            pid_t kid = fork();
            if (kid == 0)
            {
                time = argv[i];
                printf("%d: i = %d; time = %s; ppid = %d\n",
                       (int)getpid(), i, time, (int)getppid());
            }
            else
            {
                time = argv[i + 1];
                printf("%d: i = %d; time = %s; waiting for %d\n",
                       (int)getpid(), i, time, (int)kid);
                int status;
                int corpse = wait(&status);
                printf("%d: i = %d; time = %s; PID %d exited with status 0x%.4X\n",
                       (int)getpid(), i, time, corpse, status);
            }
        }
        printf("%d: name = %s; time = %s\n", (int)getpid(), nameExec, time);
        execl(argv[1], nameExec, time, NULL);
    }

    printf("%d: this should not be reached!\n", (int)getpid());
    return 0;
}

当运行有4次时,输出如:

$ multiple31 count23 5 4 3 2
54575: i = 2; number = 4
54575: i = 2; time = 4; waiting for 54576
54576: i = 2; time = 5; ppid = 54575
54576: i = 3; number = 4
54576: i = 3; time = 3; waiting for 54577
54577: i = 3; time = 4; ppid = 54576
54577: i = 4; number = 4
54577: i = 4; time = 2; waiting for 54578
54578: i = 4; time = 3; ppid = 54577
54578: name = count23; time = 3
54578: start
54578: 3
54578: 2
54578: 1
54578: done
54577: i = 4; time = 2; PID 54578 exited with status 0x0000
54577: name = count23; time = 2
54577: start
54577: 2
54577: 1
54577: done
54576: i = 3; time = 3; PID 54577 exited with status 0x0000
54576: i = 4; number = 4
54576: i = 4; time = 2; waiting for 54579
54579: i = 4; time = 3; ppid = 54576
54579: name = count23; time = 3
54579: start
54579: 3
54579: 2
54579: 1
54579: done
54576: i = 4; time = 2; PID 54579 exited with status 0x0000
54576: name = count23; time = 2
54576: start
54576: 2
54576: 1
54576: done
54575: i = 2; time = 4; PID 54576 exited with status 0x0000
54575: i = 3; number = 4
54575: i = 3; time = 3; waiting for 54580
54580: i = 3; time = 4; ppid = 54575
54580: i = 4; number = 4
54580: i = 4; time = 2; waiting for 54581
54581: i = 4; time = 3; ppid = 54580
54581: name = count23; time = 3
54581: start
54581: 3
54581: 2
54581: 1
54581: done
54580: i = 4; time = 2; PID 54581 exited with status 0x0000
54580: name = count23; time = 2
54580: start
54580: 2
54580: 1
54580: done
54575: i = 3; time = 3; PID 54580 exited with status 0x0000
54575: i = 4; number = 4
54575: i = 4; time = 2; waiting for 54582
54582: i = 4; time = 3; ppid = 54575
54582: name = count23; time = 3
54582: start
54582: 3
54582: 2
54582: 1
54582: done
54575: i = 4; time = 2; PID 54582 exited with status 0x0000
54575: name = count23; time = 2
54575: start
54575: 2
54575: 1
54575: done
$

追查为什么那是可怕的输出。我开始写一个解释,但我发现我的解释与实际输出不匹配——又一次。然而,沿着所示线路的仪器是我通常理解发生了什么的方式。其中一个关键点(稍微简化)是,除了正在倒计时的 child 之外,其他所有东西都在等待 child 结束。 运行 测试 1、2 或 3 次而不是 4 次与此一致,但更简单(同时减少混淆和增加混淆)。使用 5 倍增加了输出量,但并没有真正提供更多的启发。

首先,如果您分析 multiple.c 中的代码,您会发现它仅由一个主要的 if(){...}else{...} 组成,其中,在 then 端,您使一个 exec if argc == 2 (这是正确的,如果你只是想保存一个 fork() 调用,但会使事情复杂化并使你犯错误)并且在 else 部分,你只有一次 execl() 调用,在 for 循环完全执行后。

  • 首先,您永远不会在循环中调用 wait() 之前执行 fork(),因此所有调用 return -1 都会出现 ENOCHLD.
  • 你必须将 fork()s 和 execl()s 放在 for 循环中,这样每个参数都会产生一个全新的进程。
  • 你在参数列表中做了很多额外的工作来处理它。最好将程序指定为 运行 作为一个选项(-r 可能是一个很好的选项)并使用默认程序以防万一您不指定程序。使用 getopt(3) 将是一个不错的选择,将使您的程序更 unix 专业。

下面是我所说的例子:

count.c

/* YOU NEED TO POST COMPLETE, COMPILABLE CODE, DON'T TAKE OFF
 * YOUR INCLUDE FILES TO POST, AS SOME ERRORS CAN COME FROM THE
 * FACT THAT YOU HAVE FORGOTTEN TO #include THEM.  */
#include <assert.h> /* for assert */
#include <stdio.h>  /* for printf and stdio functions */
#include <stdlib.h> /* for atoi */
#include <unistd.h> /* for getpid() */

int main(int argc, char** argv)
{
   assert(argc == 2);

   int length= atoi(argv[1]); 
   assert(length>0);

   int pid = getpid(); 

   printf("%d : %s\n", pid, "start");

   for (unsigned int i=length; i>0; i--)
   {
      printf("%d : %d\n", pid, i);

      sleep(1); 
   }

   printf("%d : %s\n", pid, "done");

   return 0;
}

multiple.c

/* SAME AS BEFORE :) */

#include <errno.h> /* for errno */
#include <stdio.h> /* for printf() and others */
#include <stdlib.h> /* for many standard functions */
#include <string.h> /* for strerror() */
#include <sys/wait.h> /* for wait() */
#include <unistd.h> /* for getpid() */
#include <getopt.h> /* for getopt() */

pid_t pid;

/* create the format string for printf with a pretty header,
 * showing pid, source file, source line, source function,
 * and the provided format string. */
#define F(_fmt) "[pid=%d]:"__FILE__":%d:%s: "_fmt,pid,__LINE__,__func__

/* fatal error macro */
#define ERROR(str) do {\
                fprintf(stderr, F("%s: %s\n"),\
                        (str), strerror(errno));\
                exit(EXIT_FAILURE);\
        } while(0)

int main(int argc, char** argv)
{
        char *program = "./a.out";  /* defaults to a.out in local dir */
        int opt;

        /* getopt use.  See getopt(3) for instructions */
        while ((opt = getopt(argc, argv, "r:")) != EOF) {
                switch (opt) {
                case 'r': program = optarg; break;
                }
        }

        /* shift all the processed parameters out */
        argc -= optind; argv += optind;

        /* get our pid to use in traces */
        pid = getpid();

        if (argc == 1) {
                /* only one parameter, save the fork() call */
                printf(F("about to exec: %s %s\n"),
                        program, argv[0]);
                execlp(program,
                        program, argv[0], NULL);
                ERROR("execlp()");
                /* NOTREACHED */
        } else {
                pid_t chld;
                int i;
                for (i = 0; i < argc; i++) {
                        chld = fork();
                        if (chld < 0) {
                                ERROR("fork()");
                                /* NOTREACHED */
                        }
                        if (!chld) { /* child */
                                printf(F("about to call: %s %s\n"),
                                        program, argv[i]);
                                execlp(program,
                                        program, argv[i], NULL);
                                ERROR("execlp()");
                                /* NOTREACHED */
                        }
                }
                /* NOW, AFTER ALL FORKS/EXECS HAVE BEEN DONE,
                 * JUST DO ALL THE WAITS.  wait() gives an
                 * error if no children exist to wait for, so
                 * we wait() until we get an error here.  */
                int status;
                while ((chld = wait(&status)) > 0) {
                        /* just change this printf to a continue;
                         * if you don't want to print the result. */
                        printf(F("wait() => child %d (status = %d)\n"),
                                chld, status);
                }
        }
        printf("%d: END OF PROGRAM\n", pid);
}

最后一个运行:

$ multiple -r count 3 5 2
[pid=78790]:multiple.c:61:main: about to call: count 3
[pid=78790]:multiple.c:61:main: about to call: count 2
78791 : start
78791 : 3
[pid=78790]:multiple.c:61:main: about to call: count 5
78793 : start
78793 : 2
78792 : start
78792 : 5
78791 : 2
78792 : 4
78793 : 1
78791 : 1
78792 : 3
78793 : done
[pid=78790]:multiple.c:77:main: wait() => child 78793 (status = 0)
78791 : done
78792 : 2
[pid=78790]:multiple.c:77:main: wait() => child 78791 (status = 0)
78792 : 1
78792 : done
[pid=78790]:multiple.c:77:main: wait() => child 78792 (status = 0)
78790: END OF PROGRAM
$ _

以上代码可供下载here