3个子进程只需要2个wait()
3 child processes require only 2 wait()
我正在尝试实现执行 ls| grep "pipes"| wc -l
的代码。为此,我创建了 3 个子进程并使用了 2 个管道。请查找使用的代码:
#include <stdio.h>
#include <errno.h>
#include <unistd.h>
#include <sys/wait.h>
#include <stdlib.h>
int main(void)
{
//pid_t p;
int pfds1[2],pfds2[2],s;
pipe(pfds1);
pipe(pfds2);
if(!fork()) //first child ls
{
//printf("ls ppid is:%d\n", getppid());
dup2(pfds1[1],1);
close(pfds1[0]);
close(pfds2[0]);
close(pfds2[1]);
close(pfds1[1]);
if(execlp("ls", "ls", NULL)==-1)
{
perror("Error in exec line 24\n");
exit(1);
}
}
else{
if(!fork())
{
//printf("grep ppid is:%d\n", getppid());
dup2(pfds1[0],0);
dup2(pfds2[1],1);
close(pfds1[0]);
close(pfds2[0]);
close(pfds2[1]);
close(pfds1[1]);
if(execlp("grep","grep","pipes",NULL)==-1)
{
perror("Error in exec line 41\n");
exit(1);
}
}
else{
if(!fork())
{ //printf("wc ppid is:%d\n", getppid());
dup2(pfds2[0],0);
close(pfds1[0]);
close(pfds2[0]);
close(pfds2[1]);
close(pfds1[1]);
if(execlp("wc","wc","-l",NULL)==-1)
{
perror("Error in exec line 56\n");
exit(1);
}
}
else{
close(pfds1[0]);
close(pfds2[0]);
close(pfds1[1]);
close(pfds1[1]);
wait(&s);
wait(&s);
wait(&s);
//printf("parent pid is:%d\n", getpid());
//printf("grandparent pid is:%d\n", getppid());
exit(0);
}
}
}
}
当只使用 2 个 wait(&s)
而不是 3 个时,即使有三个子进程,代码也能正常工作。当前代码卡住,没有完成执行。有人可以详细说明为什么会这样吗?
谢谢
首先,您的代码中有一个拼写错误,导致最后一个进程中管道的一端保持打开状态,从而导致阻塞发生。见下文。
问题是,当你 fork 你的两个文件描述符时(在调用 exec 之前,你正在关闭一个未使用的文件描述符,在 child 中)转换成四个(两个在 child、还有 parent 中的两个,并且您 不要关闭 parent 进程中的其中一个文件描述符)
如果您不关闭不使用的描述符(在 parent 或 child 进程中),在某些情况下,管道的两个描述符仍将打开,当发生这种情况时,读取过程被阻塞,等待一些输入(或 EOF)的到来。当你创建一个管道时(通过使用 pipe(2)
系统调用,一旦你 fork()
,关闭你不打算使用的管道的文件描述符,因为你可以阻塞,因为一切都完成了,但您仍然等待输入(是 parent 或 child 过程,具体取决于您如何组织信息流动)
#include <stdio.h>
#include <errno.h>
#include <unistd.h>
#include <sys/wait.h>
#include <stdlib.h>
int main(void)
{
//pid_t p;
int pfds1[2],pfds2[2], s;
pipe(pfds1);
pipe(pfds2);
if(!fork()) { //first child ls
//printf("ls ppid is:%d\n", getppid());
dup2(pfds1[1],1);
在这里,您已经将管道的写入端安装为 ls 的标准输出,但您没有阅读它。
close(pfds1[0]);
close(pfds2[0]);
close(pfds2[1]);
close(pfds1[1]);
没关系。所有文件描述符都将在退出时关闭,因此无需显式关闭它们。
if(execlp("ls", "ls", NULL)==-1) {
perror("Error in exec line 24\n");
exit(1);
}
} else { // parent
if(!fork()) {
//printf("grep ppid is:%d\n", getppid());
dup2(pfds1[0],0);
dup2(pfds2[1],1);
此处将标准输入连接到 pipe1 的读取端,将 pipe2 的标准输出连接到标准输入。 grep 将等待该管道上的输入。
close(pfds1[0]);
close(pfds2[0]);
close(pfds2[1]);
close(pfds1[1]);
如前所述,当 grep 完成时,所有管道都将关闭...因此无需在此处明确关闭它们。
if(execlp("grep","grep","pipes",NULL)==-1) {
perror("Error in exec line 41\n");
exit(1);
}
} else { // parent
if(!fork()) {
//printf("wc ppid is:%d\n", getppid());
dup2(pfds2[0],0);
您还将 pipe2 的输入边缘连接到标准输入,因此 wc -l
将等待您在 pipe2 上的输入。
close(pfds1[0]);
close(pfds2[0]);
close(pfds2[1]);
close(pfds1[1]);
一如既往,如果您不关闭也没问题,因为此过程将在完成时关闭所有这些。
if(execlp("wc","wc","-l",NULL)==-1) {
perror("Error in exec line 56\n");
exit(1);
}
} else{ // parent
close(pfds1[0]);
close(pfds2[0]);
close(pfds1[1]);
close(pfds1[1]); /* mistake!!!! */
你上面有一个错字,你关闭了两次描述符 pdfs1[1]
并且 pdfs2[1]
保持打开状态,所以最后一个进程仍在等待管道上的更多输入,并且永远不会结束。
wait(&s);
wait(&s);
wait(&s);
//printf("parent pid is:%d\n", getpid());
//printf("grandparent pid is:%d\n", getppid());
exit(0);
}
}
}
}
编辑
管道要求关闭属于写入端的所有描述符,以便通知 readers 文件结束已经发生,因此解锁进程 read()
ing在他们。在 fork 中,管道的描述符被隐式 dup()
ed,当您通过显式循环重定向输入或输出时,您再次 dup()
它们。 children 进程只需要关闭它们不打算使用的描述符(你做得很好,当你执行 dup() 重定向时,然后关闭所有管道描述符)
在你的情况下,你使用管道只是为了连接 children,并且没有从 parent...向 reader 发出信号。要从使用 EOF 的读取中解锁 reader,您需要关闭所有复制的写入描述符。这包括parent和其他children.
我正在尝试实现执行 ls| grep "pipes"| wc -l
的代码。为此,我创建了 3 个子进程并使用了 2 个管道。请查找使用的代码:
#include <stdio.h>
#include <errno.h>
#include <unistd.h>
#include <sys/wait.h>
#include <stdlib.h>
int main(void)
{
//pid_t p;
int pfds1[2],pfds2[2],s;
pipe(pfds1);
pipe(pfds2);
if(!fork()) //first child ls
{
//printf("ls ppid is:%d\n", getppid());
dup2(pfds1[1],1);
close(pfds1[0]);
close(pfds2[0]);
close(pfds2[1]);
close(pfds1[1]);
if(execlp("ls", "ls", NULL)==-1)
{
perror("Error in exec line 24\n");
exit(1);
}
}
else{
if(!fork())
{
//printf("grep ppid is:%d\n", getppid());
dup2(pfds1[0],0);
dup2(pfds2[1],1);
close(pfds1[0]);
close(pfds2[0]);
close(pfds2[1]);
close(pfds1[1]);
if(execlp("grep","grep","pipes",NULL)==-1)
{
perror("Error in exec line 41\n");
exit(1);
}
}
else{
if(!fork())
{ //printf("wc ppid is:%d\n", getppid());
dup2(pfds2[0],0);
close(pfds1[0]);
close(pfds2[0]);
close(pfds2[1]);
close(pfds1[1]);
if(execlp("wc","wc","-l",NULL)==-1)
{
perror("Error in exec line 56\n");
exit(1);
}
}
else{
close(pfds1[0]);
close(pfds2[0]);
close(pfds1[1]);
close(pfds1[1]);
wait(&s);
wait(&s);
wait(&s);
//printf("parent pid is:%d\n", getpid());
//printf("grandparent pid is:%d\n", getppid());
exit(0);
}
}
}
}
当只使用 2 个 wait(&s)
而不是 3 个时,即使有三个子进程,代码也能正常工作。当前代码卡住,没有完成执行。有人可以详细说明为什么会这样吗?
谢谢
首先,您的代码中有一个拼写错误,导致最后一个进程中管道的一端保持打开状态,从而导致阻塞发生。见下文。
问题是,当你 fork 你的两个文件描述符时(在调用 exec 之前,你正在关闭一个未使用的文件描述符,在 child 中)转换成四个(两个在 child、还有 parent 中的两个,并且您 不要关闭 parent 进程中的其中一个文件描述符)
如果您不关闭不使用的描述符(在 parent 或 child 进程中),在某些情况下,管道的两个描述符仍将打开,当发生这种情况时,读取过程被阻塞,等待一些输入(或 EOF)的到来。当你创建一个管道时(通过使用 pipe(2)
系统调用,一旦你 fork()
,关闭你不打算使用的管道的文件描述符,因为你可以阻塞,因为一切都完成了,但您仍然等待输入(是 parent 或 child 过程,具体取决于您如何组织信息流动)
#include <stdio.h>
#include <errno.h>
#include <unistd.h>
#include <sys/wait.h>
#include <stdlib.h>
int main(void)
{
//pid_t p;
int pfds1[2],pfds2[2], s;
pipe(pfds1);
pipe(pfds2);
if(!fork()) { //first child ls
//printf("ls ppid is:%d\n", getppid());
dup2(pfds1[1],1);
在这里,您已经将管道的写入端安装为 ls 的标准输出,但您没有阅读它。
close(pfds1[0]);
close(pfds2[0]);
close(pfds2[1]);
close(pfds1[1]);
没关系。所有文件描述符都将在退出时关闭,因此无需显式关闭它们。
if(execlp("ls", "ls", NULL)==-1) {
perror("Error in exec line 24\n");
exit(1);
}
} else { // parent
if(!fork()) {
//printf("grep ppid is:%d\n", getppid());
dup2(pfds1[0],0);
dup2(pfds2[1],1);
此处将标准输入连接到 pipe1 的读取端,将 pipe2 的标准输出连接到标准输入。 grep 将等待该管道上的输入。
close(pfds1[0]);
close(pfds2[0]);
close(pfds2[1]);
close(pfds1[1]);
如前所述,当 grep 完成时,所有管道都将关闭...因此无需在此处明确关闭它们。
if(execlp("grep","grep","pipes",NULL)==-1) {
perror("Error in exec line 41\n");
exit(1);
}
} else { // parent
if(!fork()) {
//printf("wc ppid is:%d\n", getppid());
dup2(pfds2[0],0);
您还将 pipe2 的输入边缘连接到标准输入,因此 wc -l
将等待您在 pipe2 上的输入。
close(pfds1[0]);
close(pfds2[0]);
close(pfds2[1]);
close(pfds1[1]);
一如既往,如果您不关闭也没问题,因为此过程将在完成时关闭所有这些。
if(execlp("wc","wc","-l",NULL)==-1) {
perror("Error in exec line 56\n");
exit(1);
}
} else{ // parent
close(pfds1[0]);
close(pfds2[0]);
close(pfds1[1]);
close(pfds1[1]); /* mistake!!!! */
你上面有一个错字,你关闭了两次描述符 pdfs1[1]
并且 pdfs2[1]
保持打开状态,所以最后一个进程仍在等待管道上的更多输入,并且永远不会结束。
wait(&s);
wait(&s);
wait(&s);
//printf("parent pid is:%d\n", getpid());
//printf("grandparent pid is:%d\n", getppid());
exit(0);
}
}
}
}
编辑
管道要求关闭属于写入端的所有描述符,以便通知 readers 文件结束已经发生,因此解锁进程 read()
ing在他们。在 fork 中,管道的描述符被隐式 dup()
ed,当您通过显式循环重定向输入或输出时,您再次 dup()
它们。 children 进程只需要关闭它们不打算使用的描述符(你做得很好,当你执行 dup() 重定向时,然后关闭所有管道描述符)
在你的情况下,你使用管道只是为了连接 children,并且没有从 parent...向 reader 发出信号。要从使用 EOF 的读取中解锁 reader,您需要关闭所有复制的写入描述符。这包括parent和其他children.