#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 Выглядит знакомо… Это был мой ответ, за вычетом того факта, что вы не ждете обоих детей, как указал Арт, вы должны это сделать. Это правильно, если он делает то, что вы хотите. Все, за что я могу поручиться, это то, что мой код компилируется и делает то, что, по моему мнению, вы хотите! (Я отменил ваши изменения в вопросе, так как в противном случае это сбивает с толку.)