Я публикую свой код просто для контекста моего вопроса. Я явно не ищу, чтобы вы помогли это исправить, я скорее хочу понять системный вызов dup2, который я просто не отвечаю на странице руководства и во множестве других вопросов stackoverflow.
pid = fork();
if(pid == 0) {
if(strcmp("STDOUT", outfile)) {
if (command->getOutputFD() == REDIRECT) {
if ((outfd = open(outfile, O_CREAT | O_WRONLY | O_TRUNC)) == -1)
return false;
command->setOutputFD(outfd);
if (dup2(command->getOutputFD(), STDOUT_FILENO) == -1)
return false;
pipeIndex++;
}
else if (command->getOutputFD() == REDIRECTAPPEND) {
if ((outfd = open(outfile, O_CREAT | O_WRONLY | O_APPEND)) == -1)
return false;
command->setOutputFD(outfd);
if (dup2(command->getOutputFD(), STDOUT_FILENO) == -1)
return false;
pipeIndex++;
}
else {
if (dup2(pipefd[++pipeIndex], STDOUT_FILENO) == -1)
return false;
command->setOutputFD(pipefd[pipeIndex]);
}
}
if(strcmp("STDIN", infile)) {
if(dup2(pipefd[pipeIndex - 1], STDIN_FILENO) == -1)
return false;
command->setOutputFD(pipefd[pipeIndex - 1]);
pipeIndex++;
}if (execvp(arguments[0], arguments) == -1) {
std::cerr << "Error!" << std::endl;
_Exit(0);
}
}
else if(pid == -1) {
return false;
}
Для контекста этот код представляет собой шаг выполнения базовой оболочки Linux. Объект команды содержит аргументы команд, «имя» ввода-вывода и дескрипторы ввода-вывода (думаю, я мог бы избавиться от дескрипторов файлов в виде полей).
Что мне труднее всего понять, так это когда и какие файловые дескрипторы закрывать. Думаю, я просто задам несколько вопросов, чтобы попытаться улучшить мое понимание концепции.
1) С моим массивом файловых дескрипторов, используемых для обработки каналов, у родителя есть копия всех этих дескрипторов. Когда дескрипторы, удерживаемые родителем, закрыты? И даже более того, какие дескрипторы? Это все из них? Все из них остались неиспользованными исполняемыми командами?
2) Какие процессы при обработке каналов внутри дочерних процессов остаются открытыми? Скажите, если я выполню команду: ls -l | Grep
«[username]», какие дескрипторы следует оставить открытыми для процесса ls? Просто конец записи трубы? И если да, то когда? Тот же вопрос относится к команде grep.
3) Когда я обрабатываю перенаправление ввода-вывода в файл, новый файл должен быть открыт и дублирован на STDOUT (я не поддерживаю перенаправление ввода). Когда этот дескриптор закрывается? Я видел в примерах, что он закрывается сразу после вызова dup2, но как тогда что-то записывается в файл, если файл был закрыт?
Спасибо заранее Я застрял на этой проблеме в течение нескольких дней, и я действительно хотел бы закончить с этим проектом.
РЕДАКТИРОВАТЬ Я обновил его с помощью измененного кода и примера вывода для всех, кто заинтересован в предложении конкретной помощи по моей проблеме. Сначала у меня есть весь цикл for, который обрабатывает выполнение. Это было обновлено с моими вызовами, чтобы закрыть на различных файловых дескрипторах.
while(currCommand != NULL) {
command = currCommand->getData();
infile = command->getInFileName();
outfile = command->getOutFileName();
arguments = command->getArgList();
pid = fork();
if(pid == 0) {
if(strcmp("STDOUT", outfile)) {
if (command->getOutputFD() == REDIRECT) {
if ((outfd = open(outfile, O_CREAT | O_WRONLY | O_TRUNC)) == -1)
return false;
if (dup2(outfd, STDOUT_FILENO) == -1)
return false;
close(STDOUT_FILENO);
}
else if (command->getOutputFD() == REDIRECTAPPEND) {
if ((outfd = open(outfile, O_CREAT | O_WRONLY | O_APPEND)) == -1)
return false;
if (dup2(outfd, STDOUT_FILENO) == -1)
return false;
close(STDOUT_FILENO);
}
else {
if (dup2(pipefd[pipeIndex + 1], STDOUT_FILENO) == -1)
return false;
close(pipefd[pipeIndex]);
}
}
pipeIndex++;
if(strcmp("STDIN", infile)) {
if(dup2(pipefd[pipeIndex - 1], STDIN_FILENO) == -1)
return false;
close(pipefd[pipeIndex]);
pipeIndex++;
}
if (execvp(arguments[0], arguments) == -1) {
std::cerr << "Error!" << std::endl;
_Exit(0);
}
}
else if(pid == -1) {
return false;
}
currCommand = currCommand->getNext();
}
for(int i = 0; i < numPipes * 2; i++)
close(pipefd[i]);
for(int i = 0; i < commands->size();i++) {
if(wait(status) == -1)
return false;
}
При выполнении этого кода я получаю следующий вывод
ᕕ( ᐛ )ᕗ ls -l
total 68
-rwxrwxrwx 1 cook cook 242 May 31 18:31 CMakeLists.txt
-rwxrwxrwx 1 cook cook 617 Jun 1 22:40 Command.cpp
-rwxrwxrwx 1 cook cook 9430 Jun 8 18:02 ExecuteExternalCommand.cpp
-rwxrwxrwx 1 cook cook 682 May 31 18:35 ExecuteInternalCommand.cpp
drwxrwxrwx 2 cook cook 4096 Jun 8 17:16 headers
drwxrwxrwx 2 cook cook 4096 May 31 18:32 implementation files
-rwxr-xr-x 1 cook cook 25772 Jun 8 18:12 LeShell
-rwxrwxrwx 1 cook cook 243 Jun 5 13:02 Makefile
-rwxrwxrwx 1 cook cook 831 Jun 3 12:10 Shell.cpp
ᕕ( ᐛ )ᕗ ls -l > output.txt
ls: write error: Bad file descriptor
ᕕ( ᐛ )ᕗ ls -l | grep "cook"ᕕ( ᐛ )ᕗ
Выход из ls -l > output.txt
подразумевает, что я закрываю неправильный дескриптор, но закрытие другого связанного дескриптора, не отображая ошибки, не обеспечивает вывод в файл. Как продемонстрировано ls -l
, grep "cook"
, должен генерировать вывод на консоль.
С моим массивом файловых дескрипторов, используемых для обработки каналов, родительский
имеет копию всех этих дескрипторов. Когда дескрипторы хранятся
родитель закрыт? И даже более того, какие дескрипторы? Это все из
их? Все из них остались неиспользованными исполняемыми командами?
Дескриптор файла может быть закрыт одним из 3 способов:
close()
в теме.exec()
функции и файловый дескриптор имеет O_CLOEXEC
флаг.Как видите, в большинстве случаев файловые дескрипторы остаются открытыми, пока вы не закроете их вручную. Это то, что происходит в вашем коде тоже — так как вы не указали O_CLOEXEC
дескрипторы файлов не закрываются при вызове дочернего процесса execvp()
, У ребенка они закрываются после того, как ребенок заканчивается. То же самое касается родителей. Если вы хотите, чтобы это произошло в любое время до завершения, вы должны вручную вызвать close()
,
При обработке каналов внутри дочерних элементов, дескрипторы которых остаются
открыть какими процессами? Скажите, если я выполню команду: ls -l | Grep
«[username]», какие дескрипторы должны быть оставлены открытыми для ls
процесс? Просто конец записи трубы? И если да, то когда? Такой же
вопрос относится к команде grep.
Вот (грубое) представление о том, что делает оболочка при вводе ls -l | grep "username"
:
pipe()
создать новую трубу. Дескрипторы файла канала наследуются потомками на следующем шаге.c1
а также c2
, Давайте предположим c1
побежит ls
а также c2
побежит grep
,c1
канал чтения канала закрыт close()
, а затем он вызывает dup2()
с каналом записи трубы и STDOUT_FILENO
чтобы написать stdout
эквивалентно записи в трубу. Затем один из семи exec()
функции вызывается, чтобы начать выполнение ls
, ls
пишет stdout
, но так как мы продублировали stdout
на канал записи канала, ls
буду писать в трубу.c2
, происходит обратное: канал записи канала закрыт, а затем dup2()
призван сделать stdin
указать канал чтения канала. Затем один из семи exec()
функции вызывается, чтобы начать выполнение grep
, grep
читает из stdin
, но так как мы dup2()
стандартный ввод в канал чтения канала, grep
буду читать из трубы.Когда я обрабатываю перенаправление ввода-вывода в файл, новый файл должен быть открыт
и дублируется на STDOUT (я не поддерживаю перенаправление ввода). Когда делает
этот дескриптор закрыли? Я видел в примерах, что это закрывается
сразу после звонка на dup2, но как потом что нибудь получится
записано в файл, если файл был закрыт?
Итак, когда вы звоните dup2(a, b)
Либо одно из них верно:
a == b
, В этом случае ничего не происходит и dup2()
возвращается преждевременно. Нет файловых дескрипторов закрыты.a != b
, В этом случае, b
закрывается при необходимости, а затем b
сделано для ссылки на ту же запись таблицы файлов, что и a
, Запись таблицы файлов представляет собой структуру, которая содержит текущее смещение файла и флаги состояния файла; несколько файловых дескрипторов могут указывать на одну и ту же запись таблицы файлов, и это именно то, что происходит, когда вы дублируете файловый дескриптор. Так, dup2(a, b)
имеет эффект создания a
а также b
поделиться одной и той же записи таблицы файлов. Как следствие, писать a
или же b
в конечном итоге запись в тот же файл. Таким образом, файл, который закрыт b
не a
, если ты dup2(a, STDOUT_FILENO)
ты закрываешь stdout
и вы делаете stdout
дескриптор файла указывает на ту же запись таблицы файлов, что и a
, Любая программа, которая пишет в stdout
будет вместо этого писать в файл, так как stdout
Описатель файла указывает на файл, который вы скопировали.ОБНОВИТЬ:
Итак, для вашей конкретной проблемы, вот что я должен сказать после краткого просмотра кода:
Вы не должны звонить close(STDOUT_FILENO)
здесь:
if (command->getOutputFD() == REDIRECT) {
if ((outfd = open(outfile, O_CREAT | O_WRONLY | O_TRUNC)) == -1)
return false;
if (dup2(outfd, STDOUT_FILENO) == -1)
return false;
close(STDOUT_FILENO);
}
Если вы закроете stdout
, вы получите ошибку в будущем, когда вы попытаетесь написать stdout
, Вот почему вы получаете ls: write error: Bad file descriptor
, В конце концов, ls
пишет stdout
, но вы закрыли это. К сожалению!
Вы делаете это задом наперед: вы хотите закрыть outfd
вместо. Вы открыли outfd
чтобы вы могли перенаправить STDOUT_FILENO
в outfd
, как только перенаправление сделано, вам не нужно outfd
больше, и вы можете закрыть его. Но вы определенно не хотите закрывать stdout
потому что идея состоит в том, чтобы иметь stdout
записать в файл, на который ссылалась outfd
,
Итак, продолжайте и сделайте это:
if (command->getOutputFD() == REDIRECT) {
if ((outfd = open(outfile, O_CREAT | O_WRONLY | O_TRUNC)) == -1)
return false;
if (dup2(outfd, STDOUT_FILENO) == -1)
return false;
if (outfd != STDOUT_FILENO)
close(outfd);
}
Обратите внимание на финал if
необходимо: если outfd
случайно оказывается равным STDOUT_FILENO
Вы не хотите закрывать его по причинам, которые я только что упомянул.
То же самое относится и к коду внутри else if (command->getOutputFD() == REDIRECTAPPEND)
: хочешь закрыть outfd
скорее, чем STDOUT_FILENO
:
else if (command->getOutputFD() == REDIRECTAPPEND) {
if ((outfd = open(outfile, O_CREAT | O_WRONLY | O_APPEND)) == -1)
return false;
if (dup2(outfd, STDOUT_FILENO) == -1)
return false;
if (outfd != STDOUT_FILENO)
close(STDOUT_FILENO);
}
Это должно, по крайней мере, заставить вас ls -l
работать как положено.
Что касается проблемы с трубами: ваше управление трубами на самом деле не правильно. Из кода, который вы показали, где и как не понятно pipefd
и сколько каналов вы создаете, но обратите внимание, что:
outfile
не является STDOUT
а также infile
не является STDIN
в итоге вы закрываете каналы чтения и записи (и, что еще хуже, после закрытия канала чтения вы пытаетесь продублировать его). Это никак не сработает.Я предлагаю изменить способ управления трубами. В этом ответе вы можете увидеть пример работающей оболочки с открытыми контурами, работающей с трубами: https://stackoverflow.com/a/30415995/2793118