非異步安全函數(禁止在信號處理中調用)
一、測試
在信號處理函數(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
,異步安全)。 - 工作線程:阻塞在管道讀端,讀取到數據后執行每秒操作。