【问题标题】:What's wrong with this C code? The child is not returning?这段 C 代码有什么问题?孩子不回来了?
【发布时间】:2017-05-17 04:29:12
【问题描述】:

这是我试图了解如何在两个子进程之间进行正确管道的尝试。我只是试图将一个 Linux 命令的输出传递给另一个(ls 到 cat)并让程序成功返回。但是,我猜测分叉的第二个孩子被卡住了,父母永远在等待这个孩子。我一直在摆弄这段代码,试图找出它卡住的原因。在 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;
}

【问题讨论】:

  • 你不是在处决两只猫吗?你先分叉,然后再分叉?
  • 一个更微妙的死锁(但这种死锁总是发生,不仅仅是在管道缓冲区已满时)与管道的读取端相关联。当ls 终止时,它会关闭管道写入端的副本。但是,您的父进程您的cat-进程仍然有一个写入端的副本(实际上,父进程末尾的printf() 会写入它......) ,因此来自cat 中管道的read() 将阻塞,直到关闭写入端,这永远不会发生,因为cat 甚至不知道写入端的文件描述符,并且父级不会终止。
  • @SamiKuhmonen exec() 如果成功则不会返回。
  • @user3499524:啊,对,我没看到你dup2(old_std_out, 1);回来。这不是做这种事情的常用方法,你通常应该只重定向文件描述符之后 fork(),那么你就不需要这个了。由于您 dup2(fd[1], 1); 第一个 fork() 之前,您保留了大约两个管道写入端的副本。一个,你通过dup2(old_std_out, 1);隐式关闭,另一个留下。
  • 可能不应该在评论中做出全面的回答。不幸的是,我现在没有时间,我可能在几个小时内。无论如何,要快速修复第二个死锁,请在第二个 fork() 之前添加 close(fd[1]),看看会有帮助。

标签: c pipe fork dup execve


【解决方案1】:

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

首先,第一个孩子 (ls) 在尝试写入管道时可能会阻塞,在这种情况下,waitpid() 将在 ls 终止之前返回,ls 直到第二个孩子 ( cat) 开始执行,直到 waitpid() 返回才会发生。 => 死锁。

其次,cat 将从它的stdin 中读取,直到写结束的所有文件描述符都关闭。父进程cat 都有一个写入端的副本,cat 有它却不明确地知道它。如果写端的唯一副本在同一个进程中(以避免这种死锁),某些操作系统将有read() not block,但这不能保证。无论哪种方式,由于父进程保留了文件描述符的副本,而父进程 waitpid()s 用于等待管道的写入端关闭的子进程,因此您再次陷入死锁。

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

#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()s 工作正常。

【讨论】:

  • 再次感谢您的帮助,为我节省了数小时的挫败感。
猜你喜欢
  • 1970-01-01
  • 2016-10-23
  • 2013-04-30
  • 2015-04-25
  • 2018-09-09
相关资源
最近更新 更多