简体   繁体   English

通过C中的管道重定向stdin和stdout适用于外部程序,但不适用于递归调用

[英]Redirection of stdin and stdout via pipes in C works for external programmes but not for recursive call

I am trying to communicate with forked child processes via pipe redirection of stdin and stdout in C. I already managed to get this to work for shell commands (like ls , for example) executed in child processes. 我正在尝试通过C中的stdin和stdout的管道重定向与分支的子进程进行通信。我已经设法ls适用于在子进程中执行的shell命令(例如ls )。 However, I wasn't able to recursively execute the same program and redirect the output (printed by printf() , fprintf() to stdout , ...) via the pipes from the child process to the parent (in this test to stdout of the parent), although this works fine for ls or similar commands. 但是,我无法递归执行同一程序,无法通过子进程到父进程(在此测试中将其stdoutstdout )通过管道重定向输出(由printf()fprintf()打印到stdout ,...)。 ),尽管这对于ls或类似命令也可以正常工作。

Here's how I tried to approach this: 这是我尝试解决的方法:

  • I create a pipe, the reading end is for the parent, the child process should write to the writing end. 我创建了一个管道,读取端是针对父端的,子进程应写入写入端。
  • The Process forks, both processes close the unused end, respectively. 进程派生,两个进程分别关闭未使用的一端。
  • The writing end of the pipe is redirected to STDOUT_FILENO and closed 管道的写入端被重定向到STDOUT_FILENO并关闭
  • The child process executes the program recursively (it is called ./to2) 子进程递归执行程序(称为./to2)

As mentioned, this does work if I execute ls in the child process, but not if I try to call the same program recursively. 如前所述,如果我在子进程中执行ls ,这确实可以工作,但是如果我尝试递归调用同一程序,则不行。 Here's my test program where I tried to get this to work: 这是我的测试程序,我尝试将其工作:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <netdb.h>
#include <errno.h>
#include <time.h>
#include <signal.h>
#include <fcntl.h>


static void usage(void){
    fprintf(stderr,"RIP");
    exit(EXIT_FAILURE);
}

int main(int argc, char *argv[]){   
    if(argc > 1){
        dprintf(STDOUT_FILENO,"Please work\n"); 
        printf("\n THIS IS A MESSAGE FROM THE CHILD \n");
        fputs("Pretty Please!\n",stdout);
        fflush(stdout);
        exit(EXIT_SUCCESS);
    }
    int p1[2];
    if(-1 == pipe(p1)) { 
        fprintf(stderr,"pipe\n");
        fprintf(stderr,"%s\n",strerror(errno));
        usage();    
    }
    int f = fork();
    if(f == 0){
        close(p1[0]);
        if(dup2(p1[1],STDOUT_FILENO) < 0){
            fprintf(stderr,"dup2\n");
            usage();
        }
        close(p1[1]);

        //I want this to work:
        //execlp("./to2", "./to2", "-e");

        //This works fine:
        execlp("ls", "ls");

        exit(EXIT_SUCCESS);
    } else if (f == -1) {
        usage();
    } else  {
        close(p1[1]);
        int w = -1;
        if(-1 == wait(&w)) usage();
        char b[12];
        memset(b,0,12); 
        read(p1[0],&b,12);

        char reading_buf[1];

        while(read(p1[0], reading_buf, 1) > 0){ 
            write(1, reading_buf, STDOUT_FILENO);
        }
        close(p1[0]);
    }
}   

For testing purposes, the function is called recursively with additional arguments, while the parent program is called without additional arguments (hence the if(argc>1) ). 为了测试目的,该函数使用附加参数递归调用,而父程序则不附加参数调用(因此if(argc>1) )。 In the final program, endless recursion is being avoided by other means. 在最终程序中,通过其他方式避免了无限递归。

Did I understand something wrongly? 我听错了吗? I am pretty confused by the fact that the only thing that doesn't seem to work is redirecting the output of my own program... 我感到非常困惑的是,似乎唯一起作用的是重定向我自己程序的输出...

