避免异步信号处理产生的数据竞争
15.1 ID_sig_dataRaces
异步信号处理函数的调用会随时打断主程序的流程,当处理函数返回后,主程序在被打断的位置继续执行,这种方式称为“中断(interrupt)”,与执行非并发的线程相似,但没有锁等同步机制,而且信号处理函数本身也可能被中断,所以在信号处理函数中访问共享数据应格外小心。
异步信号处理函数的安全模式:
- 调用“异步信号安全”函数执行清理或结束进程,如 abort、_Exit 等
- 对 volatile sig_atomic_t 等类型的共享对象赋值,主程序周期性地检查共享对象并执行相应动作
- 利用 sigsetjmp、siglongjmp 等函数使流程跳转到主程序中的预定位置
- 通过管道等方式与主程序通信,向管道写入一个字节,主程序监控该管道并执行相应动作
只应选择其中一种方式,且尽量避免访问共享数据,否则对共享数据的错误处理会使程序产生未定义的行为。
示例:
char msg[32];
void handler(int signum) {
strcpy(msg, "SIGINT received"); // Non-compliant
}
int main() {
signal(SIGINT, handler);
strcpy(msg, "No signal received"); // Race condition
....
printf("%s\n", msg);
}
例中信号处理函数和主程序均访问了共享数据,handler 中的 strcpy 可以在 main 中的 strcpy 执行之前或中途执行,造成非预期的结果。
应改为:
volatile sig_atomic_t flag = 0;
void handler(int signum) {
flag = 1; // Compliant
}
int main() {
signal(SIGINT, handler);
....
printf("%s received\n", flag? "SIGINT": "No signal");
}
注意,sig_atomic_t 由实现定义,其最小值和最大值分别为 SIG_ATOMIC_MIN 和 SIG_ATOMIC_MAX。标准规定,当 sig_atomic_t 是有符号整型时,最小值不会大于 -127,最大值不会小于 127,当 sig_atomic_t 是无符号整型时,最小值为 0,最大值不会小于 255。应避免使用超出范围的数值对 sig_atomic_t 型变量赋值。
相关
依据
ISO/IEC 9899:1999 7.14.1.1(5)-undefined
ISO/IEC 9899:2011 7.14.1.1(5)-undefined
ISO/IEC 14882:2003 1.9(9)-undefined
ISO/IEC 14882:2011 1.9(6)-undefined