C sig_原子在linux信号掩码函数中的应用

C sig_原子在linux信号掩码函数中的应用,c,linux,signals,async-safe,C,Linux,Signals,Async Safe,我最近在学习《高级Linux编程》这本书时遇到了一个问题:这本书说应该使用sig\u atomic\u t变量类型,以确保如果在信号处理函数中设置全局标志或计数器,则不会在算术运算之间发生上下文切换(即+)并将其保存到寄存器中 我的问题是:如果我们不使用sig_atomic_t而只使用另一种类型,并且发生上下文切换,会发生什么?例如,我的意思是程序将返回并稍后保存。有人能给我一个会使我们的代码不稳定或出现错误的场景吗?在您描述的场景中(从内存读取到寄存器、更新寄存器、写入内存以及在这些操作之间发

我最近在学习《高级Linux编程》这本书时遇到了一个问题:这本书说应该使用
sig\u atomic\u t
变量类型,以确保如果在信号处理函数中设置全局标志或计数器,则不会在算术运算之间发生上下文切换(即
+
)并将其保存到寄存器中


我的问题是:如果我们不使用
sig_atomic_t
而只使用另一种类型,并且发生上下文切换,会发生什么?例如,我的意思是程序将返回并稍后保存。有人能给我一个会使我们的代码不稳定或出现错误的场景吗?

在您描述的场景中(从内存读取到寄存器、更新寄存器、写入内存以及在这些操作之间发生上下文切换)运行的风险是,您可能会丢失在其他上下文中进行的更新

例如:

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
主上下文:
从存储器向寄存器R1读取i(=10)
将5添加到R1
从存储器向寄存器R1读取i(=10)
加10到R1
将R1写入内存中的i(i=20)
将R1写入内存中的i(i=15)
如您所见,来自中断的更新已丢失

如果您的类型需要多个操作将其写入内存,并且中断发生在写操作的中间,则会出现更大的问题。 例如:

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
主上下文:
从存储器读取i(=10)的前半部分到寄存器R1
从存储器读取i(=10)的后半部分到寄存器R2
将5添加到R1/R2对
将R1写入内存中i的前半部分
将i(=?)的前半部分从内存读取到寄存器R1
从内存中读取i(=?)的后半部分到寄存器R2
将10添加到R1/R2对
将R1写入内存中i的前半部分
将R2写入内存中i的后半部分
将R2写入内存中i的后半部分
在这里,没有人知道我最终会有什么价值


使用
sig_atomic\u 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读取为零,这是不可能的。

比从信号处理程序写入变量更好的解决方案是保持管道打开,并从信号处理程序向管道写入一个值。这样做的好处是,它可以在没有任何争用条件的情况下唤醒select(只要您在管道的读取端进行选择),并使您能够在主select循环中完成信号的大部分主要处理,在主select循环中,您可以自由使用任何想要的库函数。

您将问题标记为dsp。您是否希望答案只突出显示DSP上可能出现的问题?问题本身没有提到DSP。谢谢,在这种情况下,我们需要将所有变量声明为原子变量。我的问题是关于在处理另一个信号时何时出现某些信号。不客气。我试图展示一个最常见的场景。