Почему sig_atomic_t можно использовать в повторно входимой функции

Я давно занимаюсь разработкой C ++ для Linux. И когда я разрабатываю некий независимый модуль, обрабатывающий очередь сообщений / задач, я всегда обрабатываю сигнал SIGINT, чтобы избежать потери сообщения / задачи. Вот пример моего кода:

volatile sig_atomic_t sig = 0;

void sig_handler(int signal)
{
    sig = 1;
}

int main()
{
    signal(SIGINT,sig_handler);

    msg_queue = init_msg_queue();
    init_receiving_msg_thread();    // start a thread to receive msgs and push them into msg_queue
    while(!sig) {
        process_msg(msg_queue.top());    // process the first msg in the queue
        msg_queue.pop();    // remove the first msg
    }
    stop_receiving_msg_thread();
    process_all_msgs(msg_queue);
    return 0;
}

Что ж, этот фрагмент кода прост: если сигнал SIGINT захвачен, прекратите прием сообщений, обработайте все сообщения, оставшиеся в очереди, и вернитесь. В противном случае код останется бесконечно долго.

Я думал, что sig_atomic_t был какой-то черной магией. Поскольку, как я понимаю, функция sig_handler должна быть реентерабельной функцией, что означает, что она не может содержать никаких статических или глобальных непостоянных данных: Что такое реентерабельная функция?

Поэтому я всегда думал, что sig_atomic_t - это какая-то хитрость, а не глобальная переменная.

Но сегодня я прочитал эту ссылку: Как на самом деле работает sig_atomic_t?, который сказал мне, что sig_atomic_t - не что иное, как просто typedef, например int. Кажется, что sig_atomic_t sig - это просто глобальная переменная.

Теперь я запуталась.

Правильно ли использовался мой код sig_atomic_t? Если нет, не могли бы вы показать мне правильный пример? Если мой код правильный, что я неправильно понял? sig_atomic_t это не глобальная переменная? Или глобальная переменная может использоваться в повторно входимой функции? Или функция sig_handler может быть нереентерабельной функцией?


person Yves    schedule 06.01.2021    source источник
comment
Я просто хочу сказать, что это очень хороший и хорошо написанный вопрос.   -  person StealthBadger747    schedule 06.01.2021


Ответы (1)


Вы правильно используете volatile sig_atomic_t.

На странице руководства для signal (3p) говорится следующее:

   "the behavior is undefined if the signal handler refers to any
   object other than errno with static storage duration other than
   by assigning a value to an object declared as volatile
   sig_atomic_t," ...

Так почему же volatile sig_atomic_t имеет эту возможность, в то время как другие переменные небезопасны?

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

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

person StealthBadger747    schedule 06.01.2021
comment
Спасибо. Это помогает. Теперь я объясняю эту проблему следующим образом: int может быть гарантированно прочитан и записан за один раз. Но другие типы, такие как long, CustomStruct, не могут. В этом случае люди говорят: не используйте никакую другую глобальную переменную, кроме sig_atomic_t, в какой-либо функции повторного входа, чтобы убедиться, что все будет в порядке, разработчику не нужно быть очень осторожным при использовании переменной в функции повторного входа с помощью sig_atomic_t. Может быть, когда-нибудь int больше не сможет быть гарантированно прочитано и записано за один раз, тогда sig_atomic_t не будет таким же с _8 _... - person Yves; 06.01.2021
comment
Я только что нашел исходный код о sig_atomic_t: code.woboq .org / userspace / glibc / signal / bits / types /, мы можем дважды щелкнуть __sig_atomic_t и получить его определение: typedef int __sig_atomic_t;. - person Yves; 06.01.2021
comment
Конечно, как вы сказали, it is guaranteed to be read and written in one go не означает потокобезопасность. Я могу это понять. - person Yves; 06.01.2021
comment
Что касается вашего первого комментария. Я согласен с твоими рассуждениями. Но, вероятно, есть возможности, где на определенном процессоре, например, может быть, на 8-битном процессоре, где int будет слишком большим для обработки с помощью одной инструкции. Таким образом, использование этого typedef дает понять, что должно произойти с этой переменной. Также я бы сказал, что это просто хороший стилистический выбор для использования sig_atomic_t, потому что он дает понять, что это безопасное использование переменной. - person StealthBadger747; 06.01.2021
comment
Я также должен упомянуть, что определение, на которое вы ссылаетесь, гарантировано только в glibc. Хотя я уверен, что большинство других реализаций libc будут иметь такое же определение. Однако я не уверен, является ли это стандартом C или проблемой POSIX или нет, что означает, что он должен быть одинаковым везде. - person StealthBadger747; 06.01.2021
comment
Точно. Я согласен. - person Yves; 06.01.2021