Является ли вывод прочитанным из popen () ed FILE * завершенным до pclose ()?

pclose()Страница человека говорит:

Функция pclose () ожидает завершения связанного процесса и возвращает состояние завершения команды, возвращенное wait4 (2).

Я чувствую, что это означает, что если связанный FILE* создано popen() был открыт с типом "r" для того, чтобы прочитать commandвывод, то вы не уверены, что вывод завершен до после призыв к pclose(), Но после pclose()закрытый FILE* должно быть недействительным, так как вы можете быть уверены, что прочитали весь вывод command?

Чтобы проиллюстрировать мой вопрос на примере, рассмотрим следующий код:

// main.cpp

#include <iostream>
#include <cstdio>
#include <cerrno>
#include <cstring>
#include <sys/types.h>
#include <sys/wait.h>

int main( int argc, char* argv[] )
{
FILE* fp = popen( "someExecutableThatTakesALongTime", "r" );
if ( ! fp )
{
std::cout << "popen failed: " << errno << " " << strerror( errno )
<< std::endl;
return 1;
}

char buf[512] = { 0 };
fread( buf, sizeof buf, 1, fp );
std::cout << buf << std::endl;

// If we're only certain the output-producing process has terminated after the
// following pclose(), how do we know the content retrieved above with fread()
// is complete?
int r = pclose( fp );

// But if we wait until after the above pclose(), fp is invalid, so
// there's nowhere from which we could retrieve the command's output anymore,
// right?

std::cout << "exit status: " << WEXITSTATUS( r ) << std::endl;

return 0;
}

Мои вопросы, как указано выше: если мы уверены, что дочерний процесс, производящий вывод, завершился после pclose()откуда мы знаем содержимое, полученное с помощью fread() завершено? Но если мы будем ждать, пока после pclose(), fp неверно, так что больше нет места, откуда мы могли бы получить вывод команды, верно?

Это похоже на проблему с курицей и яйцом, но я видел код, похожий на описанный выше, поэтому я, вероятно, что-то неправильно понимаю. Я благодарен за объяснение этого.

0

Решение

TL; DR резюме: Как мы узнаем, что содержимое, полученное с помощью fread (), завершено? — у нас есть EOF.

Вы получаете EOF, когда дочерний процесс закрывает свой конец канала. Это может произойти, когда он вызывает close эксплицитно или же выходы. Ничто не может выйти из твоего конца трубы после этого. После получения EOF вы не знаете, завершился ли процесс, но вы точно знаете, что он никогда ничего не запишет в канал.

По телефону pclose Вы закрываете свой конец трубы а также ждать окончания ребенка. когда pclose возвращается, вы знаете, что ребенок был прекращен.

Если вы позвоните pclose без получения EOF, и ребенок пытается записать материал в конец канала, он потерпит неудачу (фактически он получит SIGPIPE и, вероятно, умрет).

Здесь абсолютно нет места для ситуации с курицей и яйцом.

2

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

Читать документация для popen более тщательно:

pclose() Функция должна закрыть поток, который был открыт popen(), дождитесь окончания команды, и вернуть статус завершения процесса, который выполнял интерпретатор командного языка.

Он блокирует и ждет.

0

Я изучил несколько вещей, исследуя эту проблему далее, которые, я думаю, отвечают на мой вопрос:

По сути: да, это безопасно fread от FILE* вернулся popen до pclose, Предполагая, что буфер передан fread достаточно велик, вы не будете «пропускать» вывод, сгенерированный command дано popen,

