在 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 5
和 5 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
我正在尝试创建一个程序,该程序在命令行中接收许多要执行的计数并并行执行它们。
我有一个 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 5
和 5 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