10.4.4. Системные вызовы, допускающие повторный запуск

10.4.4. Системные вызовы, допускающие повторный запуск

Значение EINTR для errno (см. раздел 4.3 «Определение ошибок») указывает, что системный вызов был прерван. Хотя с этим значением ошибки может завершаться большое количество системных вызовов, двумя наиболее значительными являются read() и write(). Рассмотрите следующий код:

void handler(int signal) { /* обработка сигналов */ }

int main(int argc, char **argv) {

 signal(SIGINT, handler);

 ...

 while ((count = read(fd, buf, sizeof buf)) > 0) {

  /* Обработка буфера */

 }

 if (count == 0)

  /* конец файла, очистка и т.п. */

 else if (count == -1)

  /* ошибка */

 ...

}

Предположим, что система успешно прочла (и заполнила) часть буфера, когда возник SIGINT. Системный вызов read() еще не вернулся из ядра в программу, но ядро решает, что оно может доставить сигнал. Вызывается handler(), запускается и возвращается в середину read(). Что возвратит read()?

В былые времена (V7, более ранние системы System V) read() возвратила бы -1 и установила бы errno равным EINTR. Не было способа сообщить, что данные были переданы. В данном случае V7 и System V действуют, как если бы ничего не случилось: не было перемещений данных в и из буфера пользователя, и смещение файла не было изменено. BSD 4.2 изменила это. Были два случая:

Медленные устройства

«Медленное устройство» является в сущности терминалом или почти всяким другим устройством, кроме обычного файла. В этом случае read() могла завершиться с ошибкой EINTR, лишь если не было передано никаких данных, когда появился сигнал. В противном случае системный вызов был бы запущен повторно, и read() возвратилась бы нормально.

Обычные файлы

Системный вызов был бы запущен повторно В этом случае read() вернулась бы нормально; возвращенное значение могло быть либо числом запрошенных байтов, либо числом действительно прочитанных байтов (как в случае чтения вблизи конца файла).

Поведение BSD несомненно полезно; вы всегда можете сказать, сколько данных было прочитано.

Поведение POSIX сходно, но не идентично первоначальному поведению BSD. POSIX указывает, что read()[108] завершается с ошибкой EINTR лишь в случае появления сигнала до начала перемещения данных. Хотя POSIX ничего не говорит о «медленных устройствах», на практике это условие проявляется именно на них.

В противном случае, если сигнал прерывает частично выполненную read(), возвращенное значение является числом уже прочитанных байтов. По этой причине (а также для возможности обработки коротких файлов) всегда следует проверять возвращаемое read() значение и никогда не предполагать, что прочитано все запрошенное количество байтов. (Функция POSIX API sigaction(), описанная позже, позволяет при желании получить поведение повторно вызываемых системных вызовов BSD.)