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

问题描述 投票:2回答:1

我试图用C重现unix(OSX)shell(比如bash)中管道的行为。我没有问题来处理一个非无限的命令(例如:ls | wc -c)。

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

我的函数只是等待urandom的结束,这是一个infite ...所以,我需要用“CTRL + C”来杀死这个过程(并且只处理孩子的信号而不是我的函数)来打印前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检查我的整个代码

提前致谢。

c unix pipe exec execvp
1个回答
0
投票

不,你没有。当读取过程刚刚结束时,它会关闭管道的读取端,因此不允许写入器再写入管道,并从系统中获取错误EPIPE(无法写入管道)。

对于ocurr,没有任何进程必须打开它才能读取,因为内核会计算读取和写入进程的数量,并且在至少有一个进程将其打开以供读取时不会发出此错误(这意味着第一个进程)链必须关闭管道的读文件描述符,如果它不会从中读取)

然后,您必须关闭未使用的描述符(这取决于您是否希望父级是作者或读者)在父母和孩子中,并且dup2(2)另一个描述符(再次,在父母和孩子中)到文件描述符0 (in)或1(out)。 (首先要说的是,我通过调用/dev/urandom命令改变了你的yes示例,因为它是所有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);
}

EDIT

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

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 [email protected]:mojadita/pipe.git

请注意那里的程序几乎完全相同,但已经做了一些工作来促进不同管道的编写,而不必触及主源文件中的几个位置。如果从git服务器下载程序并且想要修改管道,请编辑文件pipe.i。然后make和voilá!!! :)该程序已在linux,freebsd和mac osx上测试过,所以我认为你可以根据自己的需要调整它。如果你从github得到它,你也会有一个Makefile

© www.soinside.com 2019 - 2024. All rights reserved.