Возвращаясь и тщательно обдумывая, что fread делает: эффективно блокирует до (size * nmembбайт был прочитан или конец файла (или ошибка).

Благодаря C — труба без использования popen, Я лучше понимаю что popen делает под капотом: это делает dup2 перенаправить его stdout на конец записи канала, который он использует. Важно отметить, что он выполняет некоторую форму exec выполнить указанное command в раздвоенном процессе, и после завершения этого дочернего процесса его дескрипторы открытых файлов, включая 1 (stdout) закрыты. То есть прекращение указанного command это условие, при котором дочерний процесс » stdout закрыто.

Затем я вернулся и подумал более тщательно о том, что EOF действительно был в этом контексте. Сначала я был под слабым и ошибочным впечатлением, что «fread пытается читать с FILE* так быстро, как может и возвращает / разблокирует после прочтения последнего байта«. Это не совсем так: как отмечено выше: fread будет читать / блокировать, пока не будет прочитано целевое число байтов или EOF или ошибка обнаружена. FILE* вернулся popen исходит от fdopen чтения конца трубы, используемой popen, так что это EOF происходит, когда дочерний процесс stdout — который был dup2редактирование с записью конца трубы — закрыто.

Итак, в итоге мы имеем: popen создание канала, конец записи которого получает выходные данные дочернего процесса, выполняющего указанный commandи чье чтение заканчивается, если fdopenред FILE* перешел к fread, (Предполагается, что freadбуфер достаточно большой), fread будет блокировать до EOF происходит, что соответствует закрытию конца записи popenтруба в результате прекращения выполнения command, То есть так как fread блокируется до EOF встречается, и EOF происходит после command — работает в popenдочерний процесс — завершается, безопасно использовать fread (с достаточно большим буфером) для получения полного вывода command дано popen,

Благодарен, если кто-нибудь может проверить мои выводы и выводы.

0

popen () — это просто ярлык для ряда fork, dup2, execv, fdopen и т. д. Он даст нам доступ к дочерним STDOUT, STDIN через потоковую операцию с файлами.

После popen () родительский и дочерний процессы выполняются независимо.
pclose () не является функцией «kill», она просто ждет завершения дочернего процесса. Поскольку это блокирующая функция, выходные данные, сгенерированные во время выполнения pclose (), могут быть потеряны.

Чтобы избежать потери этих данных, мы будем вызывать pclose () только тогда, когда мы знаем, что дочерний процесс уже завершен: вызов fgets () вернет NULL или возврат fread () из блокировки, общий поток достигнет конца, а EOF () верните истину.

Вот пример использования popen () с fread (). Эта функция возвращает -1, если выполняющийся процесс завершился неудачей, 0, если Ok. Дочерние выходные данные возвращаются в szResult.

int exec_command( const char * szCmd, std::string & szResult ){

printf("Execute commande : [%s]\n", szCmd );

FILE * pFile = popen( szCmd, "r");
if(!pFile){
printf("Execute commande : [%s] FAILED !\n", szCmd );
return -1;
}

char buf[256];

//check if the output stream is ended.
while( !feof(pFile) ){

//try to read 255 bytes from the stream, this operation is BLOCKING ...
int nRead = fread(buf, 1, 255, pFile);

//there are something or nothing to read because the stream is closed or the program catch an error signal
if( nRead > 0 ){
buf[nRead] = '\0';
szResult += buf;
}
}

//the child process is already terminated. Clean it up or we have an other zoombie in the process table.
pclose(pFile);

printf("Exec command [%s] return : \n[%s]\n",  szCmd, szResult.c_str() );
return 0;
}

Обратите внимание, что все операции с файлами в обратном потоке работают в режиме BLOCKING, поток открыт без флагов O_NONBLOCK. Fread () может быть заблокирован навсегда, когда дочерний процесс зависает и завершается, поэтому используйте popen () только с доверенной программой.

Чтобы получить больше контроля над дочерним процессом и избежать операции блокирования файлов, мы должны сами использовать fork / vfork / execlv и т. Д., Модифицировать каналы, открытые с помощью флагов O_NONBLOCK, время от времени использовать poll () или select (), чтобы определите, есть ли какие-то данные, затем используйте функцию read () для чтения из канала.

Периодически используйте waitpid () с WNOHANG, чтобы увидеть, был ли завершен дочерний процесс.

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