linux信号掩码函数中sig_atomic_t的用法
我最近在学习《高级 Linux 编程》一书,遇到了这个问题:这本书说你应该使用 sig_atomic_t 变量类型来确保如果你在信号处理函数中设置全局标志或计数器,算术运算(即++
)和将它们保存到寄存器之间不会发生上下文切换。
我的问题是:如果我们不使用 sig_atomic_t 而是使用另一种类型并且发生上下文切换,会发生什么?我的意思是,程序稍后会返回并保存它。有人能给我一个会使我们的代码不稳定或有错误的场景吗?
I was recently studying the book named Advanced Linux Programming and I ran into this question: The book said you should use sig_atomic_t
variable type to be sure that if you set a global flag or counter in a signal handler function, context switch does not occur between the arithmetic operations(i.e. ++
) and saving those into a register.
My question is: What can happen if we don't use sig_atomic_t
and just use another type and context switch occurs? I mean the program will just return and save it later for example. Can someone give me a scenario which it will make our code unstable or buggy?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
在您描述的场景中(从内存读取寄存器、更新寄存器、写入内存以及在任何这些操作之间发生上下文切换)所面临的风险是您可能会丢失在其他上下文中进行的更新。
例如:
正如您所看到的,来自中断的更新已经丢失。
如果您的类型需要多个操作才能将其写入内存并且中断发生在写入操作的中间,则会出现更大的问题。
例如:
在这里,不知道我最终会得到什么值。
使用
sig_atomic_t
,第二个问题不会发生,因为该类型保证使用原子读/写操作。The risk you run in the scenario you describe (read from memory to register, update register, write to memory and a context switch happens between any of those operations) is that you could lose an update made in the other context.
For example:
As you can see, the update from the interrupt has been lost.
An even bigger problem would occur if your type requires multiple operations to write it to memory and the interrupt occurs in the middle of a write operation.
For example:
Here, there is no telling what value i will end up with.
With
sig_atomic_t
, this second problem can't occur, because the type is guaranteed to use atomic read/write operations.以下是导致不安全行为的示例:
假设采用 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 读为 0,这并不是有意的。
Here is an example which leads to unsafe behaviour:
Assume a 32 Bit architecture. The variable a is in effect stored as 2 32 Bit integers and starts as {0,2^32-1}. First f reads the upper half of a as 0. Then a signal occurs and the execution switches to the signal handler. It increments a from 2^32-1 to 2^32 a's new value is {1,0}. The signal handler completes and the execution of f continues. f reads the lower half of a as 0. Altogether f has read a as zero which was never intended.
比从信号处理程序写入变量更好的解决方案是保持管道打开并从信号处理程序向管道写入值。这样做的好处是它可以唤醒选择(只要您在管道的读取端进行选择)而无需任何竞争条件,并且使您可以在主程序中完成信号的大部分主要处理select 循环,您可以在其中自由使用您想要的任何库函数。
A better solution than writing variables from your signal handler is to keep a pipe open and write a value to the pipe from the signal handler. This has the benefit that it can wakeup a select (as long as you're selecting on the reading end of the pipe) without any race conditions, and makes it possible for you to do most of the main handling of the signal in your main select loop, where you're free to use any library functions you want.