Использование 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 как ноль, который никогда не был задуман.

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

Другие вопросы по тегам