Сигналы c linux. Надежные сигналы

28.03.2019

Серия контента:

В современных операционных системах существует понятие межпроцессного взаимодействия (Inter-Process Communication – IPC) – это набор способов обмена данными между процессами и/или потоками. Одним из таких способов обмена служат сигналы. Концепцию сигналов поддерживает большинство операционных систем, но, например, Windows, не имеет их полноценной поддержки для использования в качестве одного из способов IPC – в подобных операционных системах сигналы лишь реализованы в стандартной библиотеке языка C.

Концепция сигналов

Сигналы способны в случайное время (асинхронно) прерывать процесс для обработки какого-либо события. Процесс может быть прерван сигналом по инициативе другого процесса или ядра. Ядро использует сигналы для извещения процессов о различных событиях, например о завершении дочернего процесса.

Сигналы имеют определенный жизненный цикл. Вначале сигнал создается – высылается процессом или генерируется ядром. Затем сигнал ожидает доставки в процесс-приемник. Принято считать, что период ожидания сигнала равен промежутку времени между созданием сигнала и действием, которое этот сигнал высылает. В конце жизненного цикла сигнала происходит его перехват (прием) процессом и выполнение связанных с сигналом действий.

Простые сигналы и надежные сигналы

Существует деление сигналов на простые и надежные.

Изначально были разработаны и использовались простые (ненадежные) сигналы. По своему принципу работы они похожи на канонический механизм обработки аппаратных прерываний в процессоре. Если процесс хочет особым образом обрабатывать некий сигнал, то он сообщает ядру об этом, указывая специальную функцию – обработчик сигнала. При доставке сигнала процессу ядро как можно скорее вызывает обработчик сигнала, прерывая работу процесса. По завершении работы обработчика выполнение процесса продолжается с того места, где он был прерван.

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

Вся эта концепция работы с сигналами выглядит достаточно хорошо, пока сигнал не приходит процессу в то время, когда он уже занят обработкой другого сигнала. Здесь и проявляются проблемы – повторно вызванный обработчик сигнала может испортить те разделяемые ресурсы (общие структуры данных и переменные), которые он использует. Кроме того, в случае прихода большого количества сигналов стек процесса может неограниченно увеличиваться, что может привести к сбоям в работе программы.

В процессе решения проблемы были разработаны надежные сигналы, которые стандартизованы в POSIX и используются по сей день. Далее в статье рассматриваются именно надежные сигналы.

Сигналы и системные вызовы

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

На сегодняшний момент существует реализация сигналов, которая свободна от этих недостатков. Решение состоит в том, что в обработчике сигнала следует лишь установить флаг, обозначающий, что сигнал получен, а затем нужно обеспечить возвращение из системного вызова с кодом ошибки, обозначающим прерывание вызова сигналом. Далее программа должна проверить флаг, установленный обработчиком сигнала, и выполнить соответствующие действия, например, очистить терминал и завершиться.

Современная реализация сигналов заставляет медленные системные вызовы возвращать код ошибки EINTR, когда они прерываются приходящим сигналом. Быстрые системные вызовы должны завершаться перед тем, как сигнал будет доставлен. Медленный системный вызов – системный вызов, требующий неопределенного количества времени для своего завершения, например read(), wait(), write(). Все системные вызовы, зависящие от непредсказуемых ресурсов, таких как действия человека, сетевые данные и т.п., являются медленными. Естественно, все остальные системные вызовы – быстрые.

Обычно программа обрабатывает код ошибки EINTR и, если не произошло ничего фатального, перезапускает системный вызов. Большинство Unix-like операционных систем в настоящее время делает это по умолчанию – нужно лишь обработать сигнал в обработчике, а системный вызов будет перезапущен автоматически. В Linux по умолчанию системные вызовы не перезапускаются, но для каждого сигнала процесс может установить флаг, указывающий системе о необходимости перезапуска медленных системных вызовов, прерванных этим сигналом.

Посылка сигналов

Посылка сигналов от одного процесса к другому обычно осуществляется при помощи системного вызова kill(). Его первый параметр – PID процесса, которому посылается сигнал; второй параметр – номер сигнала. Если мы хотим послать сигнал SIGTERM процессу с PID 6666, то используем системный вызов kill() так:

kill(6666, SIGTERM);

Вместо положительного значения PID можно передать вызову равное по модулю, но отрицательное значение. Тогда сигнал будет послан всем процессам из группы с номером, равным модулю переданного PID"а. Если PID равен 0, то сигнал посылается всем процессам из группы, к которой относится и текущий процесс. Эти возможности используются в основном оболочками для управления заданиями.

Если в качестве PID передать в системный вызов kill() значение -1, то сигнал будет послан всем процессам за исключением init"а. Такая возможность применяется для завершения работы системы.

Более подробная информация по системному вызову kill() приведена в man 2 kill.

Послать сигнал самому себе процесс может при помощи системного вызова raise(), который принимает один параметр – номер сигнала. Пример:

raise(SIGTERM);

Естественно, каждый из рассмотренных системных вызовов возвращает ноль в случае успешного выполнения и ненулевое значение, если произошла какая-нибудь ошибка.

Перехват сигналов

Все программы, подчиняющиеся стандарту POSIX, регистрируют свои обработчики сигналов при помощи системного вызова sigaction(). Этот системный вызов имеет три параметра: первый – int signum – номер перехватываемого сигнала. Второй – struct sigaction * act – указатель на структуру, описывающую правила установки обработчика. Третий параметр – struct sigaction * oact – принимает уже установленные правила обработчика сигнала. Либо второй, либо третий (но не оба сразу!) параметр можно установить в NULL при необходимости.

Структура struct sigaction имеет следующее описание:

struct sigaction { __sighandler_t sa_handler; sigset_t sa_mask; int sa_flags; };

sa_handler – указатель на обработчик сигнала, причем обработчик должен быть объявлен следующим образом:

void signal_handler(int signo);

где единственный параметр – номер сигнала, который попал в обработчик. sa_handler также может быть равным SIG_IGN – сигнал игнорируется процессом, и SIG_DFL – сигнал вызывает действие по умолчанию, например прерывание процесса.

sa_mask – набор сигналов, которые должны блокироваться при вызове заданного в этой же структуре обработчика сигнала. Как их устанавливать, рассматривается далее.

