#c #posix #piping
#c #posix #трубопровод
Вопрос:
Я пытался использовать системный вызов pipe () для создания оболочки, поддерживающей конвейер (с произвольным количеством команд).
К сожалению, мне не очень повезло с использованием pipe(). Потратив несколько дней на изучение различных онлайн-ресурсов, я решил собрать упрощенную программу, которая имеет тот же эффект, что и выполнение ls | sort
, чтобы посмотреть, смогу ли я заставить канал работать для двух родственных дочерних процессов. Вот код:
#include <sys/wait.h>
#include <unistd.h>
void run(char *cmd) {
char *args[2];
args[0] = cmd;
args[1] = NULL;
execvp(cmd, args);
}
int main(void) {
int filedes[2];
pipe(filedes);
pid_t pid_a, pid_b;
if (!(pid_a = fork())) {
dup2(filedes[1], 1);
run("ls");
}
if (!(pid_b = fork())) {
dup2(filedes[0], 0);
run("sort");
}
waitpid(pid_a, NULL, 0);
waitpid(pid_b, NULL, 0);
return 0;
}
Канал создается в родительском, и я знаю, что после вызова execvp() каждый дочерний процесс наследует дескрипторы файлов, которые pipe() создает в родительском. Для ls
процесса я использую dup2(), чтобы перенаправить его стандартный вывод (1) на конец записи канала, а для sort
процесса стандартный ввод (0) перенаправляется на конец чтения канала.
Наконец, я жду завершения обоих процессов перед выходом.
Моя интуиция подсказывает мне, что это должно сработать, но это не так!
Есть предложения?
Комментарии:
1. Что происходит? Можете ли вы предложить объяснение, почему это происходит?
2. Да, родительский процесс блокируется, и я подозреваю, что это связано с тем, что один из дочерних процессов никогда не завершается из-за неполучения символа EOF в их стандартном входном потоке.
Ответ №1:
Вы должны закрыть каналы, которые вы не используете. по крайней sort
мере, будет считываться из его stdin, пока stdin не будет закрыт.
В этом случае его stdin никогда не закрывается, так как у вас все еще есть 2 открытых filedescriptors для него.
- filedes[0] в дочернем элементе ls (вероятно, он закрывается при завершении ls)
- filedes[0] в родительской программе (это никогда не закрывается, поскольку вы ожидаете завершения сортировки с помощью функции waitpid(), но этого никогда не произойдет, потому что родительский stdin остается открытым)
Измените свою программу на
if (!(pid_a = fork())) {
dup2(filedes[1], 1);
closepipes(filedes);
run("ls");
}
if (!(pid_b = fork())) {
dup2(filedes[0], 0);
closepipes(filedes);
run("sort");
}
closepipe(filedes);
waitpid(pid_a, NULL, 0);
waitpid(pid_b, NULL, 0);
где closepipes — это что-то вроде
void closepipes(int *fds)
{
close(fds[0]);
close(fds[1]);
}
Ответ №2:
Перед вызовом waitpid
родительского процесса вы должны закрыть все файловые дескрипторы из канала, которые вам не нужны. Это:
filedes[0]
вpid_a
filedes[1]
вpid_b
- как
filedes[0]
иfiledes[1]
в родительском процессе
Вы также должны проверить это pipe()
и fork()
не вернули -1
, что означает, что произошла ошибка.
Комментарии:
1. В реальной ситуации я проверяю наличие ошибок — я оставил их здесь, поэтому пример может быть простым.
2. Отлично, это работает! У меня есть еще два вопроса: (1) Я предполагаю, что дескриптор файла действительно закрыт, когда все процессы, которые ссылаются на него, вызывают close() , а не только один? (2) Нам нужно только закрыть конец чтения pid_a, потому что это первый процесс в «цепочке», и мы закрываем конец записи pid_b, потому что это последний процесс в цепочке, но если бы в середине были процессы, они использовали бы оба концаканал, поэтому ни один конец не должен быть закрыт для этих процессов, верно?
3. Ваше утверждение (1) верно. Оператор (2) немного сложнее. Всякий раз, когда вы
fork()
, все файловые дескрипторы дублируются, а затем у них есть два «имени» (которые на самом деле являются небольшими числами). После форка вы обычно снова дублируете файловые дескрипторы (вstdin
иstdout
). На этом этапе вам больше не нужны исходные файловые дескрипторы иclose()
они.
Ответ №3:
Вам необходимо закрыть (по крайней мере) конец записи канала в родительском процессе. В противном случае считывающий конец канала никогда не будет считывать состояние EOF и sort
никогда не завершится.
Комментарии:
1. Я попытался закрыть конец записи перед выполнением любого из forks (
close(filedes[1])
), и теперь моя программа завершается, однако, похоже, что второй дочерний процесс ничего не получает на конце чтения канала. Я попытался заменитьsort
наwc
, и результаты0 0 0
показывают, что второй процесс ничего не получает в конце чтения.2. … и кажется, что я должен был закрыть концы канала в родительском перед ожиданием, а не разветвлением. И, основываясь на приведенных ниже предложениях, я также закрыл ненужные окончания в дочерних процессах, и теперь это работает!
Ответ №4:
Этот код работает правильно…
#include <sys/wait.h>
#include <unistd.h>
using namespace std;
void run(char *cmd) {
char *args[2];
args[0] = cmd;
args[1] = NULL;
execvp(cmd, args);
}
void closepipe(int *fds)
{
close(fds[0]);
close(fds[1]);
}
int main(int argc,char *argv[]) {
int filedes[2];
pipe(filedes);
char lss[]="ls";
char sorts[]="sort";
pid_t pid_a, pid_b;
chdir(argv[1]);
if (!(pid_a = fork())) {
dup2(filedes[1], 1);
closepipe(filedes);
run(lss);
}
if (!(pid_b = fork())) {
dup2(filedes[0], 0);
closepipe(filedes);
run(sorts);
}
closepipe(filedes);
waitpid(pid_a, NULL, 0);
waitpid(pid_b, NULL, 0);
return 0;
}