fd泄漏,自定义Shell

提问

我正在开发一个可以处理多个管道的自定义shell.但每次我执行一个新的管道并用ls -l / proc / pid / fd检查进程时,我会得到如下图所示的内容,并且列表会随着每个新管道的执行而不断扩展:

screenshot of terminal

问题:这被视为fd泄漏吗?我该如何解决?

这是我的管道执行的代码片段:

enum PIPES {READ, WRITE};

void execute_pipeline(char*** pipeline)
{
    int fd[2];
    int fd_backup = 0;
    pid_t child_pid;

    while (*pipeline != '\0')
    {
        pipe(fd);
        child_pid = fork();

        if(child_pid == -1)
        {
            perror("fork");
            exit(1);
        }
        else if(child_pid == 0)
        {
            dup2(fd_backup, 0);// (old, new)
            close(fd[READ]);

            if(*(pipeline + 1) != '\0')
            {
                dup2(fd[WRITE], 1);
            }
            execvp((*pipeline)[0], *pipeline);
            exit(1);
        }
        else// Parent process
        {
            wait(NULL);
            close(fd[WRITE]);
            fd_backup = fd[READ];
            pipeline++;
        }
    }
}

编辑

如何调用execute_pipeline的示例:

char *ls[] = {"ls", "-l", NULL};
char *sort[] = {"sort", "-r", NULL};
char *head[] = {"head", "-n", "3", NULL};
char **pipeline[] = {ls, sort, head, NULL};

execute_pipeline(pipeline);

最佳答案

正如tadman指出的那样,使用命令结构来传递内容更容易.

在管道施工期间,我们不能[嗯,我们可以但不应该]在[父母]中等待.这必须是一个单独的循环.我们会在创建第一个子节点后挂起父节点.

如果第一个子节点有大量输出,则内核管道缓冲区可能会填满,第一个子节点会阻塞.但是,由于尚未创建第二个孩子,因此没有任何东西可以读取/排出第一个孩子的输出并解除阻塞.

此外,在执行dup2之后关闭管道单元并确保先前的管道单元在父级中关闭非常重要.

这是一个完成所有这些的重构版本.

关于文件描述符泄漏的原始问题,我想我通过添加一些更接近的调用来解决这个问题.该程序有一些自我验证代码:

#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;
}