正如 tadman 所指出的,使用命令结构来传递东西更容易。
我们不能[好吧,我们可以但不应该]在管道构造期间做一个wait [在父级中] >。以后必须是一个单独的循环。在创建第一个孩子后,我们将挂起父母。
如果第一个孩子有大量输出,内核管道缓冲区可能会填满,第一个孩子会阻塞。但是,由于第二个孩子没有被创建,所以没有什么可以读取/排出第一个孩子的输出并解除阻塞。
此外,在执行dup2 之后关闭管道单元并确保先前的管道阶段单元在父级中关闭也很重要。
这是一个重构的版本,可以做到所有这些。
关于文件描述符泄漏的原始问题,我认为我通过添加更多close 调用来解决这个问题。该程序对此有一些自我验证码:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <dirent.h>
#include <sys/wait.h>
#define FREEME(ptr_) \
do { \
if (ptr_ == NULL) \
break; \
free(ptr_); \
ptr_ = NULL; \
} while (0)
#define CLOSEME(fd_) \
do { \
if (fd_ < 0) \
break; \
close(fd_); \
fd_ = -1; \
} while (0)
// command control
typedef struct {
unsigned int cmd_opt; // options
int cmd_cldno; // child number
char *cmd_buf; // command buffer
int cmd_argc; // argument count
char **cmd_argv; // arguments
int cmd_pipe[2]; // pipe units
pid_t cmd_pid; // child pid number
int cmd_status; // child status
} cmd_t;
#define CMD_FIRST (1u << 0)
#define CMD_LAST (1u << 1)
char linebuf[1000];
int cmdcount;
cmd_t *cmdlist;
int opt_d;
int opt_l;
#define dbg(fmt_...) \
do { \
if (opt_d) \
printf(fmt_); \
} while (0)
// show open fd's
void
fdshow1(int cldid)
{
char buf[100];
fprintf(stderr,"CLD: %d\n",cldid);
sprintf(buf,"ls -l /proc/%d/fd 1>&2",getpid());
system(buf);
}
// show open fd's
void
fdshow2(int cldid)
{
char dir[100];
char lnkfm[1000];
char lnkto[1000];
int len;
DIR *xf;
struct dirent *ent;
char *bp;
char obuf[1000];
sprintf(dir,"/proc/%d/fd",getpid());
xf = opendir(dir);
bp = obuf;
bp += sprintf(bp,"%d:",cldid);
while (1) {
ent = readdir(xf);
if (ent == NULL)
break;
if (strcmp(ent->d_name,".") == 0)
continue;
if (strcmp(ent->d_name,"..") == 0)
continue;
sprintf(lnkfm,"%s/%s",dir,ent->d_name);
len = readlink(lnkfm,lnkto,sizeof(lnkto));
lnkto[len] = 0;
if (strstr(lnkto,"pipe") != 0)
bp += sprintf(bp," %s-->%s",ent->d_name,lnkto);
switch (ent->d_type) {
case DT_FIFO:
break;
}
}
bp += sprintf(bp,"\n");
fputs(obuf,stderr);
fflush(stderr);
closedir(xf);
}
// show open fd's
void
fdshow(int cldid)
{
fdshow2(cldid);
}
// pipeadd -- add single command to pipe
void
pipeadd(char *buf)
{
char *cp;
char *bp;
char *sv;
cmd_t *cmd;
dbg("pipeadd: buf='%s'\n",buf);
cmdlist = realloc(cmdlist,(cmdcount + 1) * sizeof(cmd_t));
cmd = &cmdlist[cmdcount];
memset(cmd,0,sizeof(cmd_t));
cmd->cmd_pipe[0] = -1;
cmd->cmd_pipe[1] = -1;
cmd->cmd_cldno = cmdcount;
++cmdcount;
bp = buf;
while (1) {
cp = strtok_r(bp," \t",&sv);
bp = NULL;
if (cp == NULL)
break;
cmd->cmd_argv = realloc(cmd->cmd_argv,
(cmd->cmd_argc + 2) * sizeof(char **));
cmd->cmd_argv[cmd->cmd_argc + 0] = cp;
cmd->cmd_argv[cmd->cmd_argc + 1] = NULL;
cmd->cmd_argc += 1;
}
}
// pipesplit -- read in and split up command
void
pipesplit(void)
{
char *cp;
char *bp;
char *sv;
cmd_t *cmd;
printf("> ");
fflush(stdout);
fgets(linebuf,sizeof(linebuf),stdin);
cp = strchr(linebuf,'\n');
if (cp != NULL)
*cp = 0;
bp = linebuf;
while (1) {
cp = strtok_r(bp,"|",&sv);
bp = NULL;
if (cp == NULL)
break;
pipeadd(cp);
}
cmd = &cmdlist[0];
cmd->cmd_opt |= CMD_FIRST;
cmd = &cmdlist[cmdcount - 1];
cmd->cmd_opt |= CMD_LAST;
if (opt_d) {
for (cmd_t *cmd = cmdlist; cmd < &cmdlist[cmdcount]; ++cmd) {
dbg("%d:",cmd->cmd_cldno);
for (int argc = 0; argc < cmd->cmd_argc; ++argc)
dbg(" '%s'",cmd->cmd_argv[argc]);
dbg("\n");
}
}
}
// pipefork -- fork elements of pipe
void
pipefork(void)
{
cmd_t *cmd;
int fdprev = -1;
int fdpipe[2] = { -1, -1 };
for (cmd = cmdlist; cmd < &cmdlist[cmdcount]; ++cmd) {
// both parent and child should close output side of previous pipe
CLOSEME(fdpipe[1]);
// create a new pipe for the output of the current child
if (cmd->cmd_opt & CMD_LAST) {
fdpipe[0] = -1;
fdpipe[1] = -1;
}
else
pipe(fdpipe);
cmd->cmd_pid = fork();
if (cmd->cmd_pid < 0) {
printf("pipefork: fork fail -- %s\n",strerror(errno));
exit(1);
}
// parent the input side for the next pipe stage
if (cmd->cmd_pid != 0) {
CLOSEME(fdprev);
fdprev = fdpipe[0];
continue;
}
// connect up our input to previous pipe stage's output
if (fdprev >= 0) {
dup2(fdprev,0);
CLOSEME(fdprev);
}
// connect output side of our pipe to stdout
if (fdpipe[1] >= 0) {
dup2(fdpipe[1],1);
CLOSEME(fdpipe[1]);
}
// child doesn't care about reading its own output
CLOSEME(fdpipe[0]);
if (opt_l)
fdshow(cmd->cmd_cldno);
// off we go ...
execvp(cmd->cmd_argv[0],cmd->cmd_argv);
}
CLOSEME(fdpipe[0]);
CLOSEME(fdpipe[1]);
if (opt_l)
fdshow(-1);
}
// pipewait -- wait for pipe stages to complete
void
pipewait(void)
{
pid_t pid;
int status;
int donecnt = 0;
while (donecnt < cmdcount) {
pid = waitpid(0,&status,0);
if (pid < 0)
break;
for (cmd_t *cmd = cmdlist; cmd < &cmdlist[cmdcount]; ++cmd) {
if (pid == cmd->cmd_pid) {
cmd->cmd_status = status;
++donecnt;
break;
}
}
}
}
// pipeclean -- free all storage
void
pipeclean(void)
{
for (cmd_t *cmd = cmdlist; cmd < &cmdlist[cmdcount]; ++cmd)
FREEME(cmd->cmd_argv);
FREEME(cmdlist);
cmdcount = 0;
}
// main -- main program
int
main(int argc,char **argv)
{
char *cp;
--argc;
++argv;
for (; argc > 0; --argc, ++argv) {
cp = *argv;
if (*cp != '-')
break;
switch (cp[1]) {
case 'd':
opt_d = ! opt_d;
break;
case 'l':
opt_l = ! opt_l;
break;
default:
break;
}
}
while (1) {
pipesplit();
pipefork();
pipewait();
pipeclean();
}
return 0;
}