2012-02-13 52 views
7

下面的短程序旨在遍历从命令行传递的argv并执行每个参数。这不是我的家庭作业,而是我准备做作业的一些事情。我了解Unix文件描述符如何在C中工作吗?

第一个参数从STDIN和STDOUT获取输入,并写入管道。在每次迭代结束时(除了最后一个),文件描述符都会被交换,以便最后一个exec写入的管道将被下一个读取。以这种方式,我打算,例如,对于

./a.out /bin/pwd /usr/bin/wc 

只打印出工作目录的长度。代码如下

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

main(int argc, char * argv[]) {             

    int i; 
    int left[2], right[2], nbytes; /* arrays for file descriptors */ 

    /* pointers for swapping */ 
    int (* temp); 
    int (* leftPipe) = left;     
    int (* rightPipe) = right; 

    pid_t childpid;                
    char readbuffer[80];               

    /* for the first iteration, leftPipe is STDIN */ 
    leftPipe[0] = STDIN_FILENO; 
    leftPipe[1] = STDOUT_FILENO; 

    for (i = 1; i < argc; i++) {             

    /* reopen the right pipe (is this necessary?) */ 
    pipe(rightPipe);                
    fprintf(stderr, "%d: %s\n", i, argv[i]); 
    fprintf(stderr, "%d %d %d %d\n", leftPipe[0], leftPipe[1], rightPipe[0], rightPipe[1]);                      
    if ((childpid = fork()) == -1) {            
     perror("fork");               
     exit(1);                 
    }                   

    if (childpid == 0) {               

     /* read input from the left */            
     close(leftPipe[1]); /* close output */          
     dup2(leftPipe[0], STDIN_FILENO);           
     close(leftPipe[0]); /* is this necessary? A tutorial seemed to be doing this */ 

     /* write output to the right */           
     close(rightPipe[0]); /* close input */          
     dup2(rightPipe[1], STDOUT_FILENO);           
     close(rightPipe[1]);              

     execl(argv[i], argv[i], NULL);            
     exit(0);                 
    }                   

    wait();                  

    /* on all but the last iteration, swap the pipes */ 
    if (i + 1 < argc) {    

     /* swap the pipes */              
     fprintf(stderr, "%d %d %d %d\n", leftPipe[0], leftPipe[1], rightPipe[0], rightPipe[1]); 
     temp = leftPipe;               
     leftPipe = rightPipe;              
     rightPipe = temp;               
     fprintf(stderr, "%d %d %d %d\n", leftPipe[0], leftPipe[1], rightPipe[0], rightPipe[1]); 
    }                   
    }                    

    /* read what was last written to the right pipe */       
    close(rightPipe[1]); /* the receiving process closes 1 */     

    nbytes = read(rightPipe[0], readbuffer, sizeof(readbuffer));  
    readbuffer[nbytes] = 0; 
    fprintf(stderr, "Received string: %s\n", readbuffer);         

    return 0;                  
} 

UPDATE:在所有我原本使用/斌/ WC但下面的测试案例WC reveiled座便器是不是在所有地方我想。我正在修改结果。

在一个微不足道的情况下的输出(./a.out/bin中/密码)是如预期:

1: /bin/pwd 
Received string: /home/zeigfreid/Works/programmatical/Langara/spring_2012/OS/labs/lab02/play 

从与第一实施例(./a.out/bin中运行该程序的输出/ pwd/usr/bin/wc):

1: /bin/pwd 
0 1 3 4 
3 4 0 1 
2: /bin/wc 

此时,终端挂起(可能等待输入)。

正如您所看到的,该字符串未被接收。我想象的是,我在上面做了一些错误的事情,无论是交换指针还是我不了解unix文件描述符。我的任务最终将解释任意长的管道,这是我解决问题的想法之一。我在判断我是否正在吠叫树的正确轨道上遇到困难。我了解unix文件描述符吗?

UPDATE:

与/斌/ LS作为第二个参数运行它,我得到了以下结果(号码在各个点的文件描述符):

1: /bin/pwd 
0 1 3 4 
0 1 3 4 
3 4 0 1 
2: /bin/ls 
3 4 5 6 
Received string: a.out 
log 
pipe2.c 
play.c 
@ 

有还有一些垃圾在那边,但我现在更关心的是我不懂指针!这两个命令虽然彼此独立,但并不真正使用管道。

UPDATE:垃圾字符是从不关闭字符串。现在我关闭它,并没有垃圾。

+0

