переполнение pipe
Пишу эмюляцию конвейера shell, есть уже распаршенный список команд (см. структуру command). Программа корректно отрабатывает, когда ее вывод небольшой, на пример ls | more. Однако, когда вывод программы очень большой, на пример ls -lR /* | more, то программа крашается. Подскажите, пожалуйста, как переписать функцию run_pipe() чтобы процессы могли обмениваться и большими объемами информации: код:
struct command
{
vector<char*> argv;
command* follow_pipe; //следующая, если конвейер
command() {
follow_pipe = 0;
}
};
void pipe_run (command* ptr) {
pid_t pid, wpid;
int pfd[2], input, status;
bool first = true;
while (ptr != nullptr ) {
pipe(pfd);
if (!(pid = fork())) {
if (!first) {
dup2(input, 0);
close(input);
}
if ( ptr->follow_pipe != nullptr )
dup2(pfd[1], 1);
close(pfd[0]);
close(pfd[1]);
execvp( ptr->argv[0], &ptr->argv[0]);
cerr<<"exec_vp"<<'\n';
}
if (!first)
close(input);
close(pfd[1]);
input = pfd[0];
do {
wpid = waitpid(pid, &status, WUNTRACED);
} while (!WIFEXITED(status) && !WIFSIGNALED(status));
first = false;
ptr = ptr->follow_pipe;
}
}
update: добавил минимальный воспроизводимый пример (структура command была немного изменена, но это никак не влияет на проблему при тех же тестах):
#include <iostream>
#include <unistd.h>
#include <string>
#include <cstring>
#include <sys/wait.h>
#include <fstream>
#include <fcntl.h>
#include <signal.h>
#include <stdio.h>
#include <cstdlib>
#include <vector>
#include <dirent.h>
#include <algorithm>
using namespace std;
struct command
{
char** argv;
command* follow_pipe; //следующая, если конвейер
command() {
argv = 0;
follow_pipe = 0;
}
};
void pipe_run (command* ptr) {
pid_t pid, wpid;
int pfd[2], input, status;
bool first = true;
while (ptr != nullptr ) {
pipe(pfd);
if (!(pid = fork())) {
if (!first) {
dup2(input, 0);
close(input);
}
if ( ptr->follow_pipe != nullptr )
dup2(pfd[1], 1);
close(pfd[0]);
close(pfd[1]);
execvp( ptr->argv[0], &ptr->argv[0]);
}
if (!first)
close(input);
close(pfd[1]);
input = pfd[0];
do {
wpid = waitpid(pid, &status, WUNTRACED);
} while (!WIFEXITED(status) && !WIFSIGNALED(status));
first = false;
ptr = ptr->follow_pipe;
}
}
int main () {
command* temp1 = new command;
command* temp2 = new command;
vector<string> A(3), B(1);
A[0] = "ls";
A[1] = "-lR";
A[2] = "/"; // при запуске в маленькой дирректории и без добавления этого аргумента программа работает корректно
B[0] = "more";
temp1->argv = new char* [A.size() +1 ];
for ( size_t i = 0; i < A.size() ; ++i ) {
temp1->argv[i] = new char[A[i].size()];
strcpy( temp1->argv[i], A[i].c_str() );
}
temp1->argv[A.size()] = nullptr;
temp2->argv = new char* [B.size() +1 ];
for ( size_t i = 0; i < B.size() ; ++i ) {
temp2->argv[i] = new char[B[i].size()];
strcpy( temp2->argv[i], B[i].c_str() );
}
temp2->argv[B.size()] = nullptr;
temp1->follow_pipe = temp2;
pipe_run(temp1);
}
Ответы (1 шт):
Проблема в вопросе в том, что сначала запускается один элемент конвейера и сразу ожидается его окончание. Само собой если этот процесс не завершается, пытаясь писать в трубу, из которой ни кто не читает то всё зависает. Нужно сначала запустить все элементы конвейера, а уже потом ждать их завершения. т.е. цикл с waitpid()'ами должны идти после цикла запуска. PID'ы всех потомков соответственно нужно предварительно сохранить. в итоге это будет выглядеть как-то так:
struct command
{
// ...
int pid;
};
void pipe_run (command* ptr) {
pid_t wpid;
int pfd[2], input, status;
bool first = true;
for (command *cmd=ptr; cmd; cmd = cmd->follow_pipe) {
pipe(pfd);
if (!(cmd->pid = fork())) {
if (!first) {
dup2(input, 0);
close(input);
}
if ( cmd->follow_pipe != nullptr )
dup2(pfd[1], 1);
close(pfd[0]);
close(pfd[1]);
execvp( cmd->argv[0], &cmd->argv[0]);
}
if (!first)
close(input);
close(pfd[1]);
input = pfd[0];
first = false;
}
for (command *cmd=ptr; cmd; cmd = cmd->follow_pipe) {
do {
wpid = waitpid(cmd->pid, &status, WUNTRACED);
} while (!WIFEXITED(status) && !WIFSIGNALED(status));
}
}