开发者

The usage of sig_atomic_t in linux signal mask function

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?


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.


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.


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.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