我想建议改变你的一切'的printf(...)调用''到fprintf中(错误,...)'。将标准IO('printf(3)')与低级别例程('pipe(2)'''dup2(2)'''close(2)')混合会比其值得的麻烦更麻烦。 – sarnold 2012-02-13 00:51:58

+0

正式注意!我想夹板会同意。 – Ziggy 2012-02-13 00:55:47

+0

打印之前,您不要终止字符串,这解释了垃圾。 'read'后尝试'readbytes [nbytes] = 0'。 – 2012-02-13 00:58:21

回答

2

挂起是由于在分叉后主流程中“右”管的写入端没有正确关闭。正因为如此,wc永远不会停止阅读(毕竟,主进程仍然可以写入东西到管道!)。它只在写入结束的文件描述符的所有副本都关闭后才会停止读取。

这里有一个固定的版本:

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

int main(int argc, char * argv[]) 
{ 
    int i; 
    int left[2], right[2], nbytes; /* arrays for file descriptors */ 

    /* pointers for swapping */ 
    int (* temp); 
    int (* leftPipe) = left; 
    int (* rightPipe) = right; 

    pid_t childpid; 
    char readbuffer[80]; 

    leftPipe[0] = STDIN_FILENO; 
    // no need to assign leftPipe[1] here, it will not be used 

    for (i = 1; i < argc; i++) { 
    pipe(rightPipe); // create new pipe 

    fprintf(stderr, "%d: %s\n", i, argv[i]); 
    fprintf(stderr, "%d %d %d %d\n", leftPipe[0], leftPipe[1], rightPipe[0], rightPipe[1]); 
    if ((childpid = fork()) == -1) { 
     perror("fork"); 
     exit(1); 
    } 

    if (childpid == 0) { 
     // use the reading end of the left pipe as STDIN 
     dup2(leftPipe[0], STDIN_FILENO); 
     // use the writing end of the right pipe as STDOUT 
     dup2(rightPipe[1], STDOUT_FILENO); 
     // close reading end of the right pipe 
     close(rightPipe[0]); 
     execl(argv[i], argv[i], NULL); 
     exit(0); 
    } 
    // IMPORTANT!! close writing end of the right pipe, otherwise 
    // the program will hang (this is the main bug in your original 
    // implementation) 
    close(rightPipe[1]); 

    // wait properly! 
    waitpid(childpid, NULL, 0); 

    /* on all but the last iteration, swap */ 
    if (i + 1 < argc) { 
     fprintf(stderr, "%d %d %d %d\n", leftPipe[0], leftPipe[1], rightPipe[0], rightPipe[1]); 
     temp = leftPipe; 
     leftPipe = rightPipe; 
     rightPipe = temp; 
     fprintf(stderr, "%d %d %d %d\n", leftPipe[0], leftPipe[1], rightPipe[0], rightPipe[1]); 
    } 
    } 

    nbytes = read(rightPipe[0], readbuffer, sizeof(readbuffer)); 
    readbuffer[nbytes] = 0; 
    fprintf(stderr, "Received string: %s\n", readbuffer); 

    return 0; 
} 

输出:

>> ./a.out /bin/ls /bin/cat /usr/bin/wc 
1: /bin/ls 
0 32767 3 4 
0 32767 3 4 
3 4 0 32767 
2: /bin/cat 
3 4 4 5 
3 4 4 5 
4 5 3 4 
3: /usr/bin/wc 
4 5 5 6 
Received string:  266  294 4280 

如果你有关于这个解决方案的具体问题,请让我知道:)还有一些其他的小问题您的原始代码:

  • 使用指针是不必要的我们可以就在管道复制(性能肯定不会是一个问题;)
  • int被用来代替size_t
  • 您未修好一律将与-Wall标志编译时呈现给你的警告

如果你有兴趣,这是我怎么会写它:

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

int main(int argc, char **argv) { 
    size_t i, nbytes; 
    int left[2], right[2], tmp[2]; 
    pid_t childpid; 
    char readbuffer[80]; 

    left[0] = STDIN_FILENO; 

    for (i = 1; i < argc; ++i) { 
    pipe(right); 

    switch ((childpid = fork())) { 
     case -1: 
     perror("fork"); 
     exit(1); 
     case 0: 
     dup2(left[0], STDIN_FILENO); 
     dup2(right[1], STDOUT_FILENO); 
     close(right[0]); 
     execl(argv[i], argv[i], NULL); 
     default: 
     close(right[1]); 
     waitpid(childpid, NULL, 0); 
    } 

    if (i == argc - 1) break; 
    memcpy(tmp, left, sizeof tmp); 
    memcpy(left, right, sizeof left); 
    memcpy(right, tmp, sizeof right); 
    } 

    nbytes = read(right[0], readbuffer, sizeof readbuffer); 
    readbuffer[nbytes] = 0; 
    fprintf(stderr, "Received string: %s\n", readbuffer); 

    return 0; 
} 
+0

不错!我没有编译 - 墙,你是对的。通常我会这样做,而且我还修复了夹板警告,但这只是一个实验,所以我没有做到彻底。很高兴看到答案是相对较小的东西,而不是我担心的那种绝对错误。所以答案是“是”,但我需要更多的细节练习。非常感谢您的解决方案非常好! – Ziggy 2012-02-13 06:04:32

+0

@Ziggy:如果它帮助你,你被邀请接受这个答案:) – 2012-02-13 14:10:08

+0

我当然会!我倾向于 :) – Ziggy 2012-02-13 20:13:08

0

要修复输出结尾的垃圾,请在最终的printf之前添加以下行。

readbuffer[nbytes] = 0; 

至于悬挂问题 - 我需要多一点想法来解决这个问题。我猜测这与管道和缓冲有关。