Понимание dup2 и закрытие файловых дескрипторов

Я публикую свой код просто для контекста моего вопроса. Я явно не ищу, чтобы вы помогли это исправить, я скорее хочу понять системный вызов 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", должен генерировать вывод на консоль.

1

Решение

С моим массивом файловых дескрипторов, используемых для обработки каналов, родительский
имеет копию всех этих дескрипторов. Когда дескрипторы хранятся
родитель закрыт? И даже более того, какие дескрипторы? Это все из
их? Все из них остались неиспользованными исполняемыми командами?

Дескриптор файла может быть закрыт одним из 3 способов:

  1. Вы явно звоните close() в теме.
  2. Процесс завершается, и операционная система автоматически закрывает каждый дескриптор файла, который все еще был открыт.
  3. Когда процесс вызывает один из семи exec() функции и файловый дескриптор имеет O_CLOEXEC флаг.

Как видите, в большинстве случаев файловые дескрипторы остаются открытыми, пока вы не закроете их вручную. Это то, что происходит в вашем коде тоже — так как вы не указали O_CLOEXECдескрипторы файлов не закрываются при вызове дочернего процесса execvp(), У ребенка они закрываются после того, как ребенок заканчивается. То же самое касается родителей. Если вы хотите, чтобы это произошло в любое время до завершения, вы должны вручную вызвать close(),

При обработке каналов внутри дочерних элементов, дескрипторы которых остаются
открыть какими процессами? Скажите, если я выполню команду: ls -l | Grep
«[username]», какие дескрипторы должны быть оставлены открытыми для ls
процесс? Просто конец записи трубы? И если да, то когда? Такой же
вопрос относится к команде grep.

Вот (грубое) представление о том, что делает оболочка при вводе ls -l | grep "username":

  1. Оболочка вызывает pipe() создать новую трубу. Дескрипторы файла канала наследуются потомками на следующем шаге.
  2. Оболочка дважды разветвляется, давайте назовем эти процессы c1 а также c2, Давайте предположим c1 побежит ls а также c2 побежит grep,
  3. В c1канал чтения канала закрыт close(), а затем он вызывает dup2() с каналом записи трубы и STDOUT_FILENOчтобы написать stdout эквивалентно записи в трубу. Затем один из семи exec() функции вызывается, чтобы начать выполнение ls, ls пишет stdout, но так как мы продублировали stdout на канал записи канала, ls буду писать в трубу.
  4. В 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 и сколько каналов вы создаете, но обратите внимание, что:

  1. Процесс никогда не сможет читать из канала и записывать в другой канал. Например, если outfile не является STDOUT а также infile не является STDINв итоге вы закрываете каналы чтения и записи (и, что еще хуже, после закрытия канала чтения вы пытаетесь продублировать его). Это никак не сработает.
  2. Родительский процесс закрывает каждый канал перед ожиданием завершения дочерних процессов. Это провоцирует состояние гонки.

Я предлагаю изменить способ управления трубами. В этом ответе вы можете увидеть пример работающей оболочки с открытыми контурами, работающей с трубами: https://stackoverflow.com/a/30415995/2793118

7

Другие решения


По вопросам рекламы [email protected]