如果父级不调用 wait(),则同一父级的两个子级不使用管道进行通信
Posted
技术标签:
【中文标题】如果父级不调用 wait(),则同一父级的两个子级不使用管道进行通信【英文标题】:Two children of same parent are not communicating using pipe if parent do not call wait() 【发布时间】:2015-09-08 14:09:00 【问题描述】:请看下面的代码:
#include<stdio.h>
main()
int pid, fds[2], pid1;
char buf[200];
pipe(fds);
pid = fork();
if(pid==0)
close(fds[0]);
scanf("%s", &buf);
write(fds[1], buf, sizeof(buf)+1);
else
pid1 = fork();
if(pid1==0)
close(fds[1]);
read(fds[0], buf, sizeof(buf)+1);
printf("%s\n", buf);
else
Line1: wait();
如果我不注释掉 Line1,它工作正常。请看下面:
hduser@pc4:~/codes/c/os$ ./a.out
hello //*Entry from keyboard*
hello //Output
hduser@pc4:~/codes/c/os$
但是如果我注释掉Line1,两个子进程没有通信:
hduser@pc4:~/codes/c/os$ ./a.out
hduser@pc4:~/codes/c/os$
hi //*Entry from keyboard*
hi: command not found
hduser@pc4:~/codes/c/os$
这里我无法理解 wait() 的意义。
【问题讨论】:
read(fds[0], buf, sizeof(buf)+1);
是未定义的行为。它将 201 个字节读入一个 200 字节的数组中。
调试的第一步是测试每个系统调用以查看失败的原因。写入标准错误。我也会写进度报告。你应该用半现代的 C(至少 C99)编写;这需要<unistd.h>
和main()
的正确声明。
首先,'pid' 被定义为在 unistd.h
头文件中定义的 'pid_t',发布的代码丢失了。
这两行:scanf("%s", &buf); write(fds[1], buf, sizeof(buf)+1);
有几个问题: 1) 始终检查 scanf() 的返回值(不是参数值)以确保操作成功。 2) 使用 %s 输入/格式参数时,始终包含一个长度修饰符 (sizeof(buf)-1),这样用户就不会溢出缓冲区。 3) 要发送的字节数永远不会比缓冲区长(通常更短)建议:`write(fds[1], buf, strlen(buf)+1);'
发布的代码存在严重的逻辑问题。 fork()
函数可以返回 3 种值:发生错误时为 -1,在子进程中执行时为 0,在父进程中执行时为某个正数。发布的代码未能检查对 fork() 的调用的错误情况
【参考方案1】:
这里发生的是父进程在子进程完成之前完成执行。导致孩子无法使用终端。
让我们仔细看看这一切。
wait()
是做什么的?
wait() 系统调用暂停调用进程的执行,直到 它的一个孩子终止了。
你的程序是这样的
您的 main Process
派生了 2 个子进程。第一个写入管道,而另一个从管道读取。这一切都是在main process
继续执行时发生的。
当主进程执行它的代码时会发生什么?它终止。当它终止时,它放弃对终端的控制。这会导致孩子无法访问终端。
这就解释了为什么你会得到command not found
——你输入的不是你程序的stdin
,而是shell提示符本身。
您的代码也存在一些其他问题,
1) 在这部分代码中,
scanf("%s", &buf);
这是错误的。你很不幸,没有遇到分段错误。由于buf
已经是一个地址,这应该是
scanf("%s", buf);
2) 注意这一点,
read(fds[0], buf, sizeof(buf)+1);
正如 cmets 部分所指出的,这是未定义的行为。您正在尝试读取更多数据并将其存储在较小的内存空间中。这 应该是,
read(fds[0], buf, sizeof(buf));
3) 致电wait()
。你已经创建了两个子进程,你应该等待它们都完成,所以你应该调用wait()
两次。
【讨论】:
【参考方案2】:在修复了代码中的一些错误之后,我想出了一个半仪表化版本的程序,如下所示:
#include <unistd.h>
#include <stdio.h>
#include <string.h>
int main(void)
int pid, fds[2], pid1;
char buf[200];
pipe(fds);
pid = fork();
if (pid == 0)
close(fds[0]);
printf("Prompt: "); fflush(0);
if (scanf("%199s", buf) != 1)
fprintf(stderr, "scanf() failed\n");
else
write(fds[1], buf, strlen(buf) + 1);
else
pid1 = fork();
if (pid1 == 0)
close(fds[1]);
if (read(fds[0], buf, sizeof(buf)) > 0)
printf("%s\n", buf);
else
fprintf(stderr, "read() failed\n");
else
/*Line1: wait();*/
return 0;
在严格的选项下编译干净(Mac OS X 10.10.5 上的 GCC 5.1.0):
gcc -O3 -g -std=c11 -Wall -Wextra -Werror p11.c -o p11
当我运行它时,输出是:
$ ./p11
Prompt: scanf() failed
read() failed
$
问题很明确; scanf()
失败。问题:为什么?
wait()
版本需要一个额外的标头#include <sys/wait.h>
和正确的调用顺序。我使用了以下段落:
else
printf("Kids are %d and %d\n", pid, pid1);
int status;
int corpse = wait(&status);
printf("Parent gets PID %d status 0x%.4X\n", corpse, status);
编译运行后,现在的输出是:
$ ./p11
Kids are 20461 and 20462
Prompt: Albatross
Albatross
Parent gets PID 20461 status 0x0000
$
那么,问题就变成了:当父进程不等待时,子进程的标准输入是如何或为什么关闭的?造成严重破坏的是 Bash 进行了一些工作控制。
我再次升级了程序,使用int main(int argc, char **argv)
并测试该命令是否传递了任何参数:
else if (argc > 1 && argv != 0) // Avoid compilation warning for unused argv
printf("Kids are %d and %d\n", pid, pid1);
int status;
int corpse = wait(&status);
printf("Parent gets PID %d status 0x%.4X\n", corpse, status);
我有一个传家宝贝壳,它接近原始的伯恩贝壳。我在它下面运行了程序,它的行为和我预期的一样:
$ ./p11
Prompt: $ Albatross
Albatross
$ ./p11 1
Kids are 20483 and 20484
Prompt: Albatross
Albatross
Parent gets PID 20483 status 0x0000
$
注意第一次运行时Prompt:
后面的$
;那是 shell 提示符,但是当我输入 Albatross
时,它(幸运的是)被 p11
进程的子进程读取。这不能保证;它可能是读取输入的外壳。在第二次运行中,我们看到了父母的输出,然后是孩子在工作,然后是父母退出消息。
因此,在经典 shell 下,您的代码将按预期工作。 Bash 以某种方式干扰了子进程的正常运行。 Korn shell 的行为类似于 Bash。 C shell (tcsh
) 也是如此。尝试dash
,我得到了有趣的行为(3 次运行):
$ ./p11
Prompt: $ Albatross
scanf() failed
read() failed
dash: 2: Albatross: not found
$ ./p11
Prompt: $ Albatross
scanf() failed
dash: 4: Albatross: not found
$ read() failed
$ ./p11
Prompt: scanf() failed
$ read() failed
$
请注意,前两次运行显示dash
正在读取输入,但孩子们直到我在输入信天翁后按回车后才发现问题。上次,孩子们在我打字之前就发现了问题。
而且,回到 Bash,重定向标准输入“正常”工作:
$ ./p11 <<< Albatross
Prompt: Albatross
$ ./p11 1 <<< Albatross
Kids are 20555 and 20556
Prompt: Albatross
Parent gets PID 20555 status 0x0000
$
Albatross
的输出当然来自第二个孩子。
答案将潜伏在作业控制外壳的某个行为中,但这足以让我想回到之前的生活。
【讨论】:
以上是关于如果父级不调用 wait(),则同一父级的两个子级不使用管道进行通信的主要内容,如果未能解决你的问题,请参考以下文章
C++ 虚拟方法:我必须在父类中为子级和父级不共享的每个方法创建一个虚拟方法吗?
Java - 为啥另一个包中的子级无法通过父级引用访问父级的受保护方法?