Категории
Самые читаемые
PochitayKnigi » Компьютеры и Интернет » Программное обеспечение » Linux программирование в примерах - Арнольд Роббинс

Linux программирование в примерах - Арнольд Роббинс

Читать онлайн Linux программирование в примерах - Арнольд Роббинс

Шрифт:

-
+

Интервал:

-
+

Закладка:

Сделать
1 ... 85 86 87 88 89 90 91 92 93 ... 165
Перейти на страницу:

#include <unistd.h> /* GLIBC */

long int TEMP_FAILURE_RETRY(expression);

Вот определение макроса:

/* Оценить EXPRESSION и повторять, пока оно возвращает -1 с 'errno',

    установленным в EINTR. */

# define TEMP_FAILURE_RETRY(expression)

 (__extension__

  ({ long int __result;

   do __result = (long int)(expression);

   while (__result == -1L && errno == EINTR);

   __result; }))

Макрос использует расширение GCC к языку С (как обозначено ключевым словом __extension__), которое допускает заключенным в фигурные скобки внутри обычных скобок выражениям возвращать значение, действуя таким образом подобно простому выражению.

Используя этот макрос, мы могли бы переписать safe_read() следующим образом:

size_t safe_read(int fd, void const *buf, size_t count) {

 ssize_t result;

 /* Ограничить count, как в ранее приведенном комментарии. */

 if (count > INT_MAX)

  count = INT_MAX & ~8191;

 result = TEMP_FAILURE_RETRY(read(fd, buf, count));

 return (size_t)result;

}

10.4.5. Состояния гонок и sig_atomic_t (ISO C)

Пока обработка одного сигнала за раз выглядит просто: установка обработчика сигнала в main() и (не обязательная) переустановка самого себя обработчиком сигнала (или установка действия SIG_IGN) в качестве первого действия обработчика.

Но что произойдет, если возникнут два идентичных сигнала, один за другим? В частности, что, если ваша система восстановит действие по умолчанию для вашего сигнала, а второй сигнал появится после вызова обработчика, но до того, как он себя восстановит?

Или предположим, что вы используете bsd_signal(), так что обработчик остается установленным, но второй сигнал отличается от первого? Обычно обработчику первого сигнала нужно завершить свою работу до того, как запускается второй, а каждый обработчик сигнала не должен временно игнорировать все прочие возможные сигналы!

Оба случая относятся к состоянию гонки. Одним решением для этих проблем является как можно большее упрощение обработчиков сигналов. Это можно сделать, создав флаговые переменные, указывающие на появление сигнала. Обработчик сигнала устанавливает переменную в true и возвращается. Затем основная логика проверяет флаговую переменную в стратегических местах:

int sig_int_flag = 0; /* обработчик сигнала устанавливает в true */

void int_handler(int signum) {

 sig_int_flag = 1;

}

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

 bsd_signal(SIGINT, int_handler);

 /* ...программа продолжается... */

 if (sig_int_flag) {

  /* возник SIGINT, обработать его */

 }

 /* ...оставшаяся логика... */

}

(Обратите внимание, что эта стратегия уменьшает окно уязвимости, но не устраняет его).

Стандарт С вводит специальный тип — sig_atomic_t — для использования с такими флаговыми переменными. Идея, скрывающаяся за этим именем, в том, что присвоение значений переменным этого типа является атомарной операцией: т.е. они совершаются как одно делимое действие. Например, на большинстве машин присвоение значения int осуществляется атомарно, тогда как инициализация значений в структуре осуществляется либо путем копирования всех байтов в (сгенерированном компилятором) цикле, либо с помощью инструкции «блочного копирования», которая может быть прервана. Поскольку присвоение значения sig_atomic_t является атомарным, раз начавшись, оно завершается до того, как может появиться другой сигнал и прервать его.

Наличие особого типа является лишь частью истории. Переменные sig_atomic_t должны быть также объявлены как volatile:

volatile sig_atomic_t sig_int_flag = 0; /* обработчик сигнала устанавливает в true */

/* ...оставшаяся часть кода как раньше... */

Ключевое слово volatile сообщает компилятору, что переменная может быть изменена извне, за спиной компилятора, так сказать. Это не позволяет компилятору применить оптимизацию, которая могла бы в противном случае повлиять на правильность кода

Структурирование приложения исключительно вокруг переменных sig_atomic_t ненадежно. Правильный способ обращения с сигналами показан далее, в разделе 10.7 «Сигналы для межпроцессного взаимодействия».

10.4.6. Дополнительные предостережения

Стандарт POSIX предусматривает для обработчиков сигналов несколько предостережений:

• Что случается, когда возвращаются обработчики для SIGFPE, SIGILL, SIGSEGV или любых других сигналов, представляющих «вычислительные исключения», не определено.

• Если обработчик был вызван в результате вызова abort(), raise() или kill(), он не может вызвать raise(). abort() описана в разделе 12.4 «Совершение самоубийства: abort()», a kill() описана далее в этой главе. (Описанная далее функция API sigaction() с обработчиком сигнала, принимающая три аргумента, дает возможность сообщить об этом, если это имеет место.)

• Обработчики сигналов могут вызвать лишь функции из табл. 10.2. В частности, они должны избегать функций <stdio.h>. Проблема в том, что во время работы функции <stdio.h> может возникнуть прерывание, когда внутреннее состояние библиотечной функции находится в середине процесса обновления. Дальнейшие вызовы функций <stdio.h> могут повредить это внутреннее состояние.

Список в табл. 10.2 происходит из раздела 2.4 тома System Interfaces (Системные интерфейсы) стандарта POSIX 2001. Многие из этих функций относятся к сложному API и больше не рассматриваются в данной книге.

Таблица 10.2. Функции, которые могут быть вызваны из обработчика сигнала

_Exit() fpathconf() raise() sigqueue() _exit() fstat() read() sigset() accept() fsync() readlink() sigsuspend() access() ftruncate() recv() sleep() aio_error() getegid() recvfrom() socket() aio_return() geteuid() recvmsg() socketpair() aio_suspend() getgid() rename() stat() alarm() getgroups() rmdir() sysmlink() bind() getpeername() select() sysconf() cfgetispeed() getpgrp() sem_post() tcdrain() cfgetospeed() getpid() send() tcflow() cfsetispeed() getppid() sendmsg() tcflush() cfsetospeed() getsockname() sendto() tcgetattr() chdir() getsockopt() setgid() tcgetpgrp() chmod() getuid() setpgid() tcsendbreak() chown() kill() setsid() tcsetattr() clock_gettime() link() setsockopt() tcsetpgrp() close() listen() setuid() time() connect() lseek() shutdown() timer_getoverrun() creat() lstat() sigaction() timer_gettime() dup() mkdir() sigaddset() timer_settime() dup2() mkfifo() sigdelset() times() execle() open() sigemptyset() umask() execve() pathconf() sigfillset() uname() fchmod() pause() sigismember() unlink() fchown() pipe() signal() utime() fcntl() poll() sigpause() wait() fdatasync() posix_trace_event() sigpending() waitpid() fork() pselect() sigprocmask() write()

10.4.7. Наша история до настоящего времени, эпизод 1

1 ... 85 86 87 88 89 90 91 92 93 ... 165
Перейти на страницу:
Тут вы можете бесплатно читать книгу Linux программирование в примерах - Арнольд Роббинс.
Комментарии