Использование sig_atomic_t в функции маски сигнала linux
Недавно я изучал книгу под названием Advanced Linux Programming и столкнулся с вопросом: в книге сказано, что вы должны использовать sig_atomic_t
тип переменной, чтобы быть уверенным, что если вы установите глобальный флаг или счетчик в функции обработчика сигнала, переключение контекста не произойдет между арифметическими операциями (т.е. ++
) и сохранить их в регистр.
Мой вопрос: что может произойти, если мы не используем sig_atomic_t
и просто использовать другой тип и происходит переключение контекста? Я имею в виду, что программа просто вернется и сохранит ее, например, позже. Может кто-нибудь дать мне сценарий, который сделает наш код нестабильным или глючным?
3 ответа
Риск, который вы запускаете в сценарии, который вы описываете (чтение из памяти для регистрации, обновление регистра, запись в память и переключение контекста между этими операциями), заключается в том, что вы можете потерять обновление, выполненное в другом контексте.
Например:
main context:
read i (=10) from memory to register R1
add 5 to R1
<interrupt. Switch to interrupt context>
read i (=10) from memory to register R1
add 10 to R1
write R1 to i in memory (i = 20)
<end of interrupt. Back to main context>
write R1 to i in memory (i = 15)
Как видите, обновление от прерывания было потеряно.
Еще более серьезная проблема может возникнуть, если вашему типу требуется несколько операций для записи его в память, а прерывание происходит в середине операции записи.
Например:
main context:
read first half of i (=10) from memory to register R1
read second half of i (=10) from memory to register R2
add 5 to R1/R2 pair
write R1 to first half of i in memory
<interrupt. Switch to interrupt context>
read first half of i (= ??) from memory to register R1
read second half of i (= ??) from memory to register R2
add 10 to R1/R2 pair
write R1 to first half of i in memory
write R2 to second half of i in memory
<end of interrupt. Back to main context>
write R2 to second half of i in memory
Здесь невозможно сказать, какой ценностью я буду в итоге.
С sig_atomic_t
эта вторая проблема не может возникнуть, потому что тип гарантированно использует атомарные операции чтения / записи.
Вот пример, который ведет к небезопасному поведению:
int64_t a = 2^32-1;
void some_signal_handler()
{
++a;
}
void f()
{
if( a == 0 )
printf("a is zero");
}
Предположим, 32-битная архитектура. Переменная a в действительности сохраняется как 2 32-битные целые числа и начинается как {0,2^32-1}. Сначала f читает верхнюю половину a как 0. Затем происходит сигнал, и выполнение переключается на обработчик сигнала. Увеличивает a с 2 ^ 32-1 до 2^32 a, новое значение равно {1,0}. Обработчик сигнала завершается, и выполнение f продолжается. f читает нижнюю половину a как 0. В целом f читает a как ноль, который никогда не был задуман.
Лучшее решение, чем запись переменных из вашего обработчика сигнала - это оставить канал открытым и записать значение в канал из обработчика сигнала. Это имеет то преимущество, что он может активировать выбор (если вы выбираете на конце чтения канала) без каких-либо условий гонки, и позволяет вам выполнять большую часть основной обработки сигнала в вашем основном Выберите цикл, где вы можете использовать любые функции библиотеки, которые вы хотите.