#c #pipe #fork #getline #dup2
#c #канал #fork #getline #dup2
Вопрос:
Я кодирую макет оболочки и в настоящее время работаю над кодированием каналов с помощью dup2. Вот мой код:
bool Pipe::execute() {
int fds[2]; //will hold file descriptors
pipe(fds);
int status;
int errorno;
pid_t child;
child = fork();
if (-1 == child) {
perror("fork failed");
}
if (child == 0) {
dup2(fds[1], STDOUT_FILENO);
close(fds[0]);
close(fds[1]);
this->component->lchild->execute();
_exit(1);
}
else if (child > 0) {
dup2(fds[0], STDIN_FILENO);
close(fds[0]);
close(fds[1]);
this->component->rchild->execute();
waitpid(child, amp;status, 0);
if ( WIFEXITED(status) ) {
//printf("child exited with = %dn",WEXITSTATUS(status));
if ( WEXITSTATUS(status) == 0) {
cout << "pipe parent finishing" << endl;
return true;
}
}
return false;
}
}
this->component->lchild->execute();
и this->component->rchild->execute();
выполнить execvp
соответствующие команды. Я подтвердил, что каждый из них возвращает, распечатав инструкцию в родительском процессе. Однако, на мой взгляд, Pipe::execute()
похоже, что дочерний процесс не завершается, потому что оператор cout в родительском процессе никогда не печатается, и я получаю ошибку сегментации после инициализации запроса ( $
) (см. Рисунок). Вот основная функция, которая инициализирует приглашение после каждого выполнения:
int main()
{
Prompt new_prompt;
while(1) {
new_prompt.initialize();
}
return 0;
}
и вот initialize()
функция:
void Prompt::initialize()
{
cout << "$ ";
std::getline(std::cin, input);
parse(input);
run();
input.clear();
tokens.clear();
fflush(stdout);
fflush(stdin);
return;
}
Кажется, что ls | sort
все работает нормально, но затем, когда инициализируется приглашение, getline считывает пустую строку во входные данные. Я пробовал использовать cin.clear(), cin.ignore и строки fflush и clear() выше. Эта пустая строка «анализируется», а затем вызывается run()
функция, которая пытается разыменовать нулевой указатель. Есть идеи о том, почему / где эта пустая строка вводится в getline? и как мне это решить? Спасибо!
ОБНОВЛЕНИЕ: родительский процесс в канале теперь завершается. Я также заметил, что я получаю ошибки seg также для моих классов перенаправления ввода-вывода ( >
и <
). Я думаю, что я неправильно очищаю поток или закрываю файловые дескрипторы…
Вот моя execute()
функция для lchild и rchild:
bool Command::execute() {
int status;
int errorno;
pid_t child;
vector<char *> argv;
for (unsigned i=0; i < this->command.size(); i) {
char * cstr = const_cast<char*>(this->command.at(i).c_str());
argv.push_back(cstr);
}
argv.push_back(NULL);
child = fork();
if (-1 == child) {
perror("fork failed");
}
if (child == 0) {
errorno = execvp(*argv.data(), argv.data());
_exit(1);
} else if (child > 0) {
waitpid(child, amp;status, 0);
if ( WIFEXITED(status) ) {
//printf("child exited with = %dn",WEXITSTATUS(status));
if ( WEXITSTATUS(status) == 0) {
//cout << "command parent finishing" << endl;
return true;
}
}
return false;
}
}
Комментарии:
1. Предполагается, что это похоже на приглашение терминала оболочки — оно ожидает ввода пользователем
2. Независимо от того, что на самом деле вызывает пустую строку, я бы рекомендовал программе не выполнять segfault при запуске через один — выполняйте проверки и выдавайте ошибку как можно скорее, а не выполняйте segfault.
3. После обновления это другой вопрос, который требует других частей кода. Я подозреваю, что там аналогичная проблема, но не могу сказать, не видя кода
4. @MichaelVeksler Я добавил функцию execute. Эта функция должна работать и для других типов команд, поэтому я боюсь закрыть один из потоков в ней (например, если я запускаю echo hello amp; amp; pwd, я не хочу ничего закрывать — только если я использую перенаправление)
Ответ №1:
Вот ошибка:
else if (child > 0) {
dup2(fds[0], STDIN_FILENO);
close(fds[0]);
close(fds[1]);
this->component->rchild->execute();
Вы закрываете stdin для родительского, а не только для нужного дочернего элемента. После этого stdin родительского процесса такой же, как и у правильного дочернего процесса.
После этого
std::getline(std::cin, input);
Пытается прочитать выходные данные левого дочернего элемента, а не исходного stdin. К этому моменту левый дочерний элемент был завершен, и этот конец канала был закрыт. Это приводит к сбою чтения stdin и оставляет input
неизменным его исходное состояние.
Редактировать: Ваш дизайн имеет незначительные и серьезные недостатки. Незначительный недостаток заключается в том, что вам не нужна вилка в Pipe::execute
. Основной недостаток заключается в том, что дочерний элемент должен перенаправлять потоки и закрывать дескрипторы.
Просто передайте входные и выходные параметры через fork() и позвольте дочернему элементу дублировать их. Не забудьте также закрыть несвязанные концы канала. Если вы этого не сделаете, левый дочерний файл завершится, но его выходной канал продолжит работу в других процессах. Пока существуют другие копии этого дескриптора, правильный потомок никогда не получит EOF при чтении своего блока конца канала и недели навсегда.
Комментарии:
1. Ааааа, понятно. Итак, есть ли способ «повторно открыть» stdin? Или мне нужно где-то дополнительно закрыть?
2. @SarahAllec вы должны вызывать
close
в самом правильном дочернем элементе, а не в родительском3. Ну, на самом деле, похоже, что правильного дочернего элемента не существует , поскольку есть только один вызов
fork()
. Один родительский элемент, один (левый) дочерний элемент. По крайней мере, это то, что существует на уровнеclass Pipe
.4. @BenVoigt посмотрите на обновленный вопрос. Как я и подозревал, метод execute вызывает fork , и это правильный дочерний элемент. Я просто удалил свою заметку об этом предположении после того, как вопрос был прояснен
5. И теперь есть 3 дочерних элемента, что по-прежнему не соответствует ни количеству
fork()
вызовов, ни управлению каналом в правильных дочерних процессах.