【问题标题】:fd leak, custom Shellfd 泄漏,自定义 Shell
【发布时间】:2019-03-20 06:09:42
【问题描述】:

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

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

这是我的管道执行的代码 sn-p:

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);

【问题讨论】:

  • 虽然*(pipeline +1)只是pipeline[1]的另一种写法,但(*pipeline)[1]这个短语并没有任何意义。 (我知道。)它不能编译。你的意思是这样做吗?下标值不是数组、指针或向量。 (在这种情况下)
  • 你能发一些minimal reproducible example来说明execute_pipeline是如何被调用的吗?
  • 在我的帖子中添加了示例。
  • 一个简单的结构来封装每个命令会比使用原始char**数组更好。
  • @kamil:这些都不对。关闭意味着关闭。当你关闭一个 fd 时,它就关闭了。这并不意味着打开的东西会消失,因为它可能由不同的进程或不同的 fd 打开。但是 fd 关闭和打开总是会给你最低的可用 fd,所以它会被重用。

标签: c linux shell unix


【解决方案1】:

让我们准确地了解文件描述符,并记住在 fork 和 execvp 期间,文件描述符由子进程继承,除非标记为 w/ CLOEXEC 标志。检查手册页:

叉子(2): 子继承父的一组打开文件描述符的副本。子文件中的每个文件描述符与父文件中的相应文件描述符引用相同的打开文件描述(参见 open(2))。

打开(2): 默认情况下,新文件描述符设置为在 execve(2) 中保持打开状态(即 fcntl(2) 中描述的 FD_CLOEXEC 文件描述符标志最初被禁用);如下所述的 O_CLOEXEC 标志可用于更改此默认值。文件偏移量设置为文件的开头(参见 lseek(2))。

但我想,这种行为正是你在管道之后调用 fork 所依赖的......

没有别的意思,让我们画这个:

             stdin, stdout                                                                   
                   /\                                                                        
                  /  \                                                                       
                 /    \                                                                      
                /      \                                                                     
               / R; W;  \                                                                    
              /          \                                                                   
       Child -            - Parent                                                           
 stdin/out, R(del),W       stdin/out, R(fd_backup), W(del)                                   
                                  /\                                                         
                                 /  \                                                        
                                /    \                                                       
                               /      \                                                      
                              / R1; W1;\                                                     
                             /          \                                                    
                      Child -            - Parent                                            
               stdin/out, R(fd_backup),   stdin, stdout                                      
                            R1(del), W1   R(fd_backup - old);                                
                                          R1(fd_backup - new); W1(del)                       
                                                    / \                                      
                                                   /   \                                     
                                                  /     \                                    
                                                 /R2; W2;\                                   
                                                /         \                                  
                                               /           \                                 
                                        Child -             - Parent                         
                                 stdin, stdout,             stdin, stdout                    
                                R(fd_backup - old),         R (fd_backup - old),             
                                R1(fd_backup - new),        R1 (fd_backup - new),            
                                R2(del),W2                  R2 (fd_backup - newest!),                                                                                            

我希望图片是不言自明的。

子进程无论如何都会死掉,它们的所有 fd 都将被关闭(所以它们没有问题)。但是父进程留下了 3 个打开的 fd,并且它们随着每个管道的执行而不断增长。

【讨论】:

    【解决方案2】:

    正如 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;
    }
    

    【讨论】:

      猜你喜欢
      • 2015-04-29
      • 2011-03-17
      • 1970-01-01
      • 1970-01-01
      • 2017-08-24
      • 2018-07-15
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多