sigprocmask() 是否工作不正常?
(對不起,這篇文章很長,但我想盡可能準確)
我在編寫 C 程序時嘗試列印主執行緒的信號遮罩,但遇到了關於該
sigprocmask
函式如何工作的奇怪問題。背景[來源:手冊頁
sigprocmask(2)
]該
sigprocmask
函式用於獲取和/或更改呼叫執行緒的信號遮罩。/* Prototype for the glibc wrapper function */ int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
如果
oldset
是非NULL
,則信號遮罩的先前值儲存在 中oldset
。如果
set
是NULL
,則信號遮罩不變(即被how
忽略),但仍會返回信號遮罩的目前值oldset
(如果不是NULL
)。中描述了一組用於修改和檢查類型變數
sigset_t
(“信號集”)的函式sigsetops(3)
。例如:
int sigemptyset(sigset_t *set);
: 將 set 給定的信號集初始化為empty,所有信號都從該集中排除。int sigfillset(sigset_t *set);
: 初始化設置為full,包括所有信號。int sigismember(const sigset_t *set, int signum);
: 測試 signum 是否是集合的成員。注意:創建填充信號集時,glibc
sigfillset
函式不包括 NPTL 執行緒實現內部使用的兩個實時信號。系統細節
Linux 發行版:Linux Mint
19.3
CinnamonGlibc 版本:(
2.27
預設)還驗證了 Glibc 版本:
2.31.9
輸出
uname -a
:
Linux 5.0.0-32-generic #34~18.04.2-Ubuntu SMP Thu Oct 10 10:36:02 UTC 2019 x86_64 x86_64 x86_64 GNU/Linux
問題複製
讓我擔心可能會出錯的程序如下:
#define _GNU_SOURCE #include <stdio.h> #include <signal.h> #include <errno.h> #include <string.h> #define BUFFER_SIZE 32 #define OUTOFBOUNDS #undef OUTOFBOUNDS void print_set_bin(sigset_t *setp); int main(void) { sigset_t set; printf("NSIG = %d\n\n", NSIG); printf("Empty set:\n"); if (sigemptyset(&set)) { perror("sigemptyset"); return -1; } print_set_bin(&set); printf("Filled set:\n"); if (sigfillset(&set)) { perror("sigfillset"); return -1; } print_set_bin(&set); printf("After sigprocmask():\n"); if (sigprocmask(SIG_BLOCK, NULL, &set)) { perror("sigprocmask"); return -1; } print_set_bin(&set); // Why non-empty? return 0; } void print_set_bin(sigset_t *setp) { int sig, res; char buff[BUFFER_SIZE]; if (!setp) { fprintf(stderr, "print_set_bin(): NULL parameter\n"); return; } #ifdef OUTOFBOUNDS for (sig = 0; sig <= NSIG; sig++) #else for (sig = 1; sig < NSIG; sig++) #endif { res = sigismember(setp, sig); if (res == -1) { snprintf(buff, BUFFER_SIZE, "sigisimember [%d]", sig); perror(buff); } else printf("%d", res); } printf(" [%s]\n\n", sigisemptyset(setp) ? "Empty" : "Non-empty"); }
函式為所有信號
print_set_bin
列印sigismember
(0
for not a member,1
for a member) 的輸出。中的宏定義NSIG
(= 65)signal.h
是最大信號數加一 (1),如 中所述/usr/include/x86_64-linux-gnu/bits/signum-generic.h
。在同一個文件中,還提到這個最大的信號數包括實時信號(數範圍$$ 32, 64 $$) 並且該信號編號零 (0) 保留用於測試目的。 結果,我在上面發布的程式碼測試了屬於範圍內的信號編號
$$ 1, 64 $$. 下面是程序的輸出:
NSIG = 65 Empty set: 0000000000000000000000000000000000000000000000000000000000000000 [Empty] Filled set: 1111111111111111111111111111111001111111111111111111111111111111 [Non-empty] After sigprocmask(): 0000000000000000000000000000000000000000000000000000000000000000 [Non-empty]
輸出解釋
在這個程序中,操作
set
類型sigset_t
的變數。首先,函式sigemptyset
用於將所有位設置為零,然後函式sigfillset
用於將所有位設置為 1(兩位除外;請參閱背景部分中的註釋),最後用於將目前信號遮罩儲存到同一變數中。在信號集上發生所有操作之後,函式用於列印哪些信號屬於該集以及該集是否為空(使用)。sigprocmask``print_set_bin``sigisemptyset()
問題似乎是對 的最後一次呼叫
print_set_bin
,其中沒有找到屬於該集合的信號,但該sigisemptyset
函式將集合表徵為非空。這讓我想到是否sigset_t
擁有超過 64 位,並且其中至少有一個是非零的。研究
跟踪包含的標頭檔
signal.h
,我發現這sigset_t
是一個定義在/usr/include/x86_64-linux-gnu/bits/types/__sigset_t.h
as__sigset_t.h
和typedef
ed 中的結構/usr/include/x86_64-linux-gnu/bits/types/sigset_t.h
:#define _SIGSET_NWORDS (1024 / (8 * sizeof (unsigned long int))) typedef struct { unsigned long int __val[_SIGSET_NWORDS]; } __sigset_t;
起初我認為 1024 位太多了,但後來我遇到了另一個 unix.stackexchange.com 問題的答案。然後我決定使用
sigset_t
結構的實現細節來列印所有 1024 位。我在下面的程式碼中做到了這一點,方法是將函式替換為列印分配給數組的所有(= 16)的print_set_bin
函式。print_set_word``_SIGSET_NWORDS``unsigned long int``__val
void print_set_word(sigset_t *setp) { int i; if (!setp) { fprintf(stderr, "print_set_word(): NULL parameter\n"); return; } for (i = 0; i < 16; i++) { printf("%lu\n", setp->__val[i]); } printf("[%s]\n\n", sigisemptyset(setp) ? "Empty" : "Non-empty"); }
程序輸出:
Empty set: 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 [Empty] Filled set: 18446744067267100671 18446744073709551615 18446744073709551615 18446744073709551615 18446744073709551615 18446744073709551615 18446744073709551615 18446744073709551615 18446744073709551615 18446744073709551615 18446744073709551615 18446744073709551615 18446744073709551615 18446744073709551615 18446744073709551615 18446744073709551615 [Non-empty] After sigprocmask(): 0 18446744073709551615 18446744073709551615 18446744073709551615 18446744073709551615 18446744073709551615 18446744073709551615 18446744073709551615 18446744073709551615 18446744073709551615 18446744073709551615 18446744073709551615 18446744073709551615 18446744073709551615 18446744073709551615 18446744073709551615 [Non-empty]
備註:
18446744067267100671
==0b1111111111111111111111111111111001111111111111111111111111111111
(64 位設置為 1,除了兩個;參見背景部分中的註釋)18446744073709551615
==0b1111111111111111111111111111111111111111111111111111111111111111
(64 位設置為 1)輸出解釋和問題
如您所見,
sigemptyset()
並sigfillset()
操作集合的所有 1024 位。呼叫sigemptyset()
而不是sigfillset()
在呼叫之前sigprocmask()
顯示sigprocmask()
僅操作前 64 位(一個unsigned long int
),其餘 1024-64=960 位保持不變!期待已久的問題來了:這不是一個錯誤嗎?不應該sigprocmask()
寫入整個結構數據嗎?
是的,
sigprocmask()
工作不正常!2020 年 3 月 11 日,我在 glibc 錯誤跟踪器上填寫了一份新的錯誤報告。幾分鐘前,從 glibc 版本開始,錯誤狀態已更改為 Resolved/Fixed
2.32
。
- 錯誤報告:https ://sourceware.org/bugzilla/show_bug.cgi?id=25657
- 錯誤修復(送出):https ://sourceware.org/git/?p=glibc.git;a=commit;h=566e10aa7292bacd74d229ca6f2cd9e8c8ba8748
非常感謝參與解決此錯誤的 glibc 開發人員!