Ядро Linux в комментариях

       

Send_sig_info


Функция send_sig_info является несомненно основополагающей, так сказать «рабочей лошадкой», для множества функций, рассмотренных до сего момента. Эти функции каким-то образом отыскивали необходимый процесс (или процессы), а всю реальную работу перекладывали на плечи send_sig_info. Сейчас мы рассмотрим, что же здесь на самом деле происходит. send_sig_info отправляет сигнал sig, используя дополнительную информацию, на которую указывает info (кстати, может быть и NULL), процессу, заданному указателем t (предполагается, что t никогда не может быть NULL; об этом должны позаботиться перед вызовом).

Проверка значения sig на предмет попадания в диапазон допустимых значений. Следует заметить, что существующая проверка:

sig > _NSIG

отличается от той, что можно было ожидать:

sig >= _NSIG

Все дело в том, что нумерация сигналов начинается с 1, а не с 0. Следовательно, допустимое количество сигналов _NSIG может рассматриваться и как допустимый номер сигнала.

Еще одна разумная проверка готовности. Главная идея заключается в том, чтобы убедиться в законности отправки сигналов. Ядро имеет право отправлять сигналы любому процессу, в то время как пользователи, отличные от привилегированного (root), не могут посылать сигналы процессам, принадлежащим другим пользователям, за исключением покрытого мраком случая с привлечением SIGCONT. В общем, длинное условие в if означает следующее:

  • (Строка ). Если дополнительная информация не задана или, если задана, но сигнал поступает от пользователя, а не от ядра, и...
  • (Строка ). ...сигнал — не SIGCONT или он SIGCONT, но отправляется другому процессу в том же сеансе и...
  • (Строки и ). ...актуальный идентификатор (ID) пользователя для отправителя — ни сохраненный ID пользователя для процесса-адресата, ни текущий ID пользователя для процесса-адресата, и...
  • (Строки и ). ...текущий ID пользователя для отправителя — ни сохраненный ID пользователя, ни текущий ID пользователя для процесса-адресата, и ...

  • (Строка ). ... пользователь не имеет полномочий послать сигнал (например, поскольку получатель — это root). В этом случае сигнал не посылается и происходит обход кода отправки сигналов.


  • Из рассмотренного выше условия можно сделать два заключения. Во-первых, если info равен 1 при приведении к unsigned long, то это не настоящий указатель на struct siginfo. Напротив, это специальное значение, указывающее, что сигнал поступает из ядра без дополнительной информации. Ядро никогда не распределяет память под собственные нужды в младшей странице (см. ), поэтому в качестве такого специального значения годится любой адрес, не превышающий 4096 и не равный 0, т.е. NULL.

    Во-вторых, во многих местах условия место более общей операции логического неравенства (!=) занимает поразрядная операция XOR (^). В данном случае значение обеих операций одно и то же, поскольку если хотя бы один разряд в сравниваемых целочисленных значениях будет отличаться, тогда в результате выполнения XOR появится хотя бы один разряд, равный 1; это означает, что результат ненулевой и, следовательно, логически истинен. Причина применения XOR, возможно, связана с тем, что старые версии gcc для ^ генерировали более эффективный код, нежели для !=, хотя сейчас это не наблюдается.



    Игнорирование сигнала 0 и отказ в отправлении сигнала мертвому процессу (т.е. процессу, который уже завершен, но пока еще не удален из структур данных системы; более подробную информацию о процессах можно найти в ).

    Перед отправкой некоторых сигналов должна производится небольшая дополнительная работа. Именно в этом switch она и делается.

    Если посылается SIGKILL или SIGCONT, send_sig_info пробуждает процесс (т.е. позволяет ему выполняться вновь, если он пребывал в состоянии останова).

    Установка кода завершения для процесса в 0; если процесс был остановлен сигналом SIGSTOP, поле кода завершения используется для взаимодействия сигнала останова с родительским процессом.

    Отмена всех ожидающих SIGSTOP (останов по запросу отладчика), SIGTSTP (останов по запросу от клавиатуры, например после нажатия Ctrl+Z), SIGTTIN (фоновый процесс пытается читать из TTY) и SIGTTOU (фоновый процесс пытается записывать в TTY); сложились условия, которые остановили процесс и возможной реакцией на станов стал SIGCONT или SIGKILL.



    Обращение к recalc_sigpending (строка ) для выяснения, остались ли после удаления еще какие-либо ожидающие сигналы.

    В предыдущем случае по прибытию SIGCONT или SIGKILL отменялись четыре сигнала. Частично ради симметрии, если поступает один из этих четырех сигналов, отменяются все ожидающие SIGCONT. Однако, SIGKILL не отменяется, поскольку SIGKILL принципиально не может ни блокироваться, ни отменяться.

    Если процесс-получатель желает, и это разрешено, проигнорировать пришедший сигнал, сигнал игнорируется.

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

    А вот сигналы реального времени — очередизуются, но с определенными ограничениями. Наиболее важное ограничение — это настраиваемый верхний предел количества сигналов, которые могут присутствовать в очереди одновременно; это max_queued_signals, определяемый в строке и изменяемый при помощи характеристики sysctl. Если имеется место для помещения в очередь дополнительных сигналов, распределяется структура struct signal_queue.

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

    Если элемент очереди распределен, send_sig_info должна записать в него информацию о сигнале.

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

    Заполнение поля info элемента очереди на основе аргумента info, передаваемого в send_sig_info.



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

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

    В нормальном случае send_sig_info получает реальную siginfo_t и может преспокойно копировать ее в элемент очереди.

    Не распределен ни один элемент очереди — либо kmem_cache_alloc возвратила NULL в строке по причине нехватки памяти в системе, либо send_sig_info даже не пыталась распределить элемент, поскольку максимальное количество сигналов в очереди уже достигнуто. Так или иначе, но функция send_sig_info выполняет те же вещи: вне зависимости от того, отправлен ли сигнал ядром, либо сигнальными функциями старого стиля (например, kill), send_sig_info возвращает код ошибки EAGAIN. Этот код ошибки уведомляет отправителя, что в данный момент сигнал не может быть помещен в очередь, однако отправитель может попытаться послать тот же сигнал позже. В противном случае send_sig_info доставляет сигнал, не помещая его в очередь.

    В конечном итоге, send_sig_info фактически готова доставить сигнал. Первоначально сигнал заносится в набор ожидающих сигналов для данного процесса. Следует заметить, что упомянутое занесение будет иметь место даже в случае, если сигнал заблокирован, — немного странно. Однако, для такого поведения есть все основания: ядро поддерживает функцию sys_sigpending (строка ), которая дает возможность процессам запрашивать, какие сигналы доставлялись заблокированными.

    Если сигнал не является заблокированным, процесс будет уведомлен о его доставке. Соответственно, устанавливается его флаг sigpending.

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


    Содержание раздела