简体   繁体   English

尝试使用两个或多个管道实现Shell,但程序挂起-C

[英]Trying to implement a shell with two or more pipes but the program hangs - C

I've successfully implemented a small shell program with the ability to implement a pipe between two commands like 我已经成功实现了一个小型Shell程序,能够在两个命令之间实现管道

ls -l | ls -l | wc -l wc -l

However, when I try to implement one or more shells so I can, for instance, do 但是,当我尝试实现一个或多个外壳程序时,例如

ls -l | ls -l | wc -l | wc -l | wc -l wc -l

my program hangs until I ^C. 我的程序挂起,直到我^ C。

I've been wrapping my head around this for hours now and I can't seem to figure out what I'm doing wrong. 我已经花了好几个小时来解决这个问题,但似乎无法弄清楚自己在做什么错。 My approach was to create as many child processes as commands I have, all with the same father. 我的方法是创建与我拥有的命令一样多的子进程,所有子进程均由同一父亲创建。 Here's my full code: 这是我的完整代码:

#define ARGVMAX 100
#define LINESIZE 1024
#define PIPESYMB "|"
#define EXITSYMB "exit"


int makeargv(char *s, char *argv[]) {
    if ( s==NULL || argv==NULL || ARGVMAX==0)
        return -1;

    int ntokens = 0;
    argv[ntokens]=strtok(s, " \t\n");
    while ( (argv[ntokens]!=NULL) && (ntokens<ARGVMAX) ) {
        ntokens++;
        argv[ntokens]=strtok(NULL, " \t\n");
    }

    argv[ntokens] = NULL;
    return ntokens;
}

void changeOutput(int mypipe[]) {
    dup2(mypipe[1], 1);
    close(mypipe[0]);
    close(mypipe[1]);
}

void changeInput(int mypipe[]) {
    dup2(mypipe[0], 0);
    close(mypipe[1]);
    close(mypipe[0]);
}

void pipeFork(char *argv[], int i, int mypipe[]) {
    int h = i;
    int mypipe1[2];
    int found = 0;
    while((argv[h] != NULL) && !found) {
        if(!(strcmp(argv[h], PIPESYMB))) {
            argv[h] = NULL;
            found = 1;
        }
        h++;
    }
    if (pipe(mypipe1)==-1) 
                abort();
    switch ( fork() ) {
                    case -1: 
                        perror("fork error"); 
                        exit(1);
                    case 0:
                        changeInput(mypipe);
                        if(found)
                            changeOutput(mypipe1);
                        execvp( argv[i], &argv[i] );
                        perror("exec");
                        exit(1);
                    default:
                        if(found)
                            pipeFork(argv, h, mypipe1);
    }
    close(mypipe1[0]);
    close(mypipe1[1]); 
    wait(NULL);
}
void runcommand(char *argv[]) {
    int i = 0;
    int mypipe[2];
    int found = 0;
    if(!(strcmp(argv[0], EXITSYMB))) 
        exit(0);
    if (pipe(mypipe)==-1) 
                abort();
    while((argv[i] != NULL) && !found) {
        if(!(strcmp(argv[i], PIPESYMB))) {
            argv[i] = NULL;
            found = 1;
        }
        i++;
    }
    switch ( fork() ) {
        case -1: 
            perror("fork error"); 
            exit(1);
        case 0:
            if(found)
                changeOutput(mypipe);
            execvp( argv[0], argv );
            perror("exec");
            exit(1);
        default: 
             if(found)
               pipeFork(argv, i, mypipe);

    }
    close(mypipe[0]);
    close(mypipe[1]); 
    wait(NULL);
} 

int main(int argc, char *argv[]) {
    char line[LINESIZE];
    char* av[ARGVMAX];

    printf("> "); fflush(stdout);
    while ( fgets ( line, LINESIZE, stdin) != NULL ) {
        if ( makeargv( line, av) > 0 ) runcommand( av );
        printf("> "); fflush(stdout);
    }

    return 0;
}

This is my first time working with multi processes and, albeit this might have not been the best approach, I'm just now super curious has to where the mistake is. 这是我第一次使用多流程,尽管这可能不是最佳方法,但我现在非常想知道错误在哪里。

Thank you very much! 非常感谢你!

I expect you've long since resolved this, but… 我希望您早就解决了这个问题,但是…

