Классический C. Использование каналов в функции execvp, перенаправление stdin и stdout

#c #pipe #execv

Вопрос:

Я хочу смоделировать bash в своей программе Linux C с использованием каналов и функции execvp. например

 ls -l | wc -l   

Вот моя программа:

 if(pipe(des_p) == -1) {perror("Failed to create pipe");}  if(fork() == 0) { //first fork  close(1); //closing stdout  dup(des_p[1]); //replacing stdout with pipe write   close(des_p[0]); //closing pipe read  close(des_p[1]); //closing pipe write   if(execvp(bash_args[0], bash_args)) // contains ls -l  /* error checking */ } else {  if(fork() == 0) { //creating 2nd child  close(0); //closing stdin  dup(des_p[0]); //replacing stdin with pipe read  close(des_p[1]); //closing pipe write  close(des_p[0]); //closing pipe read   if(execvp(bash_args[another_place], bash_args)) //contains wc -l  /* error checking */  }   close(des_p[0]);  close(des_p[1]);  wait(0);  wait(0); }  

Этот код действительно запускается, но делает что-то не так. Что не так с этим кодом? Это не работает, и я понятия не имею, почему.

Ответ №1:

Вам нужно закрыть канал fds в родительском, иначе дочерний элемент не получит EOF, потому что канал все еще открыт для записи в родительском. Это привело бы wait() к зависанию второго. Работает на меня:

 #include lt;unistd.hgt; #include lt;stdlib.hgt;   int main(int argc, char** argv) {  int des_p[2];  if(pipe(des_p) == -1) {  perror("Pipe failed");  exit(1);  }   if(fork() == 0) //first fork  {  close(STDOUT_FILENO); //closing stdout  dup(des_p[1]); //replacing stdout with pipe write   close(des_p[0]); //closing pipe read  close(des_p[1]);   const char* prog1[] = { "ls", "-l", 0};  execvp(prog1[0], prog1);  perror("execvp of ls failed");  exit(1);  }   if(fork() == 0) //creating 2nd child  {  close(STDIN_FILENO); //closing stdin  dup(des_p[0]); //replacing stdin with pipe read  close(des_p[1]); //closing pipe write  close(des_p[0]);   const char* prog2[] = { "wc", "-l", 0};  execvp(prog2[0], prog2);  perror("execvp of wc failed");  exit(1);  }   close(des_p[0]);  close(des_p[1]);  wait(0);  wait(0);  return 0; }  

Комментарии:

1. Чувак, Ты же босс. Наконец — то работает (я отредактировал первый код и теперь работает).

2. Существует довольно надежное эмпирическое правило: если вы используете dup() или dup2() дублируете один конец трубы для стандартного ввода или стандартного вывода, вам нужны close() оба конца исходной трубы. Могут быть обстоятельства, когда это не является необходимым поведением, но такие обстоятельства встречаются очень редко.

3. Просто хотел отметить, что если у вас есть несколько execvp вызовов, которые взаимодействуют друг с другом по каналам, закройте только конец записи каналов в родительском (закройте оба в дочернем). Если вы закроете оба в родительском, последующие вызовы waitpid будут зависать.

4. Вы думаете о закрытии, а не о закрытии? Когда вы разветвляете ребенка, вы хотите закрыть все fd, которые будет использовать ребенок.

5. @Khan вам действительно следует задать новый вопрос, так как вы фактически просите помощи в отладке вашего кода! Вам действительно нужно закрыть все fd, которые использует ребенок, в вашем коде есть несвязанная проблема. В строке с комментариями вы закрываете fd, который будет использоваться в следующей итерации цикла! Вы должны закрыть все fd в родительском устройстве — только не раньше, чем вы закончите использовать их в родительском устройстве. Если бы вы проверили код возврата dup2, вы бы заметили (вам также необходимо проверить коды возврата pipe/fork/close/execvp/waitpid).

Ответ №2:

Прочитайте о том, что делает эта wait функция. Он будет ждать, пока не появится один дочерний процесс. Вы ждете, пока первый ребенок выйдет, прежде чем начать второго ребенка. Первый потомок, вероятно, не выйдет, пока не появится какой-нибудь процесс, который считывает данные с другого конца канала.

Комментарии:

1. Когда я удаляю first wait(), на экране появляется безумие. Execvp запускается после завершения основной программы. Как же тогда это изменить?

2. Создайте процессы с трубой между ними, а затем wait дважды, чтобы они оба закончили.

3. @krzakov Вам нужно закрыть канал fds у родителя, иначе второй ребенок не получит EOF и поэтому будет висеть (если только первый ребенок не очень увлечен и не позвонит shutdown() на его конце).

4. @krzakov Выглядит знакомо… Это был мой ответ, за вычетом того факта, что вы не ждете обоих детей, как указал Арт, вы должны это сделать. Это правильно, если он делает то, что вы хотите. Все, за что я могу поручиться, это то, что мой код компилируется и делает то, что, по моему мнению, вы хотите! (Я отменил ваши изменения в вопросе, так как в противном случае это сбивает с толку.)