Параметр sa_flags позволяет процессу модифицировать поведение сигнала. Параметр может принимать всего четыре значения, которые, впрочем, можно объединять при помощи битовой операции "ИЛИ":

  1. SA_NOCLDSTOP – отсылать сигнал SIGCHLD только в случае прерывания дочернего процесса. Приостановка дочернего процесса не вызывает посылки сигнала.
  2. SA_NODEFER – эмуляция простых (ненадежных) сигналов.
  3. SA_RESTHAND – после прихода сигнала его обработчик сбрасывается в SIG_DFL.
  4. SA_RESTART – перезапуск системного вызова после возврата из обработчика сигнала. Если флаг не установлен, то системный вызов возвращает ошибку EINTR.

Как обычно, при успешном выполнении sigaction() возвращается 0, а в случае ошибки – отрицательное значение.

Маска сигналов процесса

Добавление сигналов в структуру sigset_t sa_mask, ее очистка и т.п. осуществляются при помощи набора функций sigemptyset(), sigfillset(), sigaddset(), sigdelset(). Первые две функции принимают один параметр – указатель на структуру sigset_t. Эти функции очищают и заполняют всеми возможными сигналами структуру sigset_t соответственно.

Последние две функции, соответственно, добавляют и удаляют один определенный сигнал из структуры и имеют по два параметра. Их первый параметр – указатель на структуру sigset_t, а второй – номер сигнала.

Все рассмотренные выше функции возвращают 0 при успешном завершении и число, не равное нулю, – при ошибке.

Кроме того, существует еще одна функция, проверяющая, находится ли указанный сигнал в указанном наборе – sigismember(). Ее параметры совпадают с параметрами sigaddset(). Функция возвращает 1, если сигнал находится в наборе, 0 – если не находится, и отрицательное число – при возникшей ошибке.

Помимо всего прочего, мы можем задать список сигналов, доставка которых процессу будет заблокирована. Это выполняется при помощи функции sigprocmask(int how, const sigset_t * set, sigset_t * oldset).

Первый ее параметр описывает то, что должно выполняться:

  1. SIG_BLOCK – сигналы из набора set блокируются;
  2. SIG_UNBLOCK – сигналы из набора set разблокируются;
  3. SIG_SETMASK – сигналы из набора set блокируются, остальные разблокируются.

Второй параметр является указателем на тот самый набор, сигналы из которого блокируются/разблокируются. Если он равен NULL, то значение первого параметра игнорируется системным вызовом.

Третий параметр – указатель на уже используемую маску сигналов; его можно поставить в NULL, если эти данные не нужны.

Для получения списка ожидающих сигналов можно использовать функцию sigpending(), которая принимает единственный параметр – указатель на структуру sigset_t, куда будет записан набор ожидающих сигналов.

Принципы написания обработчиков сигналов

Одно из самых главных правил написания обработчиков сигналов – обработчик должен быть реентерабельным, т.е. он должен допускать свой повторный вызов, когда процесс уже находится в обработчике. Нужно заботиться о том, чтобы обработчик сигналов не использовал глобальные структуры данных или медленные системные вызовы. Если избежать этого, увы, невозможно, то стоит позаботиться о защите от повторного вызова обработчика во время работы со структурой данных или с системным вызовом. Добиться этого можно, заблокировав на время доставку сигнала, обработчик которого сейчас работает, при помощи системного вызова sigprocmask(). Например, мы имеем обработчик сигнала SIGCHLD, выполняем в нем блокировку так:

void chld_handler(int signum) { sigset_t set; if (sigemptyset(&set)) { return;) if (sigaddset(&set, SIGCHLD)) { return; } if (sigprocmask(SIG_BLOCK, &set, NULL)) { return; } /* делаем здесь что-то важное */ if (sigprocmask(SIG_UNBLOCK, &set, NULL)) { return; } return; }

Кроме всего перечисленного выше, считается, что обработчик должен быть максимально простым – в идеале он должен выставлять некий флаг и завершаться, а все остальное должна выполнять основная часть программы.

Заключение

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

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

Ресурсы для скачивания

static.content.url=http://www.сайт/developerworks/js/artrating/

ArticleID=495997

ArticleTitle=Работа с сигналами в Linux: Часть 1. Основы работы с сигналами

Сигналы в ОС Unix

Сигналы представляют собой средство уведомления процесса о наступлении некоторого события в системе.

Инициатором посылки сигнала может выступать как другой процесс, так и сама ОС.

Сигналы, посылаемые ОС, уведомляют о наступлении некоторых строго предопределенных ситуаций (как, например, завершение порожденного процесса, прерывание процесса нажатием комбинации Ctrl-C, попытка выполнить недопустимую машинную инструкцию, попытка недопустимой записи в канал и т.п.), при этом каждой такой ситуации сопоставлен свой сигнал.

Кроме того, зарезервирован один или несколько номеров сигналов, семантика которых определяется пользовательскими процессами по своему усмотрению (например, процессы могут посылать друг другу сигналы с целью синхронизации).

Количество различных сигналов в современных версиях UNIX около 30, каждый из них имеет уникальное имя и номер.

Описания представлены в файле .

В таблице приведено несколько примеров сигналов:

Числовое значение

Константа

Значение сигнала

Прерывание выполнения по нажатию Ctrl-C

Аварийное завершение работы

Уничтожение процесса

Прерывание от программного таймера

Завершился процесс-потомок

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

При получении сигнала процессом возможны три варианта реакции на полученный сигнал:

    Процесс реагирует на сигнал стандартным образом, установленным по умолчанию (для большинства сигналов действие по умолчанию – это завершение процесса)

    Процесс может установить специальную обработку сигнала, в этом случае по приходу сигнала вызывается функция-обработчик, определенная процессом (при этом говорят, что сигнал перехватывается)

    Процесс может проигнорировать сигнал

Для каждого сигнала процесс может устанавливать свой вариант реакции, например, некоторые сигналы он может игнорировать, некоторые перехватывать, а на остальные установить реакцию по умолчанию. При этом в процессе своей работы процесс может изменять вариант реакции на тот или иной сигнал. Однако, необходимо отметить, что

