等待命令不会等待子进程完成(C/C++)

4
我将尝试编写一个C++程序,创建一个子进程,运行一个命令并将输出管道返回到父进程正在运行的命令的输入。
我已经让父进程执行了wait(NULL)或wait((void*)pid)命令,但它并没有等待。
以下是代码:
#include <string.h>
#include <fstream>
#include <iostream>
#include <unistd.h>
#include <stdio.h>
#include <sys/wait.h>
using namespace std;

int main(int argc, char * argv[])
{    
        char* commands[strlen(argv[1])];
        char *command = NULL;
        command = strtok(argv[1],"|");
        int i = 0;
        while(command != NULL)
        {
                commands[i] = command;
                i++;
                command = strtok(NULL,"|");
        }

        int numberOfCommands = i;

        pid_t pid;
        int pfd[2];
        char* prgname = NULL;
        if(pipe(pfd) == -1)
        {
                perror("error on pipe call");
                return(1);
        }

        for(int j = 0;j<numberOfCommands;j++)
        {
                cout<<commands[j]<<endl;
        }

        pid = fork();
        if(pid == 0){//child process
                printf("Child: My PID = %d\n", getpid());
                printf("Child: Running...\n");
                close(pfd[0]); //close read end of pipe
                dup2(pfd[1],1);//connect the pipes
                close(pfd[1]);//close extra file descriptors
                prgname = commands[0];//first command
                cout<<"child starting command: "<<prgname<<endl;
                execlp(prgname, prgname, 0);//Load the program
                **printf("Child: Done sleeping, returning.\n");**
        }
        else
        {
                printf("Parent: My PID = %d\n", getpid());
                **wait((void*)pid); //also tried wait(NULL); same effect
                printf("Parent: Running...\n");**
                close(pfd[1]); //close the write end of the pipe
                dup2(pfd[0],0);//connect the pipes
                close(pfd[0]); //close extra file descriptor
                prgname = commands[1];//now run the second command
                cout<<"parent starting command: "<<prgname<<endl;
                execlp(prgname, prgname, 0);//Load the programm
        }
        cout<<"all done"<<endl;
        return 0;
}

请注意加粗的行。我期望父进程在wait()命令处等待,子进程会打印出“Child done sleeping...”,然后结束,然后父进程会打印出“Parent: running...”。

我做错了什么!

谢谢!

更新:程序的完整输出如下:

dmegs
more
Child: My PID = 30070
Child: Running...
Parent: My PID = 30066
Parent: Running...
parent starting command: more
child starting command: dmegs
Child: Done sleeping, returning.
all done

你能否从你的代码片段中删除所有多余的杂物?(例如注释掉的代码,命令行参数等) - Oliver Charlesworth
抱歉,我正准备这样做。 - kralco626
这个程序相关的内容应该怎么翻译?是不是需要再简化一下? - kralco626
更好!虽然我想象中所有的命令行处理都与你的问题无关;你可以为更简单的测试案例硬编码变量。 - Oliver Charlesworth
4个回答

4
我看到了四个问题:
1) execlp()执行失败:execlp()(或任何exec系列函数)成功时会完全替换当前正在运行的进程映像-除非出现错误,否则不会返回。 但是你看到了“Child: Done sleeping, returning”消息,所以它肯定没有成功。(在你的例子中,我猜测这可能是因为dmegs应该是dmesg。)
2) printf()和cout输出缓冲意味着无法保证您按其发生顺序获得输出。如果您想通过打印输出来调试此问题,则最好将其打印到stderr(例如,使用fprintf(stderr,...)),后者(默认情况下)未缓冲。
3) 如其他人所指出的,wait((void*)pid)是错误的。等待(NULL)或waitpid(pid,NULL,0)。
4) 是否存在此问题取决于平台,但是…传递给execlp()的终止空指针参数应明确编写为(char *)0,而不仅仅是0,以确保将其作为指针而不是整数传递。通常,在C中,指针上下文中的0定义为null指针,但是当将参数传递给具有可变数量参数的函数时,编译器没有足够的信息来知道您尝试在指针上下文中使用它,因此将其作为整数传递,除非您明确进行转换。这可能会在指针和整数不是相同大小的平台上给您带来麻烦。
因此,我认为wait()正在工作,子进程实际上没有运行您想要的命令,并且由于缓冲而导致父级和子级的输出混合在一起。
以下是您的代码的稍微修改版本,它不使用任何C++,削减了命令处理部分,并将sleep 5的输出传输到cat(这没有什么意义,因为sleep不会产生任何输出,但延迟有助于查看情况):
#include <string.h>
#include <unistd.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/wait.h>

