(很抱歉這篇文章很長,但我想盡可能準確)
我在編寫 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給定的訊號集空的,所有訊號均從集合中排除。int sigfillset(sigset_t *set);
:初始化設定為滿的,包括所有信號。int sigismember(const sigset_t *set, int signum);
:測試signum是否為集合的成員。
筆記: 建立填充訊號集時,glibcsigfillset
函數不包含二NPTL 線程實現內部使用的即時訊號。
系統細節
Linux 發行版:Linux Mint 19.3
Cinnamon
Glibc 版本:(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
對於非成員,1
對於成員)。中的巨集定義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(除了 2;參見筆記在背景部分),最後sigprocmask
用於將當前訊號遮罩儲存到同一變數。在對訊號集進行所有操作後,print_set_bin
使用函數列印哪些訊號屬於該集以及該集是否為空(使用sigisemptyset()
)。
問題似乎是對 的最後一次調用print_set_bin
,其中沒有找到屬於該集合的信號,但該sigisemptyset
函數將集合表徵為非空。這讓我思考是否sigset_t
擁有超過 64 位元且至少其中一個非零。
研究
追蹤 中包含的頭文件signal.h
,我發現這是as和edsigset_t
中定義的結構體:/usr/include/x86_64-linux-gnu/bits/types/__sigset_t.h
__sigset_t.h
typedef
/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,除了 2;參見筆記在背景部分)18446744073709551615
==0b1111111111111111111111111111111111111111111111111111111111111111
(64位元設定為1)
輸出解釋和問題
正如您所看到的,sigemptyset()
並sigfillset()
操作該集合的所有 1024 位元。呼叫sigemptyset()
而不是sigfillset()
呼叫之前sigprocmask()
顯示sigprocmask()
僅操作前 64 位(一個unsigned long int
),其餘 1024-64=960 位保持不變!等待已久的問題來了:這不是一個錯誤嗎?不應該sigprocmask()
寫入整個結構資料嗎?
答案1
是的,sigprocmask()
工作不正常!
2020 年 3 月 11 日,我在 glibc 錯誤追蹤器上填寫了一份新的錯誤報告。幾分鐘前,從 glibc 版本開始,錯誤狀態已變更為「已解決/已修復」2.32
。
- 錯誤報告:https://sourceware.org/bugzilla/show_bug.cgi?id=25657
- 錯誤修復(提交):https://sourceware.org/git/?p=glibc.git;a=commit;h=566e10aa7292bacd74d229ca6f2cd9e8c8ba8748
非常感謝參與解決此錯誤的 glibc 開發人員!