некоторые сигналы невозможно ни перехватить, ни игнорировать. Они используются ядром ОС для управления работой процессов (например, SIGKILL, SIGSTOP).

Если в процесс одновременно доставляется несколько различных сигналов, то порядок их обработки не определен . Если же обработки ждут несколько экземпляров одного и того же сигнала, то ответ на вопрос, сколько экземпляров будет доставлено в процесс – все или один – зависит от конкретной реализации ОС.

Отдельного рассмотрения заслуживает ситуация, когда сигнал приходит в момент выполнения системного вызова. Обработка такой ситуации в разных версиях UNIX реализована по-разному, например, обработка сигнала может быть отложена до завершения системного вызова; либо системный вызов автоматически перезапускается после его прерывания сигналом; либо системный вызов вернет –1, а в переменной errno будет установлено значение EINTR

Для отправки сигнала существует системный вызов kill():

#include

#include

int kill (pid _ t pid , int sig );

pid идентификатор процесса, которому посылается сигнал (в частности, процесс может послать сигнал самому себе). Существует также возможность одновременно послать сигнал нескольким процессам,

например, если значение этого параметра есть 0, сигнал будет передан всем процессам, которые принадлежат той же группе, что и процесс, посылающий сигнал, за исключением процессов с идентификаторами 0 и 1.

sig номер посылаемого сигнала.

Если этот параметр равен 0, то будет выполнена проверка корректности обращения к kill() (в частности, существование процесса с идентификатором pid), но никакой сигнал в действительности посылаться не будет.

Если процесс-отправитель не обладает правами привилегированного пользователя, то он может отправить сигнал только тем процессам, у которых реальный или эффективный идентификатор владельца процесса совпадает с реальным или эффективным идентификатором владельца процесса-отправителя.

Для определения реакции на получение того или иного сигнала в процессе служит системный вызов signal():

#include

void (*signal

(int sig, void (*disp) (int))) (int);

sig - номер сигнала, для которого устанавливается реакция, disp - либо определенная пользователем функция-обработчик сигнала, либо одна из констант: SIG_DFL (обработка по умолчанию, т.е. стандартная реакцию системы,)и SIG_IGN (данный сигнал необходимо игнорировать).

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

Как видно из прототипа вызова signal(), определенная пользователем функция-обработчик сигнала должна принимать один целочисленный аргумент (в нем будет передан номер обрабатываемого сигнала), и не возвращать никаких значений.

Отметим одну особенность реализации сигналов в ранних версиях UNIX: каждый раз при получении сигнала его диспозиция (т.е. действие при получении сигнала) сбрасывается на действие по умолчанию, т.о. если процесс желает многократно обрабатывать сигнал своим собственным обработчиком, он должен каждый раз при обработке сигнала заново устанавливать реакцию на него.

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

В данном примере при получении сигнала S

IGINT четырежды вызывается специальный

обработчик, а в пятый раз происходит

обработка по умолчанию.

#include

#include

#include

int count = 0;

void SigHndlr (int s) /* обработчик сигнала */

printf("\n I got SIGINT %d time(s) \n",

++ count);

if (count == 5) signal (SIGINT, SIG_DFL);

/* ставим обработчик сигнала по умолчанию */

else signal (SIGINT, SigHndlr);

/* восстанавливаем обработчик сигнала */

int main(int argc, char **argv)

signal (SIGINT, SigHndlr); /* установка реакции на сигнал */

while (1); /*”тело программы” */

return 0;

Высокоуровневые средства межпроцессного

Многие программы Unix принимают сигналы типа USR1 и USR2 . Например, чтобы обновить исполняемый файл для Nginx "на лету", вы отправляете kill -USR2 .

Я понимаю, что USR1 является "определяемым пользователем" сигналом, что означает, что тот, кто создал программу, может использовать его для обозначения "выключить" или "выгрузить ваши журналы" или "распечатать foo тысячу раз" или что-то еще. Но я не понимаю, почему они должны использовать это произвольное имя. Почему бы не kill -UPGRADE , или kill -GRACEFUL_SHUTDOWN ? Имеет ли Unix только определенные сигналы?

Пока мы это делаем, Nginx также использует следующие сигналы (см. документация):

  • TERM, INT : быстрое отключение
  • QUIT : изящное завершение работы
  • HUP :
    • Конфигурация перезагрузки
    • Запустите новые рабочие процессы с новой конфигурацией
    • Изящное завершение работы старых рабочих процессов
  • USR1 : откройте файлы журнала
  • USR2 : обновление исполняемых на лету
  • WINCH : изящное завершение рабочих процессов

HUP? Winch? Какая причина этих имен? Где я могу узнать больше об этом?

6 ответов

Сигналы, доступные в ОС, определяются ОС (обычно после POSIX) - они не являются "строками", а целыми константами со стандартными именами. USR1 и USR2 - это два сигнала, которые не имеют конкретного значения, предназначенные для любого произвольного использования, которое хочет разработчик.

На вашей машине linux прочитайте man 7 signal для обзора обработки сигналов и сигналов.

Вы можете переопределить значение других сигналов, если вы готовы работать с ОС, выдавая эти сигналы в ответ на события. Вы можете, например, make HUP означает "перезагрузить конфигурацию" - если вы либо уверены, что процесс никогда не получит зависания (потеря терминала), либо вы готовы обрабатывать случаи, когда ОС, а не пользователь, посылает сигнал HUP.

HUP не подходит для "зависания". Этот сигнал отправляется процессу, если его управляющий терминал достигает конца файла. В прежние времена управляющие терминалы обычно подключались к последовательным портам, возможно, через модемную линию по телефонной линии. Если телефонное соединение было повёрнуто, локальный модем снизит линию Carrier Detect, что приведет к отправке отчета о завершении файла ядра и передаваемого сигнала SIGHUP .

WINCH не подходит для "изменения окна". Он отправляется процессу, если его управляющий терминал изменяет размер. По понятным причинам терминалы, которые могут изменять размер, обычно представляют собой псевдотерминалы, которые в конечном счете представлены терминальным эмулятором, работающим в среде окон (например, xterm).

Поскольку имена сигналов стандартизируются (POSIX). Вы можете написать свой собственный исполняемый файл kill-типа, чтобы взять -UPGRADE , если хотите, и доставить сигнал USR1 , но стандартный kill , который поставляется с UNIX, не узнает его.