int main(void)
{    
        pid_t pid;
        int pfd[2];
        if(pipe(pfd) == -1)
        {
                perror("error on pipe call");
                return(1);
        }

        pid = fork();
        if(pid == 0){//child process
                fprintf(stderr, "Child: My PID = %d\n", getpid());
                fprintf(stderr, "Child: Running...\n");
                close(pfd[0]); //close read end of pipe
                dup2(pfd[1],1);//connect the pipes
                close(pfd[1]);//close extra file descriptors
                fprintf(stderr, "child starting command: sleep 5\n");
                execlp("sleep", "sleep", "5", (char *)0);//Load the program
                fprintf(stderr, "child: execlp failed\n");
        }
        else
        {
                fprintf(stderr,"Parent: My PID = %d\n", getpid());
                wait(NULL);
                fprintf(stderr,"Parent: Running...\n");
                close(pfd[1]); //close the write end of the pipe
                dup2(pfd[0],0);//connect the pipes
                close(pfd[0]); //close extra file descriptor
                fprintf(stderr,"parent starting command: cat\n");
                execlp("cat", "cat", (char *)0);//Load the programm
        }
        fprintf(stderr,"all done\n");
        return 0;
}

输出:

$ gcc -Wall -o wait wait.c
$ ./wait
Child: My PID = 27846
Child: Running...
child starting command: sleep 5
Parent: My PID = 27845

(这里有5秒的延迟)

Parent: Running...
parent starting command: cat
$

太好了!原来我只是拼错了命令的名称。感谢你注意到execlp()失败的事实。我从来没有意识到过!此外,你让我注意到我将程序参数传递给execlp()的方式不正确。我得重新审查文档。真不敢相信我花了一个小时去翻转两个字符。谢谢! - kralco626

1
通常情况下,您会打开结果以允许出现错误的情况。
pid = fork();
switch( pid ) {
 case -1: // parent fail
 case 0: // child success
 default: // parent success
}

等待特定的子进程,您可能需要使用

waitpid( pid, NULL, 0 );

或者等待任何子进程

pid_t child = waitpid( -1, NULL, 0 );

0

你为什么这样做?

wait((void*)pid)

wait函数接受指向状态的指针

   #include <sys/types.h>
   #include <sys/wait.h>

   pid_t wait(int *status);

你几乎肯定传递了不可写的地址。测试等待返回代码,我敢打赌它会大声抱怨;

此外,混合使用printf和cout可能会让自己感到困惑,它们的缓冲/刷新方案可能不同。


我添加了缺失的include,并将wait((void*)pid)更改为pid_t wait(int *status); 我得到了完全相同的输出。同时将所有的printf更改为cout。你有什么想法为什么它没有起作用? - kralco626
你检查了wait的返回值吗?你看了状态值吗? - pm100
好的。现在我真的放弃了。我将运行命令硬编码到程序中,而不是使用数组命令[]。它可以工作...完全没有等待!现在我很困惑。我为子进程硬编码了“dmesg”,为父进程硬编码了“more”,它可以工作。但是为什么?为什么父进程不必等待子进程!为什么当我要求它等待时它却不等待?天哪...太困惑了... - kralco626 0秒前 - kralco626
听起来就像孩子瞬间死亡一样 - 这就是为什么等待不等待的原因。 - pm100

0
wait((void*)pid);

你不应该将东西强制转换为void*,只是为了让编译器停止抱怨。 :)

看起来你可能想要waitpid: http://linux.die.net/man/2/waitpid

更新:

你需要检查execlp调用是否真的起作用了。比较一下:

$ ./a.out "dmegs|more"
dmegs
more
Parent: My PID = 20806
Child: My PID = 20807
Child: Running...
Parent: Running...
parent starting command: more
child starting command: dmegs
Child: Done sleeping, returning.
all done

使用:

$ ./a.out "dmesg|more"
dmesg
more
Parent: My PID = 20876
Child: My PID = 20877
Child: Running...
^C

在第一种情况下,由于execlp找不到“dmegs”,子进程基本上立即退出。这解除了父进程的阻塞并允许其执行。

哈哈,是的我知道。通常我不会做那样的事情。我只是感到沮丧。我精通许多语言,如C#、Java、ASP.NET、HTML、JavaScript和T-SQL,但在Linux远程终端上使用C/C++编程并不是我的终身梦想... 我会尝试你的建议并回报结果。 - kralco626
尝试了 int status; waitpid(-1,&status,0); 但是收到了相同的结果!还有其他想法吗? - kralco626

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