Как обрабатывать EINTR (прерванный системный вызов)

Мое приложение для пользовательского пространства иногда блокируется после получения EINTR-сигнала каким-то образом.

Что я записал с помощью strace:

time(NULL) = 1257343042
time(NULL) = 1257343042
rt_sigreturn(0xbff07be4) = -1 EINTR (Interrupted system call)
--- SIGALRM (Alarm clock) @ 0 (0) ---
time(NULL) = 1257343042
futex(0xb7cea80c, 0x80 /* FUTEX_??? */, 2) = ? ERESTARTSYS (To be restarted)
--- SIGUSR1 (User defined signal 1) @ 0 (0) ---
sigreturn() = ? (mask now [ALRM])
futex(0xb7cea80c, 0x80 /* FUTEX_??? */, 2) = ? ERESTARTSYS (To be restarted)
--- SIGWINCH (Window changed) @ 0 (0) ---
futex(0xb7cea80c, 0x80 /* FUTEX_??? */, 2) = ? ERESTARTSYS (To be restarted)
--- SIGTERM (Terminated) @ 0 (0) ---
time(NULL) = 1257343443
time(NULL) = 1257343443
futex(0xb7cea80c, 0x80 /* FUTEX_??? */, 2) = ? ERESTARTSYS (To be restarted)
--- SIGWINCH (Window changed) @ 0 (0) ---
futex(0xb7cea80c, 0x80 /* FUTEX_??? */, 2

Могу ли я поймать сигнал EINTR и как я могу повторить связанные вызовы, такие как запись, чтение или выбор? Как я могу определить, где этот EINTR произошел, даже если я использовал сторонние библиотеки, работающие с системными вызовами?

Почему мое приложение полностью заблокировано после получения EINTR (см. дамп strace: я отправил SIGUSR1, который обычно должен обрабатываться)? И почему futex() возвращает ERESTARTSYS в пространство пользователя?

спасибо

2 ответа

Код, который вызывает запись (или другие операции блокировки), должен знать об EINTR. Если во время операции блокировки возникает сигнал, тогда операция будет либо (a) возвращать частичное завершение, либо (b) вернуть сбой, ничего не делать и установить errno в EINTR.

Итак, для операции записи "все-или-сбой", которая повторяет попытки прерывания, вы сделали бы что-то вроде этого:

while(size > 0) {
 int written = write(filedes, buf, size);
 if (written == -1) {
 if (errno == EINTR) continue;
 return -1;
 }
 buf += written;
 size -= written;
}
return 0; // success

Или для чего-то более хорошего поведения, которое повторяет EINTR, записывает столько, сколько может, и сообщает, сколько написано в случае сбоя (так что вызывающий может решить, продолжать ли и как продолжать частичную запись, которая не работает по другой причине прерывание по сигналу):

int total = 0;
while(size > 0) {
 int written = write(filedes, buf, size);
 if (written == -1) {
 if (errno == EINTR) continue;
 return (total == 0) ? -1 : total;
 }
 buf += written;
 total += written;
 size -= written;
}
return total; // bytes written

GNU имеет нестандартный макрос TEMP_FAILURE_RETRY, который может представлять интерес, хотя я никогда не могу найти документы для него, когда захочу. Включая сейчас.


См. также обсуждение "режима неудач" в Хуже лучше

licensed under cc by-sa 3.0 with attribution.