C fork 循环执行无限命令(事件文件上的 urandom/tail)

标签 c unix pipe exec execvp

我正在尝试使用 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

提前致谢。

最佳答案

不,你不知道。当一个读进程刚结束时,它关闭了管道的读端,因此写者不能再向管道写入,并从系统。

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

然后,您必须在 PARENT AND CHILD 中关闭未使用的描述符(这取决于您希望父级是写入者还是读取者),以及 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

关于C fork 循环执行无限命令(事件文件上的 urandom/tail),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/48993227/

相关文章:

c++ - 为什么空格在 Linux 中的管道中丢失?

C编程: Fork() and IPC with Pipes

c# - Windows 和 Linux 应用程序之间的管道

c - 对数组的引用与对数组指针的引用

c - SIGINT(ctrl+c) 不中断接受调用

c - 使用Xcode跳转到StdLib源码

linux - 符号链接(symbolic link)作为真实文件

linux - 使并行作业性能

c - 使用 fread 将文件内容读入结构

使用线程检查命令行参数是否是回文词