(很抱歉这篇文章很长,但我想尽可能准确)
当我在编写 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 开发人员!