Thank you very much in advance, any help or ideas are greatly appreciated. 提前非常感谢您,任何帮助或想法都将不胜感激。

The primary problem is precisely as outlined in the comments — you are not calling execlp() correctly (nor ls in the alternative). 主要问题execlp()注释中所概述的那样—您未正确调用execlp() (在替代方法中也不是ls )。 You must make the last argument on those function calls into an explicit null pointer, as shown in this code, which is a mostly mildly edited version of what's in the question: 您必须使这些函数调用的最后一个参数成为显式的null指针,如以下代码所示,该代码是问题内容的经过稍微编辑的版本:

#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/wait.h>
#include <unistd.h>

static void usage(void)
{
    fprintf(stderr, "RIP\n");
    exit(EXIT_FAILURE);
}

int main(int argc, char *argv[])
{
    if (argc > 1)
    {
        dprintf(STDOUT_FILENO, "Please work\n");
        printf("THIS IS A MESSAGE FROM THE CHILD\n");
        fputs("Pretty Please!\n", stdout);
        fflush(stdout);
        exit(EXIT_SUCCESS);
    }
    int p1[2];
    if (-1 == pipe(p1))
    {
        fprintf(stderr, "pipe: %s\n", strerror(errno));
        usage();
    }
    int f = fork();
    if (f == 0)
    {
        close(p1[0]);
        if (dup2(p1[1], STDOUT_FILENO) < 0)
        {
            fprintf(stderr, "dup2: %s\n", strerror(errno));
            usage();
        }
        close(p1[1]);
        execlp(argv[0], argv[0], "-e", (char *)0);
        fprintf(stderr, "failed to exec %s again\n", argv[0]);
        exit(EXIT_FAILURE);
    }
    else if (f == -1)
    {
        usage();
    }
    else
    {
        close(p1[1]);
        char b[13];
        memset(b, 0, 13);
        if (read(p1[0], &b, 12) < 0)
        {
            fprintf(stderr, "Failed to read from pipe (%s)\n", strerror(errno));
            exit(EXIT_FAILURE);
        }
        int len = strcspn(b, "\n");
        printf("M1 [%.*s]\n", len, b);

        char reading_buf[1];

        while (read(p1[0], reading_buf, 1) > 0)
        {
            write(1, reading_buf, STDOUT_FILENO);
        }
        close(p1[0]);
        int w = -1;
        if (-1 == wait(&w))
            usage();
    }
    return 0;
}

Two important changes should be highlighted: 应该强调两个重要的变化:

  • This code echoes the first line of data — the one written by dprintf() — whereas the original code just read it and discarded it. 此代码回显第一行数据dprintf()编写的第一行-而原始代码只是读取并丢弃它。
  • The wait() call is after the input, not before. wait()调用在输入之后,而不是在输入之后。 If the child had more data to write than a set of fixed messages, it could block waiting for the parent to read some of the data, while the parent is blocked waiting for the child to exit. 如果子级要写入的数据多于一组固定消息,则它可能会阻止等待父级读取某些数据,而阻止父级等待子级退出。 This would be a deadlock. 这将是一个僵局。

The usage() function is not appropriately named — it doesn't report how to run the program. usage()函数的名称不正确-它不会报告如何运行程序。 I also exit with a failure status, not success, if the child process fails the execlp() . 如果子进程使execlp()失败,我也将以失败状态而不是成功退出。

Under peculiar circumstances, the wait() call might report on the exit status from some child other than the one that was forked. 在特殊情况下, wait()调用可能会报告某个孩子的退出状态,而不是分叉的孩子。 It is generally best to use a loop to reap such children. 通常最好使用循环来收割此类孩子。 However, the circumstances required are extremely peculiar — the process which launched the parent with an exec*() function must have previously created some children for which it didn't wait, so that they are inherited by the parent process (because the PID doesn't change across an exec*() call). 但是,所需的情况非常特殊-使用exec*()函数启动父级的进程必须事先创建了一些它不等待的子级,以便它们被父级进程继承(因为PID不会)不会在exec*()调用之间进行更改)。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM