2012-06-09 55 views
7

我正努力爲類實現一個具有管道的shell。讀取C塊上的UNIX管道

typedef struct { 
    char** cmd; 
    int in[2]; 
    int out[2]; 
} cmdio; 

cmdio cmds[MAX_PIPE + 1]; 

讀取管道中的命令並將其存儲在cmds中。

cmdio[i].in是由pipe()返回的輸入管道的一對文件描述符。對於從終端輸入讀取的第一個命令,它只是{fileno(stdin),-1}。 cmdin[i].out與輸出管道/終端輸出類似。 cmdio[i].incmd[i-1].out相同。例如:

$ ls -l | sort | wc 

CMD: ls -l 
IN: 0 -1 
OUT: 3 4 

CMD: sort 
IN: 3 4 
OUT: 5 6 

CMD: wc 
IN: 5 6 
OUT: -1 1 

我們通過每個命令process_command,它會做很多事情:

for (cmdi = 0; cmds[cmdi].cmd != NULL; cmdi++) { 
    process_command(&cmds[cmdi]); 
} 

現在,裏面process_command:

if (!(pid_fork = fork())) { 
    dup2(cmd->in[0], fileno(stdin)); 
    dup2(cmd->out[1], fileno(stdout));  
    if (cmd->in[1] >= 0) { 
     if (close(cmd->in[1])) { 
      perror(NULL); 
     } 
    } 
    if (cmd->out[0] >= 0) { 
     if (close(cmd->out[0])) { 
      perror(NULL); 
     } 
    } 
    execvp(cmd->cmd[0], cmd->cmd); 
    exit(-1); 
} 

的問題是,從閱讀永久管塊:

COMMAND $ ls | wc 
Created pipe, in: 5 out: 6 
Foreground pid: 9042, command: ls, Exited, info: 0 
[blocked running read() within wc] 

如果,而不是用execvp交換的過程中,我只是這樣做:

if (!(pid_fork = fork())) { 
    dup2(cmd->in[0], fileno(stdin)); 
    dup2(cmd->out[1], fileno(stdout)); 
    if (cmd->in[1] >= 0) { 
     if (close(cmd->in[1])) { 
      perror(NULL); 
     } 
    } 
    if (cmd->out[0] >= 0) { 
     if (close(cmd->out[0])) { 
      perror(NULL); 
     } 
    } 

    char buf[6]; 
    read(fileno(stdin), buf, 5); 
    buf[5] = '\0'; 

    printf("%s\n", buf); 
    exit(0); 
} 

它發生的工作:

COMMAND $ cmd1 | cmd2 | cmd3 | cmd4 | cmd5 
Pipe creada, in: 11 out: 12 
Pipe creada, in: 13 out: 14 
Pipe creada, in: 15 out: 16 
Pipe creada, in: 17 out: 18 
hola! 
Foreground pid: 9251, command: cmd1, Exited, info: 0 
Foreground pid: 9252, command: cmd2, Exited, info: 0 
Foreground pid: 9253, command: cmd3, Exited, info: 0 
Foreground pid: 9254, command: cmd4, Exited, info: 0 
hola! 
Foreground pid: 9255, command: cmd5, Exited, info: 0 

可能是什麼問題呢?

回答

4

您沒有足夠的關閉。在代碼:

if (!(pid_fork = fork())) { 
    dup2(cmd->in[0], fileno(stdin)); 
    dup2(cmd->out[1], fileno(stdout));  
    if (cmd->in[1] >= 0) { 
     if (close(cmd->in[1])) { 
      perror(NULL); 
     } 
    } 
    if (cmd->out[0] >= 0) { 
     if (close(cmd->out[0])) { 
      perror(NULL); 
     } 
    } 
    execvp(cmd->cmd[0], cmd->cmd); 
    exit(-1); 
} 

你複製的管道stdinstdout(通過fileno())後,需要關閉該管道:

if (!(pid_fork = fork())) { 
    dup2(cmd->in[0], fileno(stdin)); 
    dup2(cmd->out[1], fileno(stdout));  
    if (cmd->in[1] >= 0) { 
     if (close(cmd->in[1])) { 
      perror(NULL); 
     } 
    } 
    if (cmd->out[0] >= 0) { 
     if (close(cmd->out[0])) { 
      perror(NULL); 
     } 
    } 
    close(cmd->in[0]); // Or your error checked version, but I'd use a function 
    close(cmd->out[1]);  
    execvp(cmd->cmd[0], cmd->cmd); 
    exit(-1); 
} 

的程序沒有完成,因爲有該文件的寫入結束仍然打開。此外,不要忘記,如果父進程(shell)創建管道,它必須關閉管道的兩端。沒有關閉足夠的管道可能是開始學習管道管道時最常見的錯誤。

+0

哼,不起作用:\它仍然是相同的,除了標準輸入/標準輸出關閉運行命令之前,所以例如'貓'失敗與'貓:stdin:錯誤的文件描述符。但即使我避免關閉stdin/out,其行爲與以前相同。 據我所知,'cmd - >(in | out)[(0 | 1)]'指的是與'fileno(std(in | out)'相同的底層文件,不是嗎? –

+0

,每個管道都有一個讀描述符和一個寫描述符,據我所知,父和子之間(或兩個子之間)有兩個管道。在每個子進程中,你只需要在'stdin'上打開一個管道讀端的'dup2()',並在'stdout'上打開另一個管道寫端的'dup2()';應該關閉管道的所有四個末端(這是人們的驚喜)。我不確定是否有足夠的代碼來說明還有什麼問題。 –

+0

謝謝喬納森,我沒有像你說的那樣關閉父母管道的兩端。非常感謝! –

1

好的,我終於解決了。

在父進程,就整個兒叉後,我把:

f (cmd->in[0] != fileno(stdin)) { 
    close(cmd->in[0]); 
    close(cmd->in[1]); 
} 

瞧。我之前做過類似的事情,但是我輸入錯了,而不是close(cmd->out[0]);。就是這樣了。現在有道理。