Diagnosis 诊断

The primary problem, as ever, is not closing enough file descriptors. 与以往一样,主要问题是没有关闭足够的文件描述符。 A secondary problem is demonstrating this. 第二个问题是证明这一点。 I took your original code and added a fair amount of instrumentation — printing (to standard error). 我采用了您的原始代码,并添加了大量的检测工具-打印(标准错误)。 The messages are prefixed with the PID of the process doing the printing, which is rather important when there are multiple processes around. 消息的前缀是执行打印的进程的PID,这在周围有多个进程时非常重要。

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <unistd.h>

#define ARGVMAX 100
#define LINESIZE 1024
#define PIPESYMB "|"
#define EXITSYMB "exit"

static void dump_fds(int max_fd)
{
    char buffer[64];
    char *base = buffer + snprintf(buffer, sizeof(buffer), "%d: fds ", (int)getpid());
    for (int i = 0; i < max_fd; i++)
    {
        struct stat sb;
        if (fstat(i, &sb) == 0)
            *base++ = 'o';
        else
            *base++ = '-';
    }
    *base = '\0';
    fprintf(stderr, "%s\n", buffer);
}

static void dump_argv(const char *tag, char **argv)
{
    fprintf(stderr, "%d: %s:\n", (int)getpid(), tag);
    int i = 0;
    while (*argv)
        fprintf(stderr, "%d: argv[%d] = \"%s\"\n", (int)getpid(), i++, *argv++);
    dump_fds(20);
}

static int makeargv(char *s, char *argv[])
{
    if (s == NULL || argv == NULL || ARGVMAX == 0)
        return -1;

    int ntokens = 0;
    argv[ntokens] = strtok(s, " \t\n");
    while ((argv[ntokens] != NULL) && (ntokens < ARGVMAX))
    {
        ntokens++;
        argv[ntokens] = strtok(NULL, " \t\n");
    }

    argv[ntokens] = NULL;
    return ntokens;
}

static void changeOutput(int mypipe[])
{
    fprintf(stderr, "%d: (%d closed) (%d to 1)\n", (int)getpid(), mypipe[0], mypipe[1]);
    dup2(mypipe[1], 1);
    close(mypipe[0]);
    close(mypipe[1]);
}

static void changeInput(int mypipe[])
{
    fprintf(stderr, "%d: (%d to 0) (%d closed)\n", (int)getpid(), mypipe[0], mypipe[1]);
    dup2(mypipe[0], 0);
    close(mypipe[1]);
    close(mypipe[0]);
}

static void wait_for(int pid)
{
    int corpse;
    int status;
    while ((corpse = wait(&status)) > 0)
    {
        fprintf(stderr, "%d: child %d exit status 0x%.4X\n", (int)getpid(), corpse, status);
        if (pid == 0 || corpse == pid)
            break;
    }
}

static void pipeFork(char *argv[], int i, int mypipe[])
{
    int h = i;
    int mypipe1[2];
    int found = 0;
    dump_argv("pipeFork", &argv[h]);
    while ((argv[h] != NULL) && !found)
    {
        if (!(strcmp(argv[h], PIPESYMB)))
        {
            argv[h] = NULL;
            found = 1;
        }
        h++;
    }
    if (pipe(mypipe1) == -1)
        abort();
    fprintf(stderr, "%d: %s - pipe (%d,%d)\n", (int)getpid(), __func__, mypipe1[0], mypipe1[1]);
    int pid = fork();
    switch (pid)
    {
    case -1:
        perror("fork error");
        exit(1);
    case 0:
        fprintf(stderr, "%d: pipeFork - child\n", (int)getpid());
        changeInput(mypipe);
        if (found)
            changeOutput(mypipe1);
        dump_argv("pipefork:execvp", &argv[i]);
        execvp(argv[i], &argv[i]);
        perror("exec");
        exit(1);
    default:
        fprintf(stderr, "%d: forked child %d\n", (int)getpid(), pid);
        if (found)
        {
            dump_argv("recurse-pipeFork", &argv[h]);
            pipeFork(argv, h, mypipe1);
        }
        break;
    }
    fprintf(stderr, "%d: pipeFork: close %d %d\n", (int)getpid(), mypipe1[0], mypipe1[1]);
    close(mypipe1[0]);
    close(mypipe1[1]);
    fprintf(stderr, "%d: waiting in pipeFork for %d\n", (int)getpid(), pid);
    wait_for(0);
}

static void runcommand(char *argv[])
{
    int i = 0;
    int mypipe[2];
    int found = 0;
    if (!(strcmp(argv[0], EXITSYMB)))
        exit(0);
    if (pipe(mypipe) == -1)
        abort();
    fprintf(stderr, "%d: %s - pipe (%d,%d)\n", (int)getpid(), __func__, mypipe[0], mypipe[1]);
    while ((argv[i] != NULL) && !found)
    {
        if (!(strcmp(argv[i], PIPESYMB)))
        {
            argv[i] = NULL;
            found = 1;
        }
        i++;
    }
    int pid = fork();
    switch (pid)
    {
    case -1:
        perror("fork error");
        exit(1);
    case 0:
        fprintf(stderr, "%d: runcommand - child\n", (int)getpid());
        if (found)
            changeOutput(mypipe);
        dump_argv("about to exec", argv);
        execvp(argv[0], argv);
        perror("exec");
        exit(1);
    default:
        fprintf(stderr, "%d: forked child %d\n", (int)getpid(), pid);
        if (found)
        {
            dump_argv("call-pipeFork", &argv[i]);
            pipeFork(argv, i, mypipe);
        }
        break;
    }
    fprintf(stderr, "%d: runcommand: close %d %d\n", (int)getpid(), mypipe[0], mypipe[1]);
    close(mypipe[0]);
    close(mypipe[1]);
    fprintf(stderr, "%d: waiting in runcommand for %d\n", (int)getpid(), pid);
    wait_for(0);
}

int main(void)
{
    char line[LINESIZE];
    char *av[ARGVMAX];

    printf("> ");
    fflush(stdout);
    while (fgets(line, LINESIZE, stdin) != NULL)
    {
        if (makeargv(line, av) > 0)
        {
            dump_argv("after reading", av);
            runcommand(av);
        }
        printf("> ");
        fflush(stdout);
    }

    return 0;
}

The dump_fds() function builds the buffer and prints a single line because when it was outputting characters piecemeal, I got interference between processes running the loop at the same time. dump_fds()函数将构建缓冲区并打印一行,因为当它dump_fds()输出字符时,我在同时运行循环的进程之间受到了干扰。

With the instrumentation in place, we can see what happens with ls | wc 使用适当的工具,我们可以看到ls | wc发生了什么ls | wc ls | wc : ls | wc

$ ./shell23
> ls | wc
6418: after reading:
6418: argv[0] = "ls"
6418: argv[1] = "|"
6418: argv[2] = "wc"
6418: fds ooo-----------------
6418: runcommand - pipe (3,4)
6418: forked child 6419
6418: call-pipeFork:
6418: argv[0] = "wc"
6418: fds ooooo---------------
6418: pipeFork:
6418: argv[0] = "wc"
6418: fds ooooo---------------
6418: pipeFork - pipe (5,6)
6418: forked child 6420
6418: pipeFork: close 5 6
6419: runcommand - child
6419: (3 closed) (4 to 1)
6418: waiting in pipeFork for 6420
6419: about to exec:
6419: argv[0] = "ls"
6419: fds ooo-----------------
6420: pipeFork - child
6420: (3 to 0) (4 closed)
6420: pipefork:execvp:
6420: argv[0] = "wc"
6420: fds ooo--oo-------------
6418: child 6419 exit status 0x0000
6418: runcommand: close 3 4
6418: waiting in runcommand for 6419
       8       8      81
6418: child 6420 exit status 0x0000
> 
> …

If you look at the fds output for process 6420 just before the execvp() , you can see that file descriptors 5 and 6 are still open. 如果您在execvp()之前查看进程6420的fds输出,则可以看到文件描述符5和6仍处于打开状态。 Fortunately, in this case, it doesn't do any damage; 幸运的是,在这种情况下,它不会造成任何损坏。 ls (6419) has just three desriptors open (as it should) and when it exits, the input for wc (6420) is closed. ls (6419)仅打开了三个描述符(应按要求),并且退出时, wc (6420)的输入已关闭。

The trace for ls | wc | wc ls | wc | wc的跟踪ls | wc | wc ls | wc | wc ls | wc | wc is more problematic: ls | wc | wc更成问题:

> …
>
> ls | wc | wc
6418: after reading:
6418: argv[0] = "ls"
6418: argv[1] = "|"
6418: argv[2] = "wc"
6418: argv[3] = "|"
6418: argv[4] = "wc"
6418: fds ooo-----------------
6418: runcommand - pipe (3,4)
6418: forked child 6421
6418: call-pipeFork:
6418: argv[0] = "wc"
6418: argv[1] = "|"
6418: argv[2] = "wc"
6418: fds ooooo---------------
6418: pipeFork:
6418: argv[0] = "wc"
6418: argv[1] = "|"
6418: argv[2] = "wc"
6418: fds ooooo---------------
6418: pipeFork - pipe (5,6)
6418: forked child 6422
6421: runcommand - child
6421: (3 closed) (4 to 1)
6418: recurse-pipeFork:
6418: argv[0] = "wc"
6418: fds ooooooo-------------
6418: pipeFork:
6418: argv[0] = "wc"
6418: fds ooooooo-------------
6421: about to exec:
6418: pipeFork - pipe (7,8)
6421: argv[0] = "ls"
6421: fds ooo-----------------
6422: pipeFork - child
6422: (3 to 0) (4 closed)
6418: forked child 6423
6418: pipeFork: close 7 8
6418: waiting in pipeFork for 6423
6422: (5 closed) (6 to 1)
6422: pipefork:execvp:
6422: argv[0] = "wc"
6422: fds ooo-----------------
6423: pipeFork - child
6423: (5 to 0) (6 closed)
6423: pipefork:execvp:
6423: argv[0] = "wc"
6423: fds ooooo--oo-----------
6418: child 6421 exit status 0x0000
6418: pipeFork: close 5 6
6418: waiting in pipeFork for 6422
^C
$

Processes 6421 ( ls ) and 6422 ( wc the first) are OK, but 6423 has file descriptors 3, 4, 7, 8 open (as well as 0, 1, 2), and this time, these cause trouble; 进程6421( ls )和6422(首先是wc )可以,但是6423具有打开的文件描述符3、4、7、8(以及0、1、2),这一次会引起麻烦; one of them is the writing end of the standard input pipe, so EOF cannot be reported because there's a process (6423) that could write to the pipe if it weren't hung up on reading from it. 其中之一是标准输入管道的写入端,因此无法报告EOF,因为有一个进程(6423)可以在不中断读取的情况下将其写入管道。

There's also a chance that the parent process (6418 in this example) is keeping pipe file descriptors open inappropriately — a modified version of the code used working on the prescription (below) showed the parent process with descriptors 0-4 open when waiting for the second wc to complete. 父进程(在此示例中为6418)也有可能不适当地保持打开管道文件描述符的状态-在处方上使用的代码的修改版本(如下所示)显示,当等待父进程的描述符为0-4时打开第二个wc完成。

Prescription 处方

The simple prescription is "close enough file descriptors". 简单的规定是“关闭足够的文件描述符”。

Actually closing the correct file descriptors is a bigger problem. 实际上,关闭正确的文件描述符是一个更大的问题。 On the recursive call to pipeFork() from within pipeFork() , it is important to close the mypipe file descriptors; 在递归调用pipeFork()从内部pipeFork()它关闭是重要mypipe文件描述符; the recursive function uses the mypipe1 file descriptors passed in and knows nothing about the others and doesn't need them. 递归函数使用传入的mypipe1文件描述符,而对其他文件一无所知,并且不需要它们。 Similarly, the parent shell must close the mypipe file descriptors (as well as the mypipe1 file descriptors) before waiting. 同样,父外壳程序必须在等待之前关闭mypipe文件描述符(以及mypipe1文件描述符)。 Also, it is sensible that the last child does not create an unused pipe as happened before. 另外,明智的做法是最后一个子项不会像以前那样创建未使用的管道。

/* SO 4672-0692 */

#include <stdarg.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <unistd.h>

#define ARGVMAX 100
#define LINESIZE 1024
#define PIPESYMB "|"
#define EXITSYMB "exit"

static void logmsg(const char *func, const char *fmt, ...)
{
    char buffer[1024];
    char *base = buffer + snprintf(buffer, sizeof(buffer), "%d: %s(): ", (int)getpid(), func);
    va_list args;
    va_start(args, fmt);
    vsnprintf(base, sizeof(buffer) - (base - buffer), fmt, args);
    va_end(args);
    fprintf(stderr, "%s\n", buffer);
}

static void dump_fds(const char *func, int max_fd)
{
    char buffer[64] = "fds ";
    char *base = buffer + strlen(buffer);
    for (int i = 0; i < max_fd; i++)
    {
        struct stat sb;
        if (fstat(i, &sb) == 0)
            *base++ = 'o';
        else
            *base++ = '-';
    }
    *base = '\0';
    logmsg(func, "%s", buffer);
}

static void dump_argv(const char *func, const char *tag, char **argv)
{
    logmsg(func, "%s:", tag);
    int i = 0;
    while (*argv != 0)
        logmsg(func, "argv[%d] = \"%s\"", i++, *argv++);
    dump_fds(func, 20);
}

static int makeargv(char *s, char *argv[])
{
    if (s == NULL || argv == NULL || ARGVMAX == 0)
        return -1;

    int ntokens = 0;
    argv[ntokens] = strtok(s, " \t\n");
    while ((argv[ntokens] != NULL) && (ntokens < ARGVMAX))
    {
        ntokens++;
        argv[ntokens] = strtok(NULL, " \t\n");
    }

    argv[ntokens] = NULL;
    return ntokens;
}

static void changeOutput(int mypipe[])
{
    logmsg(__func__, "(%d closed) (%d to 1)", mypipe[0], mypipe[1]);
    dup2(mypipe[1], 1);
    close(mypipe[0]);
    close(mypipe[1]);
}

static void changeInput(int mypipe[])
{
    logmsg(__func__, "(%d to 0) (%d closed)", mypipe[0], mypipe[1]);
    dup2(mypipe[0], 0);
    close(mypipe[1]);
    close(mypipe[0]);
}

static void wait_for(const char *func, int pid)
{
    int corpse;
    int status;
    dump_fds(__func__, 20);
    while ((corpse = waitpid(pid, &status, 0)) > 0)
    {
        logmsg(func, "child %d exit status 0x%.4X", corpse, status);
        if (pid == 0 || corpse == pid)
            break;
    }
}

static void pipeFork(char *argv[], int i, int mypipe[])
{
    int h = i;
    int mypipe1[2];
    int found = 0;
    dump_argv(__func__, "entry", &argv[h]);
    while ((argv[h] != NULL) && !found)
    {
        if (!(strcmp(argv[h], PIPESYMB)))
        {
            argv[h] = NULL;
            found = 1;
        }
        h++;
    }
    if (found)
    {
        if (pipe(mypipe1) == -1)
            abort();
        logmsg(__func__, "pipe (%d,%d)", mypipe1[0], mypipe1[1]);
    }
    int pid = fork();
    switch (pid)
    {
    case -1:
        perror("fork error");
        exit(1);
    case 0:
        logmsg(__func__, "- child");
        changeInput(mypipe);
        if (found)
            changeOutput(mypipe1);
        dump_argv(__func__, "execvp", &argv[i]);
        execvp(argv[i], &argv[i]);
        perror("exec");
        exit(1);
    default:
        logmsg(__func__, "forked child %d", pid);
        if (found)
        {
            close(mypipe[0]);
            close(mypipe[1]);
            dump_argv(__func__, "recurse", &argv[h]);
            pipeFork(argv, h, mypipe1);
        }
        break;
    }
    if (found)
    {
        logmsg(__func__, "close %d %d", mypipe1[0], mypipe1[1]);
        close(mypipe1[0]);
        close(mypipe1[1]);
    }
    logmsg(__func__, "close %d %d", mypipe[0], mypipe[1]);
    close(mypipe[0]);
    close(mypipe[1]);
    logmsg(__func__, "waiting for %d", pid);
    wait_for(__func__, pid);
}

static void runcommand(char *argv[])
{
    int i = 0;
    int mypipe[2];
    int found = 0;
    fflush(0);
    if (!(strcmp(argv[0], EXITSYMB)))
        exit(0);
    if (pipe(mypipe) == -1)
        abort();
    logmsg(__func__, "pipe (%d,%d)", mypipe[0], mypipe[1]);
    while ((argv[i] != NULL) && !found)
    {
        if (!(strcmp(argv[i], PIPESYMB)))
        {
            argv[i] = NULL;
            found = 1;
        }
        i++;
    }
    int pid = fork();
    switch (pid)
    {
    case -1:
        perror("fork error");
        exit(1);
    case 0:
        logmsg(__func__, "- child");
        if (found)
            changeOutput(mypipe);
        dump_argv(__func__, "execvp", argv);
        execvp(argv[0], argv);
        perror("exec");
        exit(1);
    default:
        logmsg(__func__, "forked child %d", pid);
        if (found)
        {
            dump_argv(__func__, "call-pipeFork", &argv[i]);
            pipeFork(argv, i, mypipe);
        }
        break;
    }
    logmsg(__func__, "close %d %d", mypipe[0], mypipe[1]);
    close(mypipe[0]);
    close(mypipe[1]);
    logmsg(__func__, "waiting for %d", pid);
    wait_for(__func__, pid);
}

int main(void)
{
    char line[LINESIZE];
    char *av[ARGVMAX];

    printf("> ");
    fflush(stdout);
    while (fgets(line, LINESIZE, stdin) != NULL)
    {
        if (makeargv(line, av) > 0)
        {
            dump_argv(__func__, "after reading", av);
            runcommand(av);
        }
        printf("> ");
        fflush(stdout);
    }
    putchar('\n');

    return 0;
}

When built and run, a sample trace from shell79 is: 构建并运行后,来自shell79的示例跟踪为:

$ ./shell79
> ls | wc | wc
7182: main(): after reading:
7182: main(): argv[0] = "ls"
7182: main(): argv[1] = "|"
7182: main(): argv[2] = "wc"
7182: main(): argv[3] = "|"
7182: main(): argv[4] = "wc"
7182: main(): fds ooo-----------------
7182: runcommand(): pipe (3,4)
7182: runcommand(): forked child 7183
7182: runcommand(): call-pipeFork:
7182: runcommand(): argv[0] = "wc"
7182: runcommand(): argv[1] = "|"
7182: runcommand(): argv[2] = "wc"
7182: runcommand(): fds ooooo---------------
7182: pipeFork(): entry:
7182: pipeFork(): argv[0] = "wc"
7182: pipeFork(): argv[1] = "|"
7182: pipeFork(): argv[2] = "wc"
7182: pipeFork(): fds ooooo---------------
7182: pipeFork(): pipe (5,6)
7182: pipeFork(): forked child 7184
7182: pipeFork(): recurse:
7182: pipeFork(): argv[0] = "wc"
7182: pipeFork(): fds ooo--oo-------------
7182: pipeFork(): entry:
7182: pipeFork(): argv[0] = "wc"
7183: runcommand(): - child
7183: changeOutput(): (3 closed) (4 to 1)
7182: pipeFork(): fds ooo--oo-------------
7183: runcommand(): execvp:
7183: runcommand(): argv[0] = "ls"
7183: runcommand(): fds ooo-----------------
7182: pipeFork(): forked child 7185
7182: pipeFork(): close 5 6
7182: pipeFork(): waiting for 7185
7184: pipeFork(): - child
7184: changeInput(): (3 to 0) (4 closed)
7182: wait_for(): fds ooo-----------------
7184: changeOutput(): (5 closed) (6 to 1)
7184: pipeFork(): execvp:
7184: pipeFork(): argv[0] = "wc"
7184: pipeFork(): fds ooo-----------------
7185: pipeFork(): - child
7185: changeInput(): (5 to 0) (6 closed)
7185: pipeFork(): execvp:
7185: pipeFork(): argv[0] = "wc"
7185: pipeFork(): fds ooo-----------------
       1       3      25
7182: pipeFork(): child 7185 exit status 0x0000
7182: pipeFork(): close 5 6
7182: pipeFork(): close 3 4
7182: pipeFork(): waiting for 7184
7182: wait_for(): fds ooo-----------------
7182: pipeFork(): child 7184 exit status 0x0000
7182: runcommand(): close 3 4
7182: runcommand(): waiting for 7183
7182: wait_for(): fds ooo-----------------
7182: runcommand(): child 7183 exit status 0x0000
> ^D
$

This looks clean; 这看起来很干净; there are no stray open file descriptors. 没有杂散的打开文件描述符。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM