OS62.【Linux】与信号集相关的操作函数
目录
6.回答问题: 0号信号虽然没有,但具体需不需要在sigset_t位图中占位?
1.面试题回顾
参见OS62.【Linux】面试题: 信号集是什么? 在Linux中是如何实现的?文章
2.复习与信号相关的三张表
参见OS61.【Linux】理解信号: 信号的发送和保存文章
3.与信号集相关的操作函数
与信号相关的三张表分别是handler表、block表、pending表,其中handler表、block表的底层含有sigset_t位图
该位图对于每种信号用一个比特位表示“有效”或“无效”状态,至于这个类型内部如何存储这些比特位则依赖于系统实现,从使用者的角度是不必关心的
使用者只能调用系统提供的函数来操作sigset_ t实例化后的对象,而不应该手动操作内部数据(因为不同的类Unix系统的底层实现不一样,例如对sigset赋值是没有意义的)
sigemptyset
int sigemptyset(sigset_t *set);
![]()
作用: 顾名思义,将信号集的位图设为全0
sigfillset
int sigfillset(sigset_t *set);
![]()
作用: 顾名思义,将信号集的位图设为全1
★特别提醒
在使用sigset_ t类型的变量之前,一定要调用sigemptyset或sigfillset做初始化,使信号集处于确定的
状态
sigismember
int sigismember(const sigset_t *set, int signum);
![]()
作用: 判断某个信号是否在信号集中
sigprocmask

int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
作用: 读取或更改进程的block表,即信号屏蔽字,又称阻塞信号集
注: sigprocmask的proc是process的简称
参数how: 从下面的3个宏选一个,表示处理方法:

返回值: 若成功则为0,若出错则为-1
参数set: 输入型参数,是让sigprocmask操作的信号集
参数oset: 输出型参数(oset的o是output的首字母),作用是保存旧的信号集,保存的目的是为了之后恢复
如果调用sigprocmask解除了对当前若干个未决信号的屏蔽,则在sigprocmask返回前,至少将其中一个信号递达
注: 如果set为NULL,block表不会发生任何变化,但是oset存储当前进程的block表的内容
sigpending

int sigpending(sigset_t *set);
作用: 读取当前进程的未决信号集,通过set参数传出,即set是输出型参数
返回值: 若成功则为0,若出错则为-1
sigaddset和sigdelset
![]()
int sigaddset(sigset_t *set, int signum);
int sigdelset(sigset_t *set, int signum);
作用: sigaddset向设置某个信号在信号集中对应的比特位为1,sigdelset向设置某个信号在信号集中对应的比特位为0
★总结修改3个表的方法
修改handler表
使用signal函数,之前在OS57.【Linux】理解信号: 信号的产生(1) 键盘输入和kill命令文章讲过
修改block表
使用sigprocmask函数
修改pending表
使用sigpending函数
4.代码示范

设置图中的SIGINT信号
从图中得知: 2号信号SIGINT: 被屏蔽(block[1]==1),进程当前收到了(pending[1]==1),处理函数为SIG_IGN
2号信号在被屏蔽的情况下,按下Ctrl+C,penging表的2号信号的对应的比特位值应该从0变1,写代码演示
新建如下文件:
test_signal/
├── makefile
└── test_signal.cpp
makefile写入:
test_signal.out:test_signal.cpp
g++ -o $@ $^ -g -std=c++11
.PHONY:clean
clean:
rm -f test_signal.out
定义sigset_t set对象,注意: 一定要初始化!
sigset_t set;
//必须初始化
if (sigemptyset(&set))
{
std::cout<<"无法初始化信号集"<<std::endl;
exit(1);
}
屏蔽2号信号前,先设置set中2号信号对应的比特位为1:
if (sigaddset(&set,2))
{
std::cout<<"无法设置信号集的掩码"<<std::endl;
exit(2);
}
此时set还没有写入进程,set在当前进程的栈区上,必须借助sigprocmask系统调用才能写入内核真正生效
屏蔽2号信号:
//修改block表,用sigprocmask
if (sigprocmask(SIG_SETMASK,&set,&oset))
{
std::cout<<"无法设置当前进程的信号集"<<std::endl;
exit(2);
}
按键测试2号信号是否被屏蔽
#include <signal.h>
#include <iostream>
int main()
{
sigset_t set,oset;
//必须初始化
if (sigemptyset(&set))
{
std::cout<<"无法初始化信号集"<<std::endl;
exit(1);
}
if (sigaddset(&set,2))
{
std::cout<<"无法设置信号集的掩码"<<std::endl;
exit(2);
}
//修改block表,用sigprocmask
if (sigprocmask(SIG_SETMASK,&set,&oset))
{
std::cout<<"无法设置当前进程的信号集"<<std::endl;
exit(2);
}
std::cout<<"现在可以按下Ctrl+C测试了"<<std::endl;
for (;;);
return 0;
}
运行结果:无论怎么按Ctrl+C都无法退出进程

打印block表说明2号信号被屏蔽
利用sigprocmask的特殊情况: 如果set为NULL,block表不会发生任何变化,但是oset存储当前进程的block表的内容
得到oset后,再调用sigismember逐个判断每个信号即可
这里只判断1~31号信号:
void print_block_bitmap()
{
sigset_t oset;
//必须初始化
if (sigemptyset(&oset))
{
std::cout<<"无法初始化信号集"<<std::endl;
exit(1);
}
if (sigprocmask(SIG_SETMASK,NULL,&oset))
{
std::cout<<"无法获取当前进程的block信号集"<<std::endl;
exit(2);
}
for (int i=1;i<=31;i++)
if (sigismember(&oset,i))
std::cout<<i<<"号信号被屏蔽"<<std::endl;
else
std::cout<<i<<"号信号未屏蔽"<<std::endl;
}
运行结果:

打印pending表说明Ctrl+C被按下
Ctrl+C被按下操作系统向进程发送2号信号,pending表中2号信号对应的比特位为1,说明该信号已经产生(操作系统已经发送给进程),但当前被屏蔽,处于"等待处理"状态
但这里不能对pending进行位操作,必须借助sigpending系统调用:
void print_pending_bitmap()
{
sigset_t oset;
std::cout<<std::endl;
if (sigemptyset(&oset))
{
std::cout<<"无法初始化信号集"<<std::endl;
exit(1);
}
if (sigpending(&oset))
{
std::cout<<"无法获取当前进程的pending信号集"<<std::endl;
exit(2);
}
for (int i=1;i<=31;i++)
if (sigismember(&oset,i))
std::cout<<i<<"号信号被阻塞,处于等待处理状态"<<std::endl;
else
std::cout<<i<<"号信号未产生"<<std::endl;
}
运行结果:

解除屏蔽,打印pending表
为了防止2号信号导致进程终止,这里捕获2号信号
这里使用之前保存的oset来解除屏蔽 (注: 是否屏蔽不影响信号的处理函数)
void myhandler(int){}
signal(2,myhandler);
//解除阻塞
//修改block表,用sigprocmask
//使用之前保存的oset来恢复
if (sigprocmask(SIG_SETMASK,&oset,nullptr))
{
std::cout<<"无法设置当前进程的信号集"<<std::endl;
exit(2);
}
sleep(2);
print_pending_bitmap();
运行结果:

5.能不能屏蔽所有信号?
做个实验:
#include <signal.h>
#include <iostream>
//省略print_block_bitmap()定义
int main()
{
//必须初始化
sigset_t set;
if (sigfillset(&set))
{
std::cout<<"无法初始化信号集"<<std::endl;
exit(1);
}
//修改block表,用sigprocmask
if (sigprocmask(SIG_SETMASK,&set,nullptr))
{
std::cout<<"无法设置当前进程的信号集"<<std::endl;
exit(2);
}
print_block_bitmap();
return 0;
}
运行结果: 1~31号信号中,9号和19号信号无法被屏蔽

反思: 为什么9号和19号信号无法被屏蔽?
理由和OS57.【Linux】理解信号: 信号的产生(1) 键盘输入和kill命令文章提到的"反思: 为什么进程无法捕获9和19号信号?"一样
结论
结论: 9号和19号无法被屏蔽,无法被捕获,无法被忽略
6.回答问题: 0号信号虽然没有,但具体需不需要在sigset_t位图中占位?
这是OS61.【Linux】理解信号: 信号的发送和保存遗留的问题:0号信号虽然没有,但具体需不需要在sigset_t位图中占位?
换句话说,sigset_t位图的最低位表示1号信号? 还是位图的最低位用于占位,没有实际意义?
glibc验证
在glibc-2.42的/signal/sigismem.c中定义了sigismember函数:
/* Return 1 if SIGNO is in SET, 0 if not. */
int
sigismember (const sigset_t *set, int signo)
{
if (set == NULL || signo <= 0 || signo >= NSIG)
{
__set_errno (EINVAL);
return -1;
}
return __sigismember (set, signo);
}
libc_hidden_def (sigismember)
当signo==0时,被if (set == NULL || signo <= 0 || signo >= NSIG)拦截了,无法返回__sigismember (set, signo)
当signo==1时,执行__sigismember (set, signo)
在/sysdeps/unix/sysv/linux/sigsetops.h中定义了__sigismember:
/* Return a mask that includes the bit for SIG only. */
#define __sigmask(sig) \
(1UL << (((sig) - 1) % ULONG_WIDTH))
/* Return the word index for SIG. */
static inline int
__sigword (int sig)
{
return (sig - 1) / ULONG_WIDTH;
}
static inline int
__sigismember (const sigset_t *set, int sig)
{
unsigned long int mask = __sigmask (sig);
int word = __sigword (sig);
return set->__val[word] & mask ? 1 : 0;
}
其中sigset_t的定义:
在/signal/bits/types/sigset_t.h中:
/* A set of signals to be blocked, unblocked, or waited for. */
typedef __sigset_t sigset_t;
x86下,__sigset_t 定义在/sysdeps/unix/sysv/linux/bits/types/__sigset_t.h中:
#ifndef ____sigset_t_defined
#define ____sigset_t_defined
#define _SIGSET_NWORDS (1024 / (8 * sizeof (unsigned long int)))
typedef struct
{
unsigned long int __val[_SIGSET_NWORDS];
} __sigset_t;
#endif
调用过程:
__sigismember (set, 1)
→ unsigned long int mask = __sigmask (1);
→ unsigned long int mask = (1UL << (((1) - 1) % ULONG_WIDTH))
→ unsigned long int mask = 1UL
→ int word = __sigword (1);
→ __sigword (1)
→ return (sig - 1) / ULONG_WIDTH
→ return (1- 1) / ULONG_WIDTH
→ int word = 0;
→ return set->__val[word] & mask ? 1 : 0;
→ return set->__val[0] & 1UL ? 1 : 0;
发现1号信号找的是__val[0]!
Linux内核验证
在/arch/x86/include/asm/signal.h中定义了sigaddset宏:
#define sigaddset(set,sig) \
(__builtin_constant_p(sig) \
? __const_sigaddset((set), (sig)) \
: __gen_sigaddset((set), (sig)))
static inline void __gen_sigaddset(sigset_t *set, int _sig)
{
asm("btsl %1,%0" : "+m"(*set) : "Ir"(_sig - 1) : "cc");
}
static inline void __const_sigaddset(sigset_t *set, int _sig)
{
unsigned long sig = _sig - 1;
set->sig[sig / _NSIG_BPW] |= 1 << (sig % _NSIG_BPW);
}
其中__gen_sigaddset和__const_sigaddset的作用是等价的,__const_sigaddset实现有点复杂,但__gen_sigaddset函数内只有一个指令:
asm("btsl %1,%0" : "+m"(*set) : "Ir"(_sig - 1) : "cc");
提示已经非常明显了: "Ir"(_sig - 1),当_sig为1时, "Ir"(_sig - 1)为 "Ir"(0),发现设置1号信号,内部设置的是位图的最低位!
结论
结论: 底层上,位图的最低位表示1号信号
更多推荐




所有评论(0)