【问题标题】:dup2() is blocking with child processes? Cdup2() 阻塞子进程? C
【发布时间】:2020-06-12 20:57:29
【问题描述】:

我正在编写一个函数,它将输入回显到一个 sed,然后是另一个 sed。我认为我以正确的方式使用了所有等待信号,但我能得到的最后一个打印是在 echo 中我的第一个子进程中对 dup2() 的调用之前。

void sendbc (char * str_ ) {
    int fd[2];
    int fd1[2];
    int pid,pid1;
    char* echo[] = {"echo", str_,NULL};
    char* sed1[] = {"sed","s/[^:]*;"" " "//",NULL};
    char* sed2[] = {"sed","s/[^:]*."" " "//",NULL};
    int status,er;
    FILE *f;
    if(pipe(fd) < 0){
        exit(100);
    }
    if(pipe(fd1) < 0){
        exit(100);
    }

    pid = fork();
    if (pid == 0) {
        dup2(fd[1], 1) //last command before blocking
        close(fd[1]);
        close(fd[0]);
        execvp(echo[0], echo);
        printf("Error in execvp1\n");
    }else{
        wait(&status);
        pid = fork();
        if (pid == 0){
            dup2(fd[0], 0);
            dup2(fd1[1], 1);
            dup2(fd1[1], 2);
            close(fd[1]);
            close(fd[0]);
            close(fd1[1]);
            close(fd1[0]);
            execvp(sed1[0],sed1);
            printf("Error in execvp2\n");
        }else{
            wait(&status);
            dup2(fd1[0],0);
            dup2(1,2);
            //dup2(1,1);
            close(fd1[1]);
            close(fd1[0]);
            execvp(sed2[0],sed2);
            printf("Error in execvp3\n");
        }
    }

    if(pid!=0)
        wait(&status);
    close(fd[0]);
    close(fd[1]);
    close(fd1[1]);
    close(fd1[0]);
}

我可以想象 2 种可能性... dup2 正在阻塞或我需要创建更多进程,因为它结束了待命进程,但是在快速阅读他的手册页后这听起来不太正确... 它可能是什么?

【问题讨论】:

  • 我无法回答你的问题,但你确定这个字符串是它应该是什么?:"s/[^:]*;"" " "//"
  • 不,它是随机类型,但这意味着在 ';' 之前全部剪切以及'.'之前的所有内容。无论如何,我打印了进程的跟踪,所有这些都在 echo dup2 中被阻止...
  • 您通常应该同时运行管道中的进程,而不是运行一个,等待它完成,然后运行下一个。您的数据量可能足够小,没有问题;有大量数据,可能会导致死锁。此外,错误消息应打印到stderr,而不是stdout
  • 请注意.sed 的元字符。如果要搜索点,请使用 [.] 或反斜杠转义(但要多少个反斜杠 - 您需要两个 "\\." 以便编译器生成字符串反斜杠点。如果您使用 system() 而不是 fork()execvp(),您需要在 C 代码中使用 4 个反斜杠。
  • 您可能没有在子项中关闭足够的文件描述符。 经验法则:如果您将管道的一端dup2() 连接到标准输入或标准输出,请尽快关闭pipe() 的两个原始文件描述符。特别是,这意味着在使用任何exec*() 系列函数之前。该规则也适用于dup()fcntl()F_DUPFD

标签: c pipe fork wait dup


【解决方案1】:

一般问题

您没有在各个进程中关闭足够多的文件描述符。


经验法则:如果您 dup2() 管道的一端到标准输入或标准输出,关闭两者 返回的原始文件描述符 pipe() 尽快地。 特别是,您应该在使用任何 exec*() 函数族。

如果您使用以下任一方式复制描述符,该规则也适用 dup() 或者 fcntl() F_DUPFDF_DUPFD_CLOEXEC


如果父进程不会通过以下方式与其任何子进程通信 管,它必须确保它及早关闭管道的两端 足够(例如,在等待之前),以便它的孩子可以收到 读取时的 EOF 指示(或获取 SIGPIPE 信号或写入错误) write),而不是无限期地阻塞。 即使父级使用管道而不使用dup2(),它也应该 通常至少关闭管道的一端——这是非常罕见的 在单个管道的两端进行读写的程序。

请注意 O_CLOEXEC 选项 open(), 和fcntl()FD_CLOEXECF_DUPFD_CLOEXEC 选项也可以考虑 进入这个讨论。

如果你使用 posix_spawn() 及其广泛的支持功能系列(总共 21 个功能), 您将需要查看如何在生成的进程中关闭文件描述符 (posix_spawn_file_actions_addclose(), 等等)。

请注意,使用dup2(a, b) 比使用close(b); dup(a); 更安全 出于各种原因。 一种是如果你想强制文件描述符大于 通常的号码,dup2() 是唯一明智的做法。 另一个是如果ab 相同(例如两者都是0),那么dup2() 正确处理它(在复制 a 之前它不会关闭 b) 而单独的 close()dup() 却失败了。 这是一种不太可能,但并非不可能的情况。


具体问题

  • 您没有关闭足够的文件描述符以确保安全。
  • 您的正则表达式有问题。
  • 不应让管道中的进程相互等待。

小毛病:当我有两个密切相关的变量(如管道文件描述符对)时,我更喜欢使用fd1fd2;我觉得fdfd1 之类的很傻。但是,您可以选择忽略这一点。

工作代码

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

static void dump_argv(char **argv)
{
    printf("%d:\n", getpid());
    while (*argv != NULL)
    {
        printf("%d: <<%s>>\n", getpid(), *argv++);
    }
}

static void sendbc(char *str)
{
    int fd1[2];
    int fd2[2];
    int pid;
    char *echo[] = {"echo", str, NULL};
    char *sed1[] = {"sed", "s/[^:]*[;]//", NULL};
    char *sed2[] = {"sed", "s/[^:]*[.]//", NULL};
    if (pipe(fd1) < 0)
        exit(100);
    if (pipe(fd2) < 0)
        exit(101);

    printf("%d: at work\n", getpid());
    pid = fork();
    if (pid < 0)
        exit(102);
    else if (pid == 0)
    {
        printf("%d: child 1 - echo\n", getpid());
        dump_argv(echo);
        dup2(fd1[1], 1);
        close(fd1[1]);
        close(fd1[0]);
        close(fd2[0]);
        close(fd2[1]);
        execvp(echo[0], echo);
        fprintf(stderr, "Error in execvp1\n");
        exit(103);
    }
    else
    {
        printf("%d: parent - before second fork\n", getpid());
        pid = fork();
        if (pid == 0)
        {
            printf("%d: child 2 - sed 1\n", getpid());
            dump_argv(sed1);
            dup2(fd1[0], 0);
            dup2(fd2[1], 1);
            close(fd1[1]);
            close(fd1[0]);
            close(fd2[1]);
            close(fd2[0]);
            execvp(sed1[0], sed1);
            fprintf(stderr, "Error in execvp2\n");
            exit(104);
        }
        else
        {
            printf("%d: parent - sed 2\n", getpid());
            dump_argv(sed1);
            dup2(fd2[0], 0);
            close(fd1[1]);
            close(fd1[0]);
            close(fd2[1]);
            close(fd2[0]);
            execvp(sed2[0], sed2);
            fprintf(stderr, "Error in execvp3\n");
            exit(105);
        }
    }
    fprintf(stderr, "Reached unexpectedly\n");
    exit(106);
}

int main(void)
{
    char message[] =
        "This is the first line\n"
        "and this is the second - with a semicolon ; here before a :\n"
        "and the third line has a colon : before the semicolon ;\n"
        "but the fourth line has a dot . before the colon\n"
        "whereas the fifth line has a colon : before the dot .\n"
    ;

    sendbc(message);
    return 0;
}

示例输出

$ ./pipe29
74829: at work
74829: parent - before second fork
74829: parent - sed 2
74829:
74829: <<sed>>
74829: <<s/[^:]*[;]//>>
74830: child 1 - echo
74830:
74830: <<echo>>
74830: <<This is the first line
and this is the second - with a semicolon ; here before a :
and the third line has a colon : before the semicolon ;
but the fourth line has a dot . before the colon
whereas the fifth line has a colon : before the dot .
>>
74831: child 2 - sed 1
74831:
74831: <<sed>>
74831: <<s/[^:]*[;]//>>
This is the first line
 here before a :
and the third line has a colon :
 before the colon
whereas the fifth line has a colon :

$

除了诊断打印之外,主要区别在于此代码严格关闭了管道的所有未使用端,并且它不包含对 wait() 或其亲属的调用——它们不是必需的,并且通常在它们阻塞时是有害的管道中进程的并发执行。

【讨论】:

  • 该死的人,这是对一切的准确解释。我非常感谢您的帮助,非常感谢您抽出宝贵的时间。它也是一个非常干净且易于调试的代码!唯一的问题是它只适用于 1 次调用,然后它会停止执行 main 函数,但是添加另一个分叉就足够了,游戏就完成了!再次感谢您!
  • 您的原始代码在第一次调用后停止,所以这段代码也停止了。但是,正如您所说,它很容易修复。只需确保父代码不涉及管道,或者它足够快地关闭所有管道(在等待之前)。
  • 多亏了你,我解决了所有问题!现在我在主代码的最后一步(这只是一个小但非常有问题的功能!)问题是将一些管道内容带到一个var,如果你想检查它是我的最后一个主题!
猜你喜欢
  • 2014-03-23
  • 1970-01-01
  • 2011-08-02
  • 2023-03-11
  • 2016-06-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多