非异步安全函数(禁止在信号处理中调用)
一、测试
在信号处理函数(Signal Handler)中,只有异步信号安全函数(async-signal-safe functions) 可以安全调用。这类函数的特点是:不使用全局状态、不依赖内部锁、不调用其他非安全函数,且能被信号中断后安全恢复。
非异步安全函数(禁止在信号处理中调用)
以下是常见的非异步安全函数分类及示例(覆盖大部分常用函数):
1. 内存分配 / 释放函数
这类函数依赖全局堆管理结构和内部锁,信号处理中调用可能导致堆损坏或死锁。
malloc
、free
、realloc
、calloc
posix_memalign
、aligned_alloc
2. 标准 I/O 函数
标准 I/O 库(stdio.h
)函数使用全局缓冲区和锁,信号中断可能导致缓冲区状态不一致。
- 输入输出:
printf
、fprintf
、sprintf
、snprintf
、vprintf
、vfprintf
、vsprintf
、vsnprintf
- 文件操作:
fopen
、fclose
、fread
、fwrite
、fseek
、fflush
、fgets
、fputs
- 其他:
perror
(内部调用strerror
和fprintf
)、fflush
3. 字符串 / 字符处理函数(依赖静态缓冲区)
这类函数使用静态缓冲区存储结果,信号中断可能导致缓冲区内容被覆盖。
strtok
(使用静态缓冲区存储分割状态)strerror
(部分实现使用静态缓冲区存储错误信息)ctime
、asctime
(使用静态缓冲区存储时间字符串)getpwuid
、getpwnam
(部分实现使用静态缓冲区存储用户信息)getgrgid
、getgrnam
(部分实现使用静态缓冲区存储组信息)
4. 线程 / 同步函数
线程相关函数依赖全局锁或线程私有数据,信号处理中调用可能导致死锁。
- 互斥锁:
pthread_mutex_lock
、pthread_mutex_unlock
、pthread_mutex_trylock
- 条件变量:
pthread_cond_wait
、pthread_cond_signal
、pthread_cond_broadcast
- 线程管理:
pthread_create
、pthread_join
、pthread_cancel
- 线程私有数据:
pthread_getspecific
、pthread_setspecific
5. 环境变量 / 全局状态函数
这类函数访问或修改全局状态(如环境变量、locale),信号中断可能导致状态不一致。
- 环境变量:
getenv
、setenv
、unsetenv
、putenv
- 区域设置:
setlocale
- 进程信息:
getuid
、getgid
(部分实现可能安全,但建议避免)、getpid
(通常安全,但 POSIX 未明确)
6. 其他非安全函数
- 时间函数:
localtime
、gmtime
(使用静态缓冲区存储时间结构) - 信号相关:
signal
(非可重入,建议用sigaction
替代) - 系统调用包装:
system
(内部创建子进程并调用 shell,依赖全局状态)
为什么这些函数不安全?
非异步安全函数通常存在以下问题:
- 使用全局锁 :如
malloc
、printf
内部有全局锁,若信号处理函数在主程序持有锁时调用,会导致死锁。 - 依赖静态缓冲区 :如
strtok
、ctime
,信号中断可能导致缓冲区数据被覆盖,主程序恢复后读取错误数据。 - 修改全局状态 :如
setenv
、setlocale
,信号处理中修改全局状态可能导致主程序逻辑混乱。
安全替代方案
信号处理函数中如需完成复杂操作,应仅通过异步安全函数 做最小化处理(如设置volatile sig_atomic_t
标志),再由主程序轮询标志并执行非安全操作。
常见的异步安全函数 包括:write
、_exit
、sigprocmask
、pthread_sigmask
、getpid
、getppid
等(完整列表可参考 POSIX 标准man 7 signal-safety
)。
总结:信号处理函数中严禁调用上述非异步安全函数,核心原则是 "仅执行最小化、无状态操作",避免依赖全局资源或锁机制。
举例子
在定时器每秒触发信号的场景下,安全通知线程执行操作的核心是:避免在信号处理函数中直接调用非异步安全接口,而是通过 "信号处理函数→安全通信机制→线程" 的间接方式传递通知。
利用管道(pipe
)作为信号和线程之间的安全通信桥梁(write
是异步安全函数),线程通过监听管道数据触发操作。
实现步骤:
- 创建管道:用于信号处理函数和工作线程的通信。
- 信号处理函数 :收到信号后,向管道写 1 字节数据(仅调用
write
,异步安全)。 - 工作线程:阻塞在管道读端,读取到数据后执行每秒操作。