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