C fork 循环执行无限命令(urandom/tail 在活动文件上)

C fork loop to exec infinite commands (urandom/tail on an active file)

我正在尝试使用 C 在 unix (OSX) shell(如 bash)中重现管道的行为。 我可以毫无问题地处理不是无限的命令(例如:ls | wc -c)。

但是正如您所想象的,我有一个问题,我无法处理无限命令,例如:base64 /dev/urandom" | head -c 1000。 此命令的输出立即是 urandom 的前 1000 个字符。

我的函数只是等待 urandom 的结束,这是无限的...所以,我需要用 "CTRL + C" 终止进程(并将信号处理到 child 而不是我的函数)打印前 1000 个字符。

我执行所有管道的函数:

#define READ_END        0
#define WRITE_END       1


void    exec_pipes(char ***argv)
{
    int   p[2];
    int   fd_save = 0;

    while (*argv)
    {
        pipe(p);
        if (fork() == 0)
        {
            dup2(fd_save, STDIN_FILENO);
            if (*(argv + 1))
                dup2(p[WRITE_END], STDOUT_FILENO);
            close(p[READ_END]);
            execvp(**argv, *argv);
            exit(EXIT_FAILURE);
        }
        else
        {
            wait(NULL);
            fd_save = p[READ_END];
            close(p[WRITE_END]);
            (*argv)++;
        }
    }
}

在这里,您可以使用 main 检查我的整个代码: https://www.onlinegdb.com/Hkbjd3WOz

提前致谢。

不,你不知道。当读取进程刚刚结束时,它关闭了管道的读取端,因此不允许写入者再向管道写入,并从系统中得到错误 EPIPE(无法写入管道)。

要发生这种情况,没有进程必须打开它进行读取,因为内核会计算读取和写入进程的数量,并且当至少有一个进程打开它进行读取时不会给出此错误(这意味着链中的第一个进程必须关闭管道的读取文件描述符,如果它不打算从中读取的话)

然后,你必须在 PARENT AND CHILD 中关闭未使用的描述符(这取决于你是否希望父代成为作者或 reader),以及 dup2(2) 另一个描述符(同样,在父子关系中)到文件描述符 0(输入)或 1(输出)。 (首先要说的是,我已经通过调用 yes 命令更改了您的 /dev/urandom 示例,因为它是所有 unix 风格的标准,并且还会产生无限输出)

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

char *writer_program = "yes";
char *argv_writer[] = {
    "yes", "yes", NULL,
};
char *reader_program = "head";
char *argv_reader[] = {
    "head", "-c", "10", NULL,
};

int main()
{
    int pipe_fds[2];
    int res;

    pipe(pipe_fds);
    if ((res = fork()) < 0) {     /* PLEASE, CHECK FOR ERRORS. */
        perror("fork");
    } else if (res == 0) {        /* child process, ACTING AS WRITER */
        close(pipe_fds[0]);       /* close writing part of pipe */
        dup2(pipe_fds[1], 1);     /* dup2 reading part as stdin */
        execvp(writer_program, argv_writer);
        perror("execvp");         /* PLEASE, CHECK FOR ERRORS. */
    } else {                      /* parent process, ACTING AS A READER */
        close(pipe_fds[1]);       /* just the opposite */
        dup2(pipe_fds[0], 0);
        execvp(reader_program, argv_reader);
        perror("execvp");         /* PLEASE, CHECK FOR ERRORS. */
    }
    /* BOTH, THIS IS REACHED IN CASE OF FAILURE (fork fails, or any
     * of the exec(2) calls  fail. */
    exit(EXIT_FAILURE);
}

编辑

下面是一个完整的示例,其中的管道链等同于下一个:

dd if=/dev/urandom ibs=1024 | base64 | head -n 150 | sort -r | pr

在这种情况下,第一个死掉的程序是 head,如您所愿,所有程序都在它后面死掉。父程序在等待他所有的子程序死掉,每一个系统调用都被包裹了,所以你通过stderr得到执行的踪迹,看看会发生什么。

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

#define F(fmt) "[pid=%d]:"__FILE__":%d:%s: " fmt, getpid(), __LINE__, __func__

#define SIZE(a) (sizeof a/sizeof *a)

#define ERR(fmt, ...) do{\
            fprintf(stderr, \
                    F(fmt": %s (errno = %d)\n"),\
                    ##__VA_ARGS__,\
                    strerror(errno),\
                    errno);\
            exit(EXIT_FAILURE);\
    }while(0)

#define CLOSE(expr) do{\
            close(expr);\
            fprintf(stderr, \
                    F("close("#expr" === %d);\n"), \
                    (expr));\
    }while(0)

#define PIPE(var) do{\
            if(pipe(var)<0) ERR("pipe"); \
            fprintf(stderr,F("PIPE ==> %d, %d\n"), var[0], var[1]);\
    }while(0)

#define FORK() do{\
            if((res = fork()) < 0)ERR("fork");\
            fprintf(stderr,F("FORK ==> %d\n"), res);\
    }while(0)

#define DUP(expr1, expr2) do{\
            if((res = dup2(expr1, expr2)) < 0) ERR("dup");\
            fprintf(stderr,\
                    F("DUP("#expr1" === %d, "#expr2" === %d);\n"),\
                    (expr1), (expr2));\
    }while(0)


