dup2() 正在阻塞子进程? C

dup2() is blocking with child processes? C

我正在编写一个函数,将输入回显到一个 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 正在阻塞,或者我需要创建更多进程,因为它会在调用时结束进程,但是在快速阅读他的手册页后这听起来不正确...可能是什么?

一般问题

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


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

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


如果 parent 进程将不与其任何 children 通过 管道,它必须确保尽早关闭管道的两端 足够(例如,在等待之前)以便其 children 可以接收 EOF 指示读取(或获取 SIGPIPE 信号或写入错误 写),而不是无限期地阻塞。 即使 parent 使用管道而不使用 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() 或其亲属的调用——它们不是必需的,通常是当它们阻止管道中进程的并发执行时是有害的。