fd 泄漏,自定义 Shell [英] fd leak, custom Shell

查看:19
本文介绍了fd 泄漏,自定义 Shell的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

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

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

enum PIPES {READ, WRITE};void execute_pipeline(char*** 管道){int fd[2];int fd_backup = 0;pid_t child_pid;while (*pipeline != ''){管道(FD);child_pid = fork();如果(child_pid == -1){错误(叉");退出(1);}否则如果(child_pid == 0){dup2(fd_backup, 0);//(旧的,新的)关闭(FD [阅读]);if(*(pipeline + 1) != ''){dup2(fd[写],1);}execvp((*pipeline)[0], *pipeline);退出(1);}else//父进程{等待(空);关闭(FD [写]);fd_backup = fd[读];管道++;}}}

编辑

一个如何调用execute_pipeline的例子:

char *ls[] = {"ls", "-l", NULL};char *sort[] = {"sort", "-r", NULL};char *head[] = {"head", "-n", "3", NULL};char **pipeline[] = {ls, sort, head, NULL};执行管道(管道);

解决方案

正如 tadman 所指出的,使用命令结构来传递信息更容易.

我们不能[好吧,我们可以不应该]在管道期间[在父级中]做一个wait>建设.稍后必须是一个单独的循环.我们会在第一个孩子创建后挂起父母.

如果第一个孩子有大量输出,内核管道缓冲区可能会填满,第一个孩子会阻塞.但是,由于第二个孩子还没有被创建,所以没有什么可以读取/排出第一个孩子的输出并解除阻塞.

此外,在执行 dup2 后关闭管道单元并确保之前的管道阶段单元在父级中关闭也很重要.

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

至于您最初的文件描述符泄漏问题,我认为我通过添加更多 close 调用解决了这个问题.该程序对此有一些自验证码:

#include #include #include #include #include #include #include #define FREEME(ptr_) 做 { 如果 (ptr_ == NULL) 休息;免费(ptr_);ptr_ = NULL;} 而 (0)#define CLOSEME(fd_) 做 { 如果 (fd_ <0) 休息;关闭(FD_);fd_ = -1;} 而 (0)//命令控制类型定义结构{无符号整数 cmd_opt;//选项int cmd_cldno;//孩子编号字符 *cmd_buf;//命令缓冲区int cmd_argc;//参数计数字符 **cmd_argv;//参数int cmd_pipe[2];//管道单位pid_t cmd_pid;//子进程号int cmd_status;//子状态cmd_t;#define CMD_FIRST (1u <<0)#define CMD_LAST (1u <<1)字符线缓冲[1000];int cmdcount;cmd_t *cmdlist;int opt_d;int opt_l;#define dbg(fmt_...) 做 { 如果 (opt_d) printf(fmt_);} 而 (0)//显示打开的 fd空白fdshow1(int cldid){字符缓冲区[100];fprintf(stderr,"CLD: %d
",cldid);sprintf(buf,"ls -l/proc/%d/fd 1>&2",getpid());系统(缓冲);}//显示打开的 fd空白fdshow2(int cldid){字符目录[100];字符 lnkfm[1000];字符 lnkto[1000];内里;目录 *xf;struct dirent *ent;字符 *bp;字符 obuf[1000];sprintf(dir,"/proc/%d/fd",getpid());xf = opendir(dir);bp = obuf;bp += sprintf(bp,"%d:",cldid);而 (1) {ent = readdir(xf);if (ent == NULL)休息;if (strcmp(ent->d_name,".") == 0)继续;如果 (strcmp(ent->d_name,"..") == 0)继续;sprintf(lnkfm,"%s/%s",dir,ent->d_name);len = readlink(lnkfm,lnkto,sizeof(lnkto));lnkto[len] = 0;如果(strstr(lnkto,管道")!= 0)bp += sprintf(bp,"%s-->%s",ent->d_name,lnkto);开关(ent->d_type){案例DT_FIFO:休息;}}bp += sprintf(bp,"
");fputs(obuf,stderr);fflush(stderr);关闭 (xf);}//显示打开的 fd空白fdshow(int cldid){fdshow2(cldid);}//pipeadd -- 添加单个命令到管道空白pipeadd(char *buf){字符 *cp;字符 *bp;字符 *sv;cmd_t *cmd;dbg("pipeadd: buf='%s'
",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;++命令计数;bp = buf;而 (1) {cp = strtok_r(bp," 	",&sv);bp = NULL;如果(cp == NULL)休息;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 -- 读入并拆分命令空白管道分裂(无效){字符 *cp;字符 *bp;字符 *sv;cmd_t *cmd;printf(">");fflush(标准输出);fgets(linebuf,sizeof(linebuf),stdin);cp = strchr(linebuf,'
');如果(cp!= NULL)*cp = 0;bp = 行缓冲;而 (1) {cp = strtok_r(bp,"|",&sv);bp = NULL;如果(cp == NULL)休息;管道添加(cp);}cmd = &cmdlist[0];cmd-> cmd_opt |= CMD_FIRST;cmd = &cmdlist[cmdcount - 1];cmd->cmd_opt |= CMD_LAST;如果(opt_d){for (cmd_t *cmd = cmdlist; cmd < &cmdlist[cmdcount]; ++cmd) {dbg("%d:",cmd->cmd_cldno);for (int argc = 0; argc cmd_argc;++argc)dbg(" '%s'",cmd->cmd_argv[argc]);dbg("
");}}}//pipefork -- 管道的分叉元素空白管叉(空){cmd_t *cmd;int fdprev = -1;int fdpipe[2] = { -1, -1 };for (cmd = cmdlist; cmd < &cmdlist[cmdcount]; ++cmd) {//父母和孩子都应该关闭前一个管道的输出端关闭(fdpipe [1]);//为当前孩子的输出创建一个新管道如果(cmd-> cmd_opt & CMD_LAST){fdpipe[0] = -1;fdpipe[1] = -1;}别的管道(fdpipe);cmd->cmd_pid = fork();如果(cmd-> cmd_pid <0){printf("pipefork: fork 失败 -- %s
",strerror(errno));退出(1);}//下一个管道阶段的输入端的父级如果(cmd-> cmd_pid != 0){关闭(fdprev);fdprev = fdpipe[0];继续;}//将我们的输入连接到前一个管道阶段的输出如果 (fdprev >= 0) {dup2(fdprev,0);关闭(fdprev);}//将管道的输出端连接到标准输出如果 (fdpipe[1] >= 0) {dup2(fdpipe[1],1);关闭(fdpipe [1]);}//孩子不关心读取自己的输出关闭(fdpipe [0]);如果(opt_l)fdshow(cmd->cmd_cldno);//我们走...execvp(cmd->cmd_argv[0],cmd->cmd_argv);}关闭(fdpipe [0]);关闭(fdpipe [1]);如果(opt_l)fdshow(-1);}//pipewait -- 等待管道阶段完成空白管道等待(无效){pid_t pid;内部状态;int donecnt = 0;while (donecnt < cmdcount) {pid = waitpid(0,&status,0);如果 (pid <0)休息;for (cmd_t *cmd = cmdlist; cmd < &cmdlist[cmdcount]; ++cmd) {如果(pid == cmd-> cmd_pid){cmd->cmd_status = 状态;++完成;休息;}}}}//pipeclean -- 释放所有存储空间空白管道清洁(无效){for (cmd_t *cmd = cmdlist; cmd <&cmdlist[cmdcount]; ++cmd)自由(cmd-> cmd_argv);免费(命令列表);cmdcount = 0;}//main -- 主程序整数主要(int argc,char **argv){字符 *cp;--argc;++argv;for (; argc > 0; --argc, ++argv) {cp = *argv;如果 (*cp != '-')休息;开关(cp[1]){案例d":opt_d = !opt_d;休息;案例'l':opt_l = !opt_l;休息;默认:休息;}}而 (1) {管道分裂();管叉();管道等待();管道清洁();}返回0;}

