这段 C 代码有什么问题? child 不返回?

What's wrong with this C code? The child is not returning?

这是我试图了解如何在两个 child 进程之间进行正确管道传输的尝试。我只是想将一个 Linux 命令的输出传递给另一个命令(ls 到 cat)并使程序 return 成功。但是,我猜测分叉的第二个 child 卡住了,parent 永远等待这个 child。很长一段时间以来,我一直在摆弄这段代码,试图找出它卡住的原因。在C系统编程方面,我有点菜鸟,但我正在努力学习。

有人知道为什么程序没有退出,而是挂在 cat 上吗?

如有任何帮助,我们将不胜感激。

谢谢。

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <fcntl.h>
int main()
{
    char *a[2] = {"/bin/ls", NULL};
    char *b[2] = {"/bin/cat", NULL};
    char *envp[2] = {getenv("PATH"), NULL};
    int fd[2], status;
    pipe(fd);
    int old_std_out = dup(1);
    int old_std_in = dup(0);
    dup2(fd[1], 1);
    int pid = fork();
    switch(pid)
    {
        case -1:
            perror("Forkscrew");
            exit(1);
            break;
        case 0:
            execve(a[0], a, envp);
            exit(0);
            break;
       default:
            waitpid(-1, &status, 0);
            dup2(old_std_out, 1);
            break;
    }
    dup2(fd[0], 0);
    pid = fork();
    switch(pid)
    {
        case -1:
            perror("Forkscrew");
            exit(1);
            break;
        case 0:
            execve(b[0], b, envp);
            exit(0);
            break;
        default:
            waitpid(-1, &status, 0);
            dup2(old_std_in, 0);
            break;
    }
    printf("\n");
    return 0;
}

你的程序中有两个潜在的死锁。

首先,第一个 child (ls) 可能会在尝试写入管道时阻塞,在这种情况下 waitpid() 不会 return 直到 ls 终止,而 ls 直到第二个 child (cat) 开始执行后才会终止,这要到 waitpid() returns 才会发生。 => 死锁。

其次,cat将从它的stdin读取,直到写端的所有文件描述符都关闭。 parent 进程 cat 都有一个写入端的副本,cat 有它 而不知道它明确。如果写端的唯一副本在同一个进程中,有些操作系统会read()不阻塞(避免这种死锁),但这并不能保证。无论哪种方式,因为 parent 进程保留文件描述符的副本,而 parent 进程 waitpid()s 用于 child,它等待 write-end要关闭的管道,你又遇到了死锁。

通常,简化程序可以解决这样的问题:

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <fcntl.h>
int main()
{
    char *a[2] = {"/bin/ls", NULL};
    char *b[2] = {"/bin/cat", NULL};
    char *envp[2] = {getenv("PATH"), NULL};
    int fd[2], status;
    pipe(fd);
    //int old_std_out = dup(1); /*No need to copy stdout...*/
    //int old_std_in = dup(0);  /*...or stdin...*/
    //dup2(fd[1], 1);           /*...if you wait dup2()ing until you need to*/
    int pid = fork();
    switch(pid)
    {
        case -1:
            perror("Forkscrew");
            exit(1);
            //break; /*unreachable*/
        case 0:
            dup2(fd[1], STDOUT_FILENO); /*NOW we dup2()*/
            close(fd[0]); /*no need to pass these file descriptors to...*/
            close(fd[1]); /*...a program that doesn't expect to have them open*/ 
            execve(a[0], a, envp);
            exit(0); /*might want an error message*/
            //break; /*unreachable*/
       default:
            //waitpid(-1, &status, 0); /*don't wait yet*/
            //dup2(old_std_out, 1);
            close(fd[1]); /*we don't need this in the parent anymore*/
            break;
    }
    //dup2(fd[0], 0); /*not needed anymore*/
    pid = fork();
    switch(pid)
    {
        case -1:
            perror("Forkscrew");
            /*might want to ensure the first child can terminate*/
            exit(1);
            //break; /*unreachable*/
        case 0:
            dup2(fd[0], STDIN_FILENO);
            close(fd[0]); /*again, cat doesn't expect a fourth fd open*/
            execve(b[0], b, envp);
            /*again, error message would be nice*/
            exit(0);
            //break;
        default:
            //waitpid(-1, &status, 0);
            //dup2(old_std_in, 0);
            break;
    }
    waitpid(-1, &status, 0); /*don't wait until both children are created*/
    waitpid(-1, &status, 0);
    printf("\n");
    return 0;
}

如您所见,我留下了一些改进建议,但如果 execve() 工作正常,现在应该已经可以正常工作了。