我最近正在研究这本名为Advanced Linux Programming的书,我遇到了这个问题:本书说你应该使用sig_atomic_t
变量类型来确保在信号处理函数中设置全局标志或计数器,算术运算(即++
)之间不会发生上下文切换,并将其保存到寄存器中。
我的问题是:如果我们不使用sig_atomic_t
并且仅使用其他类型和上下文切换会发生什么?我的意思是程序将返回并稍后保存它。有人可以给我一个场景,它会使我们的代码不稳定或有错误吗?
答案 0 :(得分:2)
您在所描述的场景中运行的风险(从内存中读取注册,更新注册,写入内存以及在任何这些操作之间发生上下文切换)是您可能会丢失在其他上下文中进行的更新。 / p>
例如:
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
时,不会发生第二个问题,因为保证类型使用原子读/写操作。
答案 1 :(得分:2)
以下是导致不安全行为的示例:
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为零,这是从未想过的。
答案 2 :(得分:1)
比从信号处理程序中编写变量更好的解决方案是保持管道打开并从信号处理程序向管道写入值。这样做的好处是,它可以在没有任何竞争条件的情况下唤醒选择(只要您在管道的读取端选择),并且使您可以在主要信号中执行大部分主要信号处理选择循环,您可以自由使用任何所需的库函数。