char * argv_DISK_DUMP[] = { "dd", "if=/dev/urandom", "ibs=1024", NULL };
char * argv_BASE64[] =    { "base64", NULL };
char * argv_HEAD[] =      { "head", "-n", "150", NULL };
char * argv_SORT[] =      { "sort", "-r", NULL };
char * argv_PR[] =        { "pr", NULL };

struct pipe_program {
 pid_t pid;
 pid_t ppid;
    char *pname;
    char **argv;
} pipe_programs[] = {

    0, 0, "dd", argv_DISK_DUMP,
    0, 0, "base64", argv_BASE64,
    0, 0, "head", argv_HEAD,
    0, 0, "sort", argv_SORT,
    0, 0, "pr", argv_PR,

};

/* size of last array */
size_t pipe_programs_n = SIZE(pipe_programs);

static size_t printus(int ix, struct pipe_program *p);
static pid_t WAIT(int *status);

int main()
{
    int res, i;
    struct pipe_program *p = pipe_programs;
    int input_fd = 0; /* first process is connected to standard input */
    static int pipe_fds[2] = { -1, -1 };

    for(i = 0; i < pipe_programs_n - 1; i++, p++) {

            PIPE(pipe_fds);

            FORK();

            if (res == 0) { /* child process, we have redirected nothing yet. */

                    p->pid = getpid();
                    p->ppid = getppid();

                    /* print who we are */
                    printus(i, p);

                    /* redirect input, if needed */
                    if (input_fd != 0) {
                            DUP(input_fd, 0);
                            CLOSE(input_fd); /* not used after redirection */
                    }

                    CLOSE(pipe_fds[0]); /* we don't use this */

                    /* and output */
                    DUP(pipe_fds[1], 1);
                    CLOSE(pipe_fds[1]);

                    execvp(p->pname, p->argv);

                    ERR("execvp: %s", p->pname);
                    /* NOTREACHED */

            }
            /* parent process */

            /* save pid to be used later */
            p->pid = res; /* we'll use it later */
            p->ppid = getpid();

            /* close unused pipe descriptor */
            CLOSE(pipe_fds[1]);

            /* if we have an old input_fd, then close it */
            if (input_fd) CLOSE(input_fd);

            /* ... and save pipe read descriptor */
            input_fd = pipe_fds[0];
    } /* for */

    /* now we have our input connected to the output of the last process */
    FORK();
    if (res == 0) { /* child, last process in the pipe */

            p->pid = getpid();
            p->ppid = getppid();

            /* print who we are */
            printus(i, p);

            /* redirect input */
            if (input_fd != 0) {
                    DUP(input_fd, 0);
                    CLOSE(input_fd); /* not used after_redirection */
            }

            /* this time no output redirection */

            execvp(p->pname, p->argv);

            ERR("execvp");
            /* NOTREACHED */
    }

    CLOSE(pipe_fds[1]);
    if (input_fd) CLOSE(input_fd);

    /* parent code... we did pipe_programs_n fork()'s so we
     * have to do pipe_programs_n wait()'s */
    int status;
    pid_t cld_pid;
    /* while we can wait for a child */
    while ((cld_pid = WAIT(&status)) > 0) {
            for (i = 0, p = pipe_programs; i < pipe_programs_n; i++, p++) {
                    if (cld_pid == p->pid) {
                            fprintf(stderr,
                                    F("Child finished: pid = %d\n"),
                                    cld_pid);
                            printus(i, p);
                            break;
                    }
            }
    } /* while */
    exit(EXIT_SUCCESS);
}

static size_t printus(int ix, struct pipe_program *p)
{
    size_t res = 0;
    int j;
    static char buffer[1024];
    char *s = buffer;
    size_t bfsz = sizeof buffer;
    size_t n;

#define ACT() do{s+=n; bfsz-=n;}while(0)

    n = snprintf(s, bfsz,
            F("%d: pid = %d, ppid = %d, program = \"%s\": args = "),
            ix, p->pid, p->ppid, p->pname);
    ACT();
    for (j = 0; p->argv[j]; j++) {
            n = snprintf(s, bfsz,
                    "%s\"%s\"",
                    j ? ", " : "{",
                    p->argv[j]);
        ACT();
    }
    n = snprintf(s, bfsz, "};\n");
    ACT();
    fputs(buffer, stderr);

    return res;
}

static pid_t WAIT(int *status)
{
    pid_t res = wait(status);
    fprintf(stderr, F("WAIT() ==> %d\n"), res);
    return res;
}

您可以通过以下命令获取整个程序:

git clone git@github.com:mojadita/pipe.git

请注意,那里的程序几乎完全相同,但是已经做了一些工作来促进不同管道的编写,而不必触及主源文件中的几个地方。如果您从 git 服务器下载程序并且要修改管道,请编辑文件 pipe.i。然后 make 瞧! :) 该程序已经在 linux、freebsd 和 mac osx 上进行了测试,所以我认为您可以毫无问题地适应您的需要。如果你从 github 得到它,你也会有一个 Makefile.