В качестве альтернативы вы можете создать псевдоним, функцию или оболочку script для выполнения перевода для вас, например, с помощью псевдонима bash:

Alias upgrade="kill -USR1"

Файл заголовка signal.h сопоставляет имена сигналов с их фактическими значениями, зависящими от реализации.

В терминах WINCH я считаю это немного мерзостью. Это сигнал, который доставляется приложениям при изменении размера окна (особенно когда изменяется окно их управляющего терминала).

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

Попробуйте kill -l и найдите ответ самостоятельно:

1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL 5) SIGTRAP 6) SIGABRT 7) SIGBUS 8) SIGFPE 9) SIGKILL 10) SIGUSR1 11) SIGSEGV 12) SIGUSR2 13) SIGPIPE 14) SIGALRM 15) SIGTERM 16) SIGSTKFLT 17) SIGCHLD 18) SIGCONT 19) SIGSTOP 20) SIGTSTP 21) SIGTTIN 22) SIGTTOU 23) SIGURG 24) SIGXCPU 25) SIGXFSZ 26) SIGVTALRM 27) SIGPROF 28) SIGWINCH 29) SIGIO 30) SIGPWR 31) SIGSYS 34) SIGRTMIN 35) SIGRTMIN+1 36) SIGRTMIN+2 37) SIGRTMIN+3 38) SIGRTMIN+4 39) SIGRTMIN+5 40) SIGRTMIN+6 41) SIGRTMIN+7 42) SIGRTMIN+8 43) SIGRTMIN+9 44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13 48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-12 53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9 56) SIGRTMAX-8 57) SIGRTMAX-7 58) SIGRTMAX-6 59) SIGRTMAX-5 60) SIGRTMAX-4 61) SIGRTMAX-3 62) SIGRTMAX-2 63) SIGRTMAX-1 64) SIGRTMAX

Надежные сигналы

Стандарт POSIX. 1 определил новый набор функций управления сигналами. основанный на интерфейсе 4.2BSD UNIX и лишенный рассмотренных выше недостатков.

Модель сигналов, предложенная POSIX, основана на понятии набора сигналов (signal set), описываемого переменной типа sigset_t . Каждый бит этой переменной отвечает за один сигнал. Во многих системах тип sigset_t имеет длину 32 бита, ограничивая количество возможных сигналов числом 32.

Следующие функции позволяют управлять наборами сигналов:

#include

int sigempyset(sigset_t *set);

int siufillset(sigset_t *set);

int sigaddset(sigset_t *set, int signo);

int sigdelset(sigset_t *set, int signo);

int sigismember(sigset_t *set, int signo);

В отличие от функции signal(3C) , изменяющей диспозицию сигналов, данные функции позволяют модифицировать структуру данных sigset_t , определенную процессом. Для управления непосредственно сигналами используются дополнительные функции, которые мы рассмотрим позже.

Функция sigemptyset(3C) инициализирует набор, очищая все биты. Если процесс вызывает sigfillset(3C) , то набор будет включать все сигналы, известные системе. Функции sigaddset(3C) и sigdelset(3C) позволяют добавлять или удалять сигналы набора. Функция sigismember(3C) позволяет проверить, входит ли указанный параметром signo сигнал в набор.

Вместо функции signal(3C) стандарт POSIX. 1 определяет функцию sigaction(2) , позволяющую установить диспозицию сигналов, узнать ее текущее значение или сделать и то и другое одновременно. Функция имеет следующее определение:

#include

int sigaction (int sig, const struct sigaction *act,

struct sigaction *oact);

Вся необходимая для управлением сигналами информация передается через указатель на структуру sigaction , имеющую следующие поля:

Поле sa_handler определяет действие, которое необходимо предпринять при получении сигналов, и может принимать значения SIG_IGN , SIG_DFL или адреса функции-обработчика. Если значение sa_handler или sa_sigaction не равны NULL , то в поле sa_mask передается набор сигналов, которые будут добавлены к маске сигналов перед вызовом обработчика. Каждый процесс имеет установленную маску сигналов, определяющую сигналы, доставка которых должна быть заблокирована. Если определенный бит маски установлен, соответствующий ему сигнал будет заблокирован. После возврата из функции-обработчика значение маски возвращается к исходному значению. Заметим, что сигнал, для которого установлена функция-обработчик, также будет заблокирован перед ее вызовом. Такой подход гарантирует, что во время обработки, последующее поступление определенных сигналов будет приостановлено до завершения функции. Как правило, UNIX не поддерживает очередей сигналов, и это значит, что блокировка нескольких однотипных сигналов в конечном итоге вызовет доставку лишь одного.

Поле sa_flags определяет флаги, модифицирующие доставку сигнала. Оно может принимать следующие значения:

SA_ONSTACK Если определена функция-обработчик сигнала, и с помощью функции sigaltstack(2) задан альтернативный стек для функции-обработчика, то при обработке сигнала будет использоваться этот стек. Если флаг не установлен, будет использоваться обычный стек процесса.
SA_RESETHAND * Если определена функция-обработчик, то диспозиция сигнала будет изменена на SIG_DFL , и сигнал не будет блокироваться при запуске обработчика. Если флаг не установлен, диспозиция сигнала остается неизменной.
SA_NODEFER * Если определена функция-обработчик, то сигнал блокируется на время обработки только в том случае, если он явно указан в поле sa_mask . Если флаг не установлен, в процессе обработки данный сигнал автоматически блокируется.
SA_RESTART Если определена функция-обработчик, ряд системных вызовов, выполнение которых было прервано полученным сигналом, будут автоматически перезапущены после обработки сигнала. Если флаг не установлен, системный вызов возвратит ошибку EINTR .
SA_SIGINFO * Если диспозиция указывает на перехват сигнала, вызывается функция, адресованная полем sa_sigaction . Если флаг не установлен, вызывается обработчик sa_handler .
SA_NOCLDWAIT * Если указанный аргументом sig сигнал равен SIGCHLD , при завершении потомки не будут переходить в состояние зомби. Если процесс в дальнейшем вызовет функции wait(2) , wait3(2) , waitid(2) или waitpid(2) , их выполнение будет блокировано до завершения работы всех потомков данного процесса.
SA_NOCLDSTOP * Если указанный аргументом sig сигнал равен SIGCHLD , указанный сигнал не будет отправляться процессу при завершении или останове любого из его потомков.

