linux信号掩码函数中sig_atomic_t的用法

发布于 2024-09-26 11:49:16 字数 238 浏览 9 评论 0原文

我最近在学习《高级 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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

醉生梦死 2024-10-03 11:49:16

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

例如:

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,第二个问题不会发生,因为该类型保证使用原子读/写操作。

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:

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)

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:

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

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.

感悟人生的甜 2024-10-03 11:49:16

以下是导致不安全行为的示例:

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 读为 0,这并不是有意的。

Here is an example which leads to unsafe behaviour:

int64_t a = 2^32-1;

void some_signal_handler()
{
   ++a;
}

void f()
{
  if( a == 0 )
    printf("a is zero");
}

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.

素手挽清风 2024-10-03 11:49:16

比从信号处理程序写入变量更好的解决方案是保持管道打开并从信号处理程序向管道写入值。这样做的好处是它可以唤醒选择(只要您在管道的读取端进行选择)而无需任何竞争条件,并且使您可以在主程序中完成信号的大部分主要处理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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文