Используя self-pipe, как я могу избежать остановки цикла чтения в read ()?
Я пытаюсь использоватьсамостоятельно трубы хитрость, чтобы получить портативную реализацию (через Solaris, MacOSX, Linux, BSD) моего приложения.
Так в дополнение к двум трубам дляstderr
а такжеstdout
который я использую, чтобы получить вывод раздвоенного ребенка (я не используюexec
у ребенка ребенок выполняет тот же код, что и родитель), у меня есть канал для сигналов (enum {SIG_PIPE, STDOUT_PIPE, STDERR_PIPE, MAX_PIPE}
обеспечивает символические имена).
O_NONBLOCK
устанавливается на трубы, до вызоваhandle_child_output()
.у ребенка есть концы записиstderr
а такжеstdout
трубы и продолжает использоватьprintf()
и друзья, эффективно пишущие в каждую соответствующую трубу (setvbuf
используется для отключения буферизации внутри дочернего элемента).Код, который нужно выполнить, немного длинен, как и трюк с самоконтролем в целом. Это уже сжатая форма. Итак, позвольте мне попытаться объяснить, что должно произойти и где оно застряло.
I необходимость чтобы получить статус выхода, и я должен быть в состоянии выяснить, был ли ребенок закончен с сигналом или через выход. Обработка этих условий в другом месте. Что важно, так этоhandle_child_output()
возвращает код выхода ребенка внутриint
указал наpstatus
.
Внешнийdo
-while
зациклитьсяhandle_child_output()
создастFD_SET
использовать вselect
вызов. Он добавляет конец чтения сигнальной трубы плюс конец чтенияstderr
а такжеstdout
трубка от ребенка.
Тогдаif(FD_ISSET(fds[SIG_PIPE], &rd))
проверяет, содержит ли сигнальная труба что-то новое, и сливает его, обрабатывая любые потенциальные сигналы
for
цикл после этого перебирает оставшиеся файловые дескрипторы, чтобы увидеть, находится ли что-либо в ожидании, а затем обрабатывает, попугая, что он получает на соответствующих родителейstdio
канал.
Вызов чтения во втором цикле, где он останавливается.
Симптом заключается в том, что родитель застрял в вызове чтения:
(gdb) bt 1
#0 0x00007f2daaa9e530 in __read_nocancel () from /lib64/libpthread.so.0
Как будто существует состояние гонки между чтением сигнального канала и другими каналами. Кажется, что ребенок уже вышел к тому времени, когда файловые дескрипторы проверяются и, следовательно, оказывается зомби, потому что родитель все еще застрял вread()
и никогда не достигнет одного изwait()
звонки.
Что я делаю неправильно? Было бы законно добавитьif(exitloop) break;
передfor
цикл, чтобы вырваться из внешнегоdo
-while
цикл? Мне кажется, что это может оставить непрочитанный контент в каналах, нет?
#define __MAX__(x,y) ((x) > (y) ? (x) : (y))
int childpid;
typedef enum { READ, WRITE, BOTH } pipefd_type_t;
static void avoid_zombie(int* pstatus)
{
int temp;
pstatus = (pstatus) ? pstatus : &temp;
if(0 > childpid && 0 != childpid) {
kill(childpid, SIGKILL); /* kill the child */
}
wait(pstatus); /* wait to avoid lingering zombies */
}
static void select_signal_handler(int sig)
{
int savedErrno = errno;
const int sigw = sig;
if(0 > write(sigpipe[WRITE], &sigw, sizeof(sigw))) {
avoid_zombie(NULL);
_exit(EXIT_FAILURE); /* actual code also shows error etc */
}
errno = savedErrno;
}
void handle_child_output(int *pstatus)
{
enum {SIG_PIPE, STDOUT_PIPE, STDERR_PIPE, MAX_PIPE};
fd_set rd;
int ready, n = 0, fds[MAX_PIPE];
size_t i, exitloop, sigint;
struct sigaction sa;
struct {
int sig;
struct sigaction oldsa;
} old_sigactions[3];
old_sigactions[0].sig = SIGINT;
old_sigactions[1].sig = SIGCHLD;
old_sigactions[2].sig = SIGQUIT;
/* fds have been initialized at this point (read-ends) */
for(i = 0; i < sizeof(old_sigactions)/sizeof(old_sigactions[0]); i++) {
sigemptyset(&sa.sa_mask);
sa.sa_flags = SA_RESTART;
sa.sa_handler = select_signal_handler;
if(0 > sigaction(old_sigactions[i].sig, &sa, &old_sigactions[i].oldsa)) {
avoid_zombie(pstatus);
_exit(EXIT_FAILURE); /* actual code also shows error etc */
}
}
do {
sigint = 0;
exitloop = 0;
FD_ZERO(&rd);
for(i = 0; i < MAX_PIPE; i++) {
if(fds[i] >= FD_SETSIZE) {
avoid_zombie(pstatus);
_exit(EXIT_FAILURE); /* actual code also shows error etc */
}
FD_SET(fds[i], &rd);
n = __MAX__(n, fds[i]);
}
while(0 > (ready = select(n+1, &rd, NULL, NULL, NULL)))
if (EINTR == errno) continue;
if(0 > ready) {
avoid_zombie(pstatus);
_exit(EXIT_FAILURE); /* actual code also shows error etc */
}
if(FD_ISSET(fds[SIG_PIPE], &rd)) {
do { /* drain the signal pipe */
int sig = -1;
if(0 > read(fds[SIG_PIPE], &sig, sizeof(sig))) {
if(EAGAIN == errno)
break;
else {
avoid_zombie(pstatus);
_exit(EXIT_FAILURE); /* actual code also shows error etc */
}
}
switch(sig) {
case SIGINT:
if(0 > childpid && 0 != childpid) {
kill(childpid, SIGINT); /* pass to child */
wait(pstatus);
}
sigint++; exitloop++;
break;
case SIGCHLD:
exitloop++;
break;
}
} while(1);
}
for(i = STDOUT_PIPE; i < MAX_PIPE; i++) {
if(FD_ISSET(fds[i], &rd)) {
do {
const size_t max_tries = 5;
char buf[0x1000];
ssize_t bytesWritten, bytesRead = read(fds[i], buf, sizeof(buf));
if(0 == bytesRead)
break;
int outchan = STDERR_FILENO;
if(0 > bytesRead) {
/* Retry (inner do-while loop) if we get an EAGAIN */
if(EAGAIN == errno) break;
/* fatal error */
avoid_zombie(pstatus);
_exit(EXIT_FAILURE); /* actual code also shows error etc */
}
if(STDOUT_PIPE == i)
outchan = STDOUT_FILENO;
bytesWritten = write(outchan, buf, bytesRead);
if(0 > bytesWritten) {
/* Retry if we get an EAGAIN */
if(EAGAIN == errno) {
size_t tries;
for(tries = 0; tries < max_tries; tries++) {
bytesWritten = write(outchan, buf, bytesRead);
if((0 > bytesWritten) && (EAGAIN == errno))
continue;
break;
}
}
if(0 > bytesWritten) {
avoid_zombie(pstatus);
_exit(EXIT_FAILURE); /* actual code also shows error etc */
}
}
if(bytesWritten < bytesRead) {
const ssize_t bytesToWrite = bytesRead - bytesWritten;
/* try to write the remainder */
ssize_t bytesWritten2 = write(outchan, &buf[bytesWritten], bytesToWrite);
if((0 > bytesWritten2) || (bytesWritten2 != bytesToWrite)) {
/* fatal error */
avoid_zombie(pstatus);
_exit(EXIT_FAILURE); /* actual code also shows error etc */
}
}
} while(1);
}
}
} while(0 == exitloop);
/* restore old signal handlers */
for(i = 0; i < sizeof(old_sigactions)/sizeof(old_sigactions[0]); i++) {
if (sigaction(old_sigactions[i].sig, &old_sigactions[i].oldsa, NULL) == -1) {
avoid_zombie(pstatus);
_exit(EXIT_FAILURE); /* actual code also shows error etc */
}
}
avoid_zombie(pstatus);
}