Чтение системного вызова заблокировано при совместном использовании канала - PullRequest
0 голосов
/ 27 мая 2020

Я новичок в Unix системном программировании и изо всех сил пытаюсь понять файловые дескрипторы и каналы. Давайте рассмотрим этот простой код:

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

int main() {
  int fd[2], p;
  char *m = "123456789\n", c;
  pipe(fd);
  p = fork();
  if (p == 0) {
    // child
    while(read(fd[0], &c, 1) > 0) write(1, &c, 1);
  }
  else {
    // parent
    write(fd[1], m, strlen(m));
    close(fd[1]);
    wait(NULL);
  }
  exit (0);
}

Когда я компилирую и запускаю код, он выводит 123456789, но процесс никогда не заканчивается, пока я не введу ^ C. На самом деле, оба процесса отображаются как остановленные в htop.

Если дочерний процесс закрывает fd [1] до read (), то кажется, что он работает нормально, но я не понимаю почему. Файлы fd используются обоими процессами, и родитель закрывает fd [1] после записи. Почему тогда ребенок не получает EOF при чтении?

Заранее спасибо!

Ответы [ 2 ]

1 голос
/ 29 мая 2020

Ну, во-первых, ваш родительский процесс ожидает завершения дочернего процесса в системном вызове wait(2), в то время как ваш дочерний процесс заблокирован в канале read(2) для другого символа. Оба процесса заблокированы ... поэтому вам нужно действовать извне, чтобы снять их. Проблема в том, что дочерний процесс не закрывает свой дескриптор записи канала (а также родительский дескриптор не закрывает свой дескриптор чтения канала, но здесь это не влияет). Просто канал блокирует любого читателя, хотя бы один такой дескриптор записи все еще открыт. Только когда все дескрипторы записи закрыты, чтение возвращает читателю 0.

Когда вы сделали fork(2), оба дескриптора канала (fd[0] и fd[1]) были dup() изменены на дочерний процесс, поэтому у вас есть канал с двумя открытыми файловыми дескрипторами (один в родительском, один в дочернем) для записи и два открытых дескриптора (опять же, один в родительском, один в дочернем) для чтения, так как один Writer остается с открытым каналом для записи (в данном случае дочерний процесс), чтение, сделанное дочерним процессом, все еще блокируется. Ядро не может обнаружить это как аномалию, потому что ребенок все еще может писать в конвейер, если другой поток (или обработчик сигнала) захочет.

Кстати, я собираюсь прокомментировать некоторые вещи, которые вы сделано неправильно в вашем коде:

  • во-первых, вы рассматриваете только два случая из fork() для родителя и для дочернего элемента, но если вилка завершится неудачно, она вернет -1, а вы у вас будет родительский процесс, записывающий в канал без процесса чтения, поэтому, вероятно, он должен заблокироваться (как я уже сказал, это не ваш случай, но это тоже ошибка). Вы всегда должны проверять наличие ошибок из системных вызовов и не думайте, что ваш вызов fork() никогда не завершится ошибкой (подумайте, что -1 считается != 0 и поэтому он не проходит через родительский код). Есть только один системный вызов, который вы можете выполнить, не проверяя его на наличие ошибок, и это close(2) (хотя по этому поводу много разногласий)
  • То же самое происходит с read() и write(). Лучшим решением вашей проблемы было бы использование большего буфера (а не только одного символа, чтобы уменьшить количество системных вызовов, выполняемых вашей программой и, таким образом, ускорить ее) и использовать возвращаемое значение read() в качестве параметра в вызов write().

Ваша программа должна (действительно, в моей системе) работать, просто вставив следующую строку:

close(fd[1]);

непосредственно перед while l oop в дочернем коде, как показано здесь:

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

int main() {
  int fd[2], p;
  char *m = "123456789\n", c;
  pipe(fd);
  p = fork();
  if (p == 0) {
    // child
    close(fd[1]);  // <--- this close is fundamental for the pipe to work properly.
    while(read(fd[0], &c, 1) > 0) write(1, &c, 1);
  }
  else if (p > 0) {
    // parent
    // another close(fd[0]); should be included here
    write(fd[1], m, strlen(m));
    close(fd[1]);
    wait(NULL);
  } else { 
    // include error processing for fork() here
  }
  exit (0);
}
0 голосов
/ 27 мая 2020

Если дочерний элемент закрывает fd [1] перед read (), то вроде работает нормально, но я не понимаю почему.

Это то, что вам нужно сделать. Ничего особенного, кроме этого. Чтение из конца чтения канала не вернет 0 (сигнализирует EOF) до тех пор, пока ядро ​​не будет уверено, что ничто больше никогда не будет записывать в конец записи этого канала, и пока он все еще открыт где-либо, включая процесс, выполняющий чтение, он не может быть уверен в этом.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...