为什么可以在重入函数中使用 sig_atomic_t

问题描述

我长期以来一直在 Linux 上开发 C++。当我开发一些处理消息/任务队列的独立模块时,我总是处理 SIGINT 信号以避免消息/任务丢失。这是我的代码示例:

volatile sig_atomic_t sig = 0;

void sig_handler(int signal)
{
    sig = 1;
}

int main()
{
    signal(SIGINT,sig_handler);

    msg_queue = init_msg_queue();
    init_receiving_msg_thread();    // start a thread to receive msgs and push them into msg_queue
    while(!sig) {
        process_msg(msg_queue.top());    // process the first msg in the queue
        msg_queue.pop();    // remove the first msg
    }
    stop_receiving_msg_thread();
    process_all_msgs(msg_queue);
    return 0;
}

嗯,这段代码很简单:如果信号 SIGINT 被捕获,停止接收消息,处理队列中剩余的所有消息并返回。否则,代码会一直停留在无限while中。

我认为 sig_atomic_t 是某种黑魔法。因为根据我的理解,函数 sig_handler 必须是可重入函数,这意味着它不能保存任何静态或全局非常量数据:What exactly is a reentrant function?

所以我一直认为 sig_atomic_t 是一些棘手的东西,而不是一个全局变量

但是今天我读到了这个链接How does sig_atomic_t actually work?,它告诉我sig_atomic_t 只不过是一个 typedef,比如一个 int。所以看起来 sig_atomic_t sig 只是一个全局变量

现在我很困惑。

我上面的代码是否正确使用了 sig_atomic_t?如果没有,你能告诉我一个正确的例子吗?如果我的代码是正确的,我误解了什么? sig_atomic_t 不是全局变量吗?或者全局变量可以用在可重入函数中?或者函数sig_handler可以是不可重入函数

解决方法

您对 volatile sig_atomic_t 的使用是正确的。

signal(3p) 的联机帮助页说明如下:

   "the behavior is undefined if the signal handler refers to any
   object other than errno with static storage duration other than
   by assigning a value to an object declared as volatile
   sig_atomic_t," ...

那么为什么 volatile sig_atomic_t 具有这种能力而其他变量不安全?

link you provided 实际上回答了这个问题。 “保证一口气读完。”但详细地说,sig_atomic_t 实际上不是原子类型,但它本质上就像一个用于信号中断目的的原子类型。但它通常不适合多线程用途,因此请勿将其用于线程间通信。

真正的原子类型实际上是由 CPU 专门处理的,它会确保只有一个内核 (cpu) 可以执行整个操作(某些操作需要多条指令才能完成),然后另一个 CPU(或进程)才能对其进行操作数据。另一方面,sig_atomic_t 不提供硬件保证,而是确保对其执行的任何操作(读/写)都将通过一条指令完成,因此该函数的可重入者能够中断该值的完成消除了操纵。