* Данные флаги не определены для UNIX BSD.

В системах UNIX BSD 4.x структура sigaction имеет следующий вид:

struct sigaction {

void (*sa_handler)();

sigset_t sa_mask;

где функция-обработчик определена следующим образом:

void handler(int signo, int code, struct sigcontext *scp);

В первом аргументе signo содержится номер сигнала, code определяет дополнительную информацию о причине поступления сигнала, a scp указывает на контекст процесса.

Для UNIX System V реализована следующая возможность получения более полной информации о сигнале. Если установлен флаг SA_SIGINFO , то при получении сигнала sig будет вызван обработчик, адресованный полем sa_sigaction . Помимо номера сигнала, обычно передаваемого обработчику сигнала, ему будет переданы указатель на структуру siginfo_t , содержащую информацию о причинах получения сигнала, а также указатель на структуру ucontext_t , содержащую контекст процесса.

Структура siginfo_t определена в файле и включает следующие поля:

В поле si_signo хранится номер сигнала. Поле si_code имеет следующий смысл: если его значение меньше или равно нулю, значит сигнал был отправлен прикладным процессом, в этом случае структура siginfo_t содержит также следующие поля:

которые адресуют процесс, пославший сигнал; если значение si_code больше нуля, то оно указывает на причину отправления сигнала. Список возможных значений si_code для некоторых сигналов, соответствующих полю si_signo , приведен в табл. 2.19

Таблица 2.19 . Значения поля si_code структуры siginfo_t для некоторых сигналов

Значение поля si_signo Значение поля si_code Описание
SIGILL Попытка выполнения недопустимой инструкции
ILL_ILLOPC Недопустимый код операции (opcode)
ILL_ILLOPN Недопустимый операнд
ILL_ADR Недопустимый режим адресации
ILL_ILLTRP Недопустимая ловушка (trap)
ILL_PRVOPC Привилегированный код операции
ILL_PRVREG Привилегированный регистр
ILL_COPROC Ошибка сопроцессора
ILL_BADSTK Ошибка внутреннего стека
SIGFPE Особая ситуация операции с плавающей точкой
FPE_INTDIV Целочисленное деление на ноль
FPE_INTOVF Целочисленное переполнение
FPE_FLTDIV Деление на ноль с плавающей точкой
FPE_FLTOVF Переполнение с плавающей точкой
FPE_FLTUND Потеря точности с плавающей точкой (underflow)
FPE_FLTRES Неоднозначный результат операции с плавающей точкой
FPE_FLTINV Недопустимая операция с плавающей точкой
FPE_FLTSUB Индекс вне диапазона
SIGSEGV Нарушение сегментации
SEGV_MAPPER Адрес не отображается на объект
SEGV_ACCERR Недостаточно прав на отображаемый объект
SIGBUS Ошибка адресации
BUS_ADRALN Недопустимое выравнивание адреса
BUS_ADRERR Несуществующий физический адрес
BUS_OBJERR Аппаратная ошибка, связанная с объектом
SIGTRAP Ловушка
TRAP_BRKPT Процессом достигнута точка останова
TRAP_TRACE Ловушка трассирования процесса
SIGCHLD Завершение выполнения дочернего процесса
CLD_EXITED Дочерний процесс завершил выполнение
CLD_KILLED Дочерний процесс был "убит"
CLD_DUMPED Ненормальное завершение дочернего процесса
CLD_TRAPPED Трассируемый дочерний процесс находится в ловушке
CLD_STOPPED Выполнение дочернего процесса было остановлено
CLD_CONTINUED Выполнение остановленного дочернего процесса было продолжено
SIGPOLL Событие на опрашиваемом устройстве
POLL_IN Поступили данные для ввода
POLL_OUT Свободны буферы данных
POLL_MSG Сообщение ожидает ввода
POLL_ERR Ошибка ввода/вывода
POLL_PRI Высокоприоритетные данные ожидают ввода
POLL_HUP Устройство отключено

Уже отмечалось, что при получении сигнала от пользовательского процесса структура siginfo_t содержит дополнительные поля (табл. 2.20).

Таблица 2.20 . Дополнительные поля структуры siginfo_t

Установить маску сигналов или получить текущую маску можно с помощью функции sigprocmask(2) :

#include

int sigprocmask(int how, sigset_t *set, sigset_t *oset);

Маска сигналов изменяется в соответствии с аргументом how , который может принимать следующие значения:

Если указатель set равен NULL , то аргумент how игнорируется. Если аргумент oset не равен NULL , то в набор, адресованный этим аргументом, помещается текущая маска сигналов.

Функция sigpending(2) используется для получения набора заблокированных сигналов, ожидающих доставки:

#include

int sigpending(int how, sigset_t *set, sigset_t *oset);

Список сигналов, ожидающих доставки, возвращается в наборе, адресованном аргументом set .

Системный вызов sigsuspend(2) замещает текущую маску набором, адресованным аргументом set , и приостанавливает выполнение процесса до получения сигналов, диспозиция которых установлена либо на завершение выполнения процесса, либо на вызов функции-обработчика сигнала.

#include

int sigsuspend(const sigset_t *set);

При получении сигнала, завершающего выполнение процесса, возврата из функции sigsuspend(2) не происходит. Если же диспозиция полученного сигнала установлена на вызов функции-обработчика, возврат из sisuspend(2) происходит сразу после завершения обработки сигнала. При этом восстанавливается маска, существовавшая до вызова sigsuspend(2) .

Заметим, что в BSD UNIX вызов signal(3) является упрощенным интерфейсом к более общей функции sigaction(2) , в то время как в ветви System V signal(3) подразумевает использование старой семантики ненадежных сигналов.

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

#include

#include

#include

#include

#include

/* Вариант "надежной" функции signal() */

void (*mysignal(int signo, void (*hndlr)(int)))(int) {

struct sigaction act, oact;

/* Установим маску сигналов */

act.sa_handler = hndlr;

sigemptyset(&act.sa_mask);

act.sa_flags = 0;

if (signo != SIGALRM)

act.sa_flags = SA_RESTART;

/* Установим диспозицию */

if (sigaction(signo, &act, &oact) < 0)

return(oact.sa_handler);

/* Функция-обработчик сигнала */

static void sig_hndlr(int signo) {

/* Эта часть кода нам уже не нужна

mysignal(SIGINT, sig_hndlr);

printf("Получен сигнал SIGINT ");

/* Установим диспозицию */

mysignal(SIGINT, sig_hndlr);

mysignal(SIGUSR2, SIG_IGN);

/* Бесконечный цикл */

Заметим, что при использовании надежных сигналов, не нужно восстанавливать диспозицию в функции-обработчике при получении сигнала.

Из книги Архитектура операционной системы UNIX автора Бах Морис Дж

7.2 СИГНАЛЫ Сигналы сообщают процессам о возникновении асинхронных событий. Посылка сигналов производится процессами - друг другу, с помощью функции kill, - или ядром. В версии V (вторая редакция) системы UNIX существуют 19 различных сигналов, которые можно классифицировать

Из книги Linux-сервер своими руками автора

5.8.2. Сигналы Демон syslogd реагирует на следующие сигналы: SYGTERM, SIGINT, SIGQUIT, SIGHUP, SIGUSR1, SIGCHLD. Реакция демона на сигналы описана в табл. 5.8.Реакция демона на сигналы Таблица 5.8 Сигнал Реакция SIGTERM Завершает работу демона SIGINT, SIGQUIT Завершает работу демона, если выключена отладка

автора Реймонд Эрик Стивен

Из книги Искусство программирования для Unix автора Реймонд Эрик Стивен

7.2.6.2. Сигналы Самый простой и грубый способ сообщения между двумя процессами на одной машине заключается в том, что один из них отправляет другому какой-либо сигнал (signal). Сигналы в операционной системе Unix представляют собой форму программного прерывания. Каждый сигнал

Из книги Инфраструктуры открытых ключей автора Полянская Ольга Юрьевна

Надежные источники времени Сервис защищенного датирования требует наличия одного или нескольких надежных источников времени, то есть способа получения надежного представления текущего времени (синхронного глобальному времени, с высоким уровнем точности) одного или

Из книги Разработка приложений в среде Linux. Второе издание автора Джонсон Майкл К.

12.1.3. Надежные сигналы Реализация BSD для решения проблемы множества сигналов полагается на простое ожидание завершения работы каждого обработчика сигналов в процессе перед доставкой следующего. Это гарантирует то, что каждый сигнал будет рано или поздно обработан, а

Из книги Linux: Полное руководство автора Колисниченко Денис Николаевич

3.3.2. Сигналы Механизм сигналов - это средство, позволяющее сообщать процессам о некоторых событиях в системе, а процессу-получателю - должным образом на эти сообщения реагировать. Послать сигнал может сам процесс (например, при попытке деления на ноль), ядро (при сбое

Из книги QNX/UNIX [Анатомия параллелизма] автора Цилюрик Олег Иванович

27.3.10. Сигналы и сокеты С сокетами связаны три сигнала:? SIGIO - сокет готов к вводу/выводу. Сигнал посылается процессу, который связан с сокетом;? SIGURG - сокет получил экспресс-данные (мы их использовать не будем, поэтому особо останавливаться на них нет смысла);? SIGPIPE - запись

Из книги Программирование для Linux. Профессиональный подход автора Митчелл Марк

3. Сигналы

Из книги Операционная система UNIX автора Робачевский Андрей М.

Из книги Linux и UNIX: программирование в shell. Руководство разработчика. автора Тейнсли Дэвид

3.3. Сигналы Сигналы - это механизм связи между процессами в Linux. Данная тема очень обширна, поэтому здесь мы рассмотрим лишь наиболее важные сигналы и методики управления процессами.Сигнал представляет собой специальное сообщение, посылаемое процессу. Сигналы являются

Из книги Взрыв обучения: Девять правил эффективного виртуального класса автора Мердок Мэттью

Сигналы Сигналы являются способом передачи от одного процесса другому или от ядра операционной системы какому-либо процессу уведомления о возникновении определенного события. Сигналы можно рассматривать как простейшую форму межпроцессного взаимодействия. В то же

Из книги автора

Из книги автора

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

Из книги автора

26.2. Сигналы Сигнал относится к типу сообщений, которые пересылаются из системы для информирования команды или сценария о совершении какого?либо события. Обычно речь идет об ошибках, связанных с функционированием памяти, о проблемах с доступом к информации или об

IPC, inter-process communication ). Фактически, сигнал - это асинхронное уведомление процесса о каком-либо событии. Когда сигнал послан процессу, операционная система прерывает выполнение процесса. Если процесс установил собственный обработчик сигнала , операционная система запускает этот обработчик, передав ему информацию о сигнале. Если процесс не установил обработчик, то выполняется обработчик по умолчанию.

Названия сигналов «SIG…» являются числовыми константами (макроопределениями Си) со значениями, определяемыми в заголовочном файле signal.h . Числовые значения сигналов могут меняться от системы к системе, хотя основная их часть имеет в разных системах одни и те же значения. Утилита kill позволяет задавать сигнал как числом, так и символьным обозначением.

Посылка сигналов

Сигналы посылаются:

  • с терминала, нажатием специальных клавиш или комбинаций (например, нажатие Ctrl-C генерирует SIGINT , Ctrl-\ SIGQUIT , а Ctrl-Z SIGTSTP);
  • ядром системы:
    • при возникновении аппаратных исключений (недопустимых инструкций, нарушениях при обращении в память, системных сбоях и т. п.);
    • ошибочных системных вызовах;
    • для информирования о событиях ввода-вывода;
  • одним процессом другому (или самому себе), с помощью системного вызова kill() , в том числе:

Сигналы не могут быть посланы завершившемуся процессу, находящемуся в состоянии «зомби» .

Обработка сигналов

Обработчик по умолчанию для большинства сигналов завершает выполнение процесса. Для альтернативной обработки всех сигналов, за исключением SIGKILL и SIGSTOP , процесс может назначить свой обработчик или игнорировать их возникновение модификацией своей сигнальной маски . Единственное исключение - процесс с pid 1 (init), который имеет право игнорировать или обрабатывать любые сигналы, включая KILL и STOP.

Безопасность

Процесс (или пользователь из оболочки) с эффективным UID , не равным 0 (UID суперпользователя), может посылать сигналы только процессам с тем же UID.

Классификация сигналов

POSIX определяет 28 сигналов, которые можно классифицировать следующим образом:

Название Код Действие по умолчанию Описание Тип
SIGABRT 6 Завершение с дампом памяти Сигнал посылаемый функцией abort() Управление
SIGALRM 14 Завершение Сигнал истечения времени, заданного alarm() Уведомление
SIGBUS 10 Завершение с дампом памяти Неправильное обращение в физическую память Исключение
SIGCHLD 18 Игнорируется Дочерний процесс завершен или остановлен Уведомление
SIGCONT 25 Продолжить выполнение Продолжить выполнение ранее остановленного процесса Управление
SIGFPE 8 Завершение с дампом памяти Ошибочная арифметическая операция Исключение
SIGHUP 1 Завершение Закрытие терминала Уведомление
SIGILL 4 Завершение с дампом памяти Недопустимая инструкция процессора Исключение
SIGINT 2 Завершение Сигнал прерывания (Ctrl-C) с терминала Управление
SIGKILL 9 Завершение Безусловное завершение Управление
SIGPIPE 13 Завершение Запись в разорванное соединение (пайп, сокет) Уведомление
SIGQUIT 3 Завершение с дампом памяти Сигнал «Quit» с терминала (Ctrl-\) Управление
SIGSEGV 11 Завершение с дампом памяти Нарушение при обращении в память Исключение
SIGSTOP 23 Остановка процесса Остановка выполнения процесса Управление
SIGTERM 15 Завершение Сигнал завершения (сигнал по умолчанию для утилиты kill) Управление
SIGTSTP 20 Остановка процесса Сигнал остановки с терминала (Ctrl-Z). Управление
SIGTTIN 26 Остановка процесса Попытка чтения с терминала фоновым процессом Управление
SIGTTOU 27 Остановка процесса Попытка записи на терминал фоновым процессом Управление
SIGUSR1 16 Завершение Пользовательский сигнал № 1 Пользовательский
SIGUSR2 17 Завершение Пользовательский сигнал № 2 Пользовательский
SIGPOLL 22 Завершение Событие, отслеживаемое poll() Уведомление
SIGPROF 29 Завершение Истечение таймера профилирования Отладка
SIGSYS 12 Завершение с дампом памяти Неправильный системный вызов Исключение
SIGTRAP 5 Завершение с дампом памяти Ловушка трассировки или брейкпоинт Отладка
SIGURG 21 Игнорируется На сокете получены срочные данные Уведомление
SIGVTALRM 28 Завершение Истечение «виртуального таймера» Уведомление
SIGXCPU 30 Завершение с дампом памяти Процесс превысил лимит процессорного времени Исключение
SIGXFSZ 31 Завершение с дампом памяти Процесс превысил допустимый размер файла Исключение

При обработке исключений и отладочных сигналов перед завершением процесс может записать в текущий каталог файл с дампом памяти процесса (англ. core image ), используя который, отладчик может восстановить условия, при которых возникло данное исключение. Иногда (например, для программ, выполняемых от имени суперпользователя) дамп памяти не создаётся из соображений безопасности.

SA_SIGINFO

Обычно обработчик сигнала получает только один аргумент - номер сигнала (это позволяет использовать одну функцию-обработчик для нескольких сигналов). Если при задании обработчика сигнала (функцией sigaction()) указать опцию SA_SIGINFO, то в обработчик будут переданы ещё два аргумента:

  1. указатель на структуру siginfo_t , включающую:
    • битовую маску дополнительных «кодов сигнала», определяющих причину его возникновения;
    • идентификатор процесса (PID), пославшего сигнал;
    • эффективный идентификатор пользователя (UID), от имени которого выполняется процесс (например, утилита kill), пославший сигнал;
    • адрес инструкции, в которой возникло исключение;
    • и т. п.
  2. указатель на «машинный контекст» на момент возникновения сигнала (со «стеком сигнала» - дополнительными данными, которые помещаются в стек при вызове некоторых сигналов-исключений).

Большинство дополнительных кодов специфичны для каждого сигнала. Коды, общие для всех сигналов:

См. также

Напишите отзыв о статье "Сигналы (UNIX)"

Ссылки

  • - Концепция сигналов (IEEE Std 1003.1, 2004 Edition)
  • - Описание структур и констант, связанных с сигналами (IEEE Std 1003.1, 2004 Edition)
  • - «Правила использования сигналов в Unix» (Сообщение в конференции RU.UNIX.PROG)

Отрывок, характеризующий Сигналы (UNIX)

– Целый вечер вам буду петь, – сказала Наташа.
– Волшебница всё со мной сделает! – сказал Денисов и отстегнул саблю. Он вышел из за стульев, крепко взял за руку свою даму, приподнял голову и отставил ногу, ожидая такта. Только на коне и в мазурке не видно было маленького роста Денисова, и он представлялся тем самым молодцом, каким он сам себя чувствовал. Выждав такт, он с боку, победоносно и шутливо, взглянул на свою даму, неожиданно пристукнул одной ногой и, как мячик, упруго отскочил от пола и полетел вдоль по кругу, увлекая за собой свою даму. Он не слышно летел половину залы на одной ноге, и, казалось, не видел стоявших перед ним стульев и прямо несся на них; но вдруг, прищелкнув шпорами и расставив ноги, останавливался на каблуках, стоял так секунду, с грохотом шпор стучал на одном месте ногами, быстро вертелся и, левой ногой подщелкивая правую, опять летел по кругу. Наташа угадывала то, что он намерен был сделать, и, сама не зная как, следила за ним – отдаваясь ему. То он кружил ее, то на правой, то на левой руке, то падая на колена, обводил ее вокруг себя, и опять вскакивал и пускался вперед с такой стремительностью, как будто он намерен был, не переводя духа, перебежать через все комнаты; то вдруг опять останавливался и делал опять новое и неожиданное колено. Когда он, бойко закружив даму перед ее местом, щелкнул шпорой, кланяясь перед ней, Наташа даже не присела ему. Она с недоуменьем уставила на него глаза, улыбаясь, как будто не узнавая его. – Что ж это такое? – проговорила она.
Несмотря на то, что Иогель не признавал эту мазурку настоящей, все были восхищены мастерством Денисова, беспрестанно стали выбирать его, и старики, улыбаясь, стали разговаривать про Польшу и про доброе старое время. Денисов, раскрасневшись от мазурки и отираясь платком, подсел к Наташе и весь бал не отходил от нее.

Два дня после этого, Ростов не видал Долохова у своих и не заставал его дома; на третий день он получил от него записку. «Так как я в доме у вас бывать более не намерен по известным тебе причинам и еду в армию, то нынче вечером я даю моим приятелям прощальную пирушку – приезжай в английскую гостинницу». Ростов в 10 м часу, из театра, где он был вместе с своими и Денисовым, приехал в назначенный день в английскую гостинницу. Его тотчас же провели в лучшее помещение гостинницы, занятое на эту ночь Долоховым. Человек двадцать толпилось около стола, перед которым между двумя свечами сидел Долохов. На столе лежало золото и ассигнации, и Долохов метал банк. После предложения и отказа Сони, Николай еще не видался с ним и испытывал замешательство при мысли о том, как они свидятся.
Светлый холодный взгляд Долохова встретил Ростова еще у двери, как будто он давно ждал его.
– Давно не видались, – сказал он, – спасибо, что приехал. Вот только домечу, и явится Илюшка с хором.
– Я к тебе заезжал, – сказал Ростов, краснея.
Долохов не отвечал ему. – Можешь поставить, – сказал он.
Ростов вспомнил в эту минуту странный разговор, который он имел раз с Долоховым. – «Играть на счастие могут только дураки», сказал тогда Долохов.
– Или ты боишься со мной играть? – сказал теперь Долохов, как будто угадав мысль Ростова, и улыбнулся. Из за улыбки его Ростов увидал в нем то настроение духа, которое было у него во время обеда в клубе и вообще в те времена, когда, как бы соскучившись ежедневной жизнью, Долохов чувствовал необходимость каким нибудь странным, большей частью жестоким, поступком выходить из нее.
Ростову стало неловко; он искал и не находил в уме своем шутки, которая ответила бы на слова Долохова. Но прежде, чем он успел это сделать, Долохов, глядя прямо в лицо Ростову, медленно и с расстановкой, так, что все могли слышать, сказал ему:
– А помнишь, мы говорили с тобой про игру… дурак, кто на счастье хочет играть; играть надо наверное, а я хочу попробовать.
«Попробовать на счастие, или наверное?» подумал Ростов.
– Да и лучше не играй, – прибавил он, и треснув разорванной колодой, прибавил: – Банк, господа!
Придвинув вперед деньги, Долохов приготовился метать. Ростов сел подле него и сначала не играл. Долохов взглядывал на него.
– Что ж не играешь? – сказал Долохов. И странно, Николай почувствовал необходимость взять карту, поставить на нее незначительный куш и начать игру.
– Со мной денег нет, – сказал Ростов.
– Поверю!
Ростов поставил 5 рублей на карту и проиграл, поставил еще и опять проиграл. Долохов убил, т. е. выиграл десять карт сряду у Ростова.
– Господа, – сказал он, прометав несколько времени, – прошу класть деньги на карты, а то я могу спутаться в счетах.
Один из игроков сказал, что, он надеется, ему можно поверить.
– Поверить можно, но боюсь спутаться; прошу класть деньги на карты, – отвечал Долохов. – Ты не стесняйся, мы с тобой сочтемся, – прибавил он Ростову.
Игра продолжалась: лакей, не переставая, разносил шампанское.
Все карты Ростова бились, и на него было написано до 800 т рублей. Он надписал было над одной картой 800 т рублей, но в то время, как ему подавали шампанское, он раздумал и написал опять обыкновенный куш, двадцать рублей.
– Оставь, – сказал Долохов, хотя он, казалось, и не смотрел на Ростова, – скорее отыграешься. Другим даю, а тебе бью. Или ты меня боишься? – повторил он.
Ростов повиновался, оставил написанные 800 и поставил семерку червей с оторванным уголком, которую он поднял с земли. Он хорошо ее после помнил. Он поставил семерку червей, надписав над ней отломанным мелком 800, круглыми, прямыми цифрами; выпил поданный стакан согревшегося шампанского, улыбнулся на слова Долохова, и с замиранием сердца ожидая семерки, стал смотреть на руки Долохова, державшего колоду. Выигрыш или проигрыш этой семерки червей означал многое для Ростова. В Воскресенье на прошлой неделе граф Илья Андреич дал своему сыну 2 000 рублей, и он, никогда не любивший говорить о денежных затруднениях, сказал ему, что деньги эти были последние до мая, и что потому он просил сына быть на этот раз поэкономнее. Николай сказал, что ему и это слишком много, и что он дает честное слово не брать больше денег до весны. Теперь из этих денег оставалось 1 200 рублей. Стало быть, семерка червей означала не только проигрыш 1 600 рублей, но и необходимость изменения данному слову. Он с замиранием сердца смотрел на руки Долохова и думал: «Ну, скорей, дай мне эту карту, и я беру фуражку, уезжаю домой ужинать с Денисовым, Наташей и Соней, и уж верно никогда в руках моих не будет карты». В эту минуту домашняя жизнь его, шуточки с Петей, разговоры с Соней, дуэты с Наташей, пикет с отцом и даже спокойная постель в Поварском доме, с такою силою, ясностью и прелестью представились ему, как будто всё это было давно прошедшее, потерянное и неоцененное счастье. Он не мог допустить, чтобы глупая случайность, заставив семерку лечь прежде на право, чем на лево, могла бы лишить его всего этого вновь понятого, вновь освещенного счастья и повергнуть его в пучину еще неиспытанного и неопределенного несчастия. Это не могло быть, но он всё таки ожидал с замиранием движения рук Долохова. Ширококостые, красноватые руки эти с волосами, видневшимися из под рубашки, положили колоду карт, и взялись за подаваемый стакан и трубку.