I'm working on a custom shell that can handle multiple pipes. But every time I execute a new pipeline and check the process with ls -l /proc/pid/fd I get something like in the picture below and the list keeps expanding with every new pipeline executed:

Question: Is this considered as a fd leak? And how do I fix it?

Here's a code snippet for my pipeline execution:

enum PIPES {READ, WRITE};

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

    while (*pipeline != '')
    {
        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) != '')
            {
                dup2(fd[WRITE], 1);
            }
            execvp((*pipeline)[0], *pipeline);
            exit(1);
        }
        else// Parent process
        {
            wait(NULL);
            close(fd[WRITE]);
            fd_backup = fd[READ];
            pipeline++;
        }
    }
}

EDIT

An example how to call 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);

解决方案

As tadman pointed out, it is easier to use a command struct to pass things around.

We can't [well, we could but shouldn't] do a wait [in the parent] during pipe construction. That has to be a separate loop later. We would hang the parent after the first child is created.

If the first child had a large amount of output, the kernel pipe buffers might fill up and the first child would block. But, since the second child has not been created, there is nothing to read/drain the first child's output and unblock it.

Also, it is important to close the pipe units after doing dup2 and ensure the previous pipe stage units are closed in the parent.

Here's a refactored version that does all that.

As to your original issue with file descriptor leakage, I think I fixed that by adding some more close calls. The program has some self verification code on this:

#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
",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,"
");
    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'
",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," 	",&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,'
');
    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("
");
        }
    }
}

// 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
",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;
}

这篇关于fd 泄漏,自定义 Shell的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