Linux

sigprocmask() 是否工作不正常?

  • April 21, 2020

(對不起,這篇文章很長,但我想盡可能準確)

我在編寫 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

  • 如果setNULL,則信號遮罩不變(即被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 是否是集合的成員。

注意:創建填充信號集時,glibcsigfillset函式不包括 NPTL 執行緒實現內部使用的兩個實時信號。

系統細節

Linux 發行版:Linux Mint 19.3Cinnamon

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( 0for not a member, 1for 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.has__sigset_t.htypedefed 中的結構/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

非常感謝參與解決此錯誤的 glibc 開發人員!

引用自:https://unix.stackexchange.com/questions/572172