Я понимаю, как файловые дескрипторы Unix работают в C? - PullRequest
7 голосов
/ 13 февраля 2012

Короткая программа, приведенная ниже, предназначена для перебора аргументов argv, переданных из командной строки, и выполнения каждого аргумента. Это не моя домашняя работа, а то, чем я занимаюсь, готовясь к домашней работе.

Первый аргумент получает входные данные из 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;                                                                     
}

ОБНОВЛЕНИЕ : во всех нижеприведенных тестовых примерах я изначально использовал / bin / wc, но который показал, что унитаз совсем не там, где я думал. Я нахожусь в процессе исправления результатов.

Выход в тривиальном случае (./a.out / bin / pwd), как и ожидалось:

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:

Запустив его с / bin / 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
@

В конце все еще есть какой-то мусор, но я теперь больше обеспокоен тем, что не понимаю указателей! Эти две команды независимы друг от друга, но они на самом деле не используют канал.

ОБНОВЛЕНИЕ : символ мусора не был закрыт строкой. Теперь я его закрываю, а мусора нет.

Ответы [ 2 ]

2 голосов
/ 13 февраля 2012

Зависание вызвано тем, что записывающий конец «правой» трубы не правильно закрывается в основном процессе после разветвления.Из-за этого 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

Если у вас есть конкретные вопросы об этом решении, пожалуйста, дайте мне знать :) Есть также некоторые другие незначительные проблемы с вашим исходным кодом:

  • использование указателей не нужно, мы можем простокопировать по каналам (производительность, безусловно, не будет проблемой;) вместо 1011 *
  • используется
  • int, вы не устранили все предупреждения, которые будут вам представленыпри компиляции с флагом -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 голосов
/ 13 февраля 2012

Чтобы исправить мусор в конце вывода, добавьте следующую строку перед окончательным printf.

readbuffer[nbytes] = 0;

Что касается проблемы с зависанием - мне нужно немного подумать, чтобы решить эту проблему. Я предполагаю, что это как-то связано с сантехникой и буферизацией.

...