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
неверно, так что больше нет места, откуда мы могли бы получить вывод команды, верно?
Это похоже на проблему с курицей и яйцом, но я видел код, похожий на описанный выше, поэтому я, вероятно, что-то неправильно понимаю. Я благодарен за объяснение этого.
TL; DR резюме: Как мы узнаем, что содержимое, полученное с помощью fread (), завершено? — у нас есть EOF.
Вы получаете EOF, когда дочерний процесс закрывает свой конец канала. Это может произойти, когда он вызывает close
эксплицитно или же выходы. Ничто не может выйти из твоего конца трубы после этого. После получения EOF вы не знаете, завершился ли процесс, но вы точно знаете, что он никогда ничего не запишет в канал.
По телефону pclose
Вы закрываете свой конец трубы а также ждать окончания ребенка. когда pclose
возвращается, вы знаете, что ребенок был прекращен.
Если вы позвоните pclose
без получения EOF, и ребенок пытается записать материал в конец канала, он потерпит неудачу (фактически он получит SIGPIPE
и, вероятно, умрет).
Здесь абсолютно нет места для ситуации с курицей и яйцом.
Читать документация для popen
более тщательно:
pclose()
Функция должна закрыть поток, который был открытpopen()
, дождитесь окончания команды, и вернуть статус завершения процесса, который выполнял интерпретатор командного языка.
Он блокирует и ждет.
Я изучил несколько вещей, исследуя эту проблему далее, которые, я думаю, отвечают на мой вопрос:
По сути: да, это безопасно 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
,
Благодарен, если кто-нибудь может проверить мои выводы и выводы.
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, чтобы увидеть, был ли завершен дочерний процесс.