目录
一. 信号保存和阻塞的相关概念
二. 进程信号的表示
2.1 进程信号在内核中的表示
2.2 sigset_t 类型
三. 信号集操作相关函数
3.1 sigset_t 类型参数设置相关函数
3.2 sigprocmask 函数
3.3 sigpending 函数
四. 演示代码
4.1 将所有信号的处理方式都注册为不退出进程
4.2 显示未决信号集
4.3 所有信号设置为阻塞
五. 总结
每一个进程的PCB中,都会存储三张用于表示信号状态的表(如图2.1所示),他们分别为block、pending和handler,其中:
OS不允许用于直接对block和pending位图中的二进制位进行修改,因此提供了一个OS类型sigset专门用于设置用于进程信号表示的相关位图,sigset_t 可以被称为信号集,sigset_t 的底层是位图结果,用 0/1 来表示状态。
阻塞信号集也叫做当前进程的信号屏蔽字(Signal Mask)。
上面的这些函数,都是成功执行返回0,失败返回-1。
sigprocmask函数 -- 读取或更改信号的阻塞状态
函数原型:int sigprocmask( int how, sigset_t *set, sigset_t *obset )
函数参数:
how -- 方法选择,添加阻塞、删除阻塞或设置阻塞
set -- 如果选择设置阻塞,则用set中的信息设置
obset -- 输出型参数,读取原来的阻塞信息
返回值:成功返回0,失败返回-1
在sigprocmask函数中,参数 how 用于方法的选择,how有三个可选项,分别为:SIG_BLOCK、SIG_UNBLOCK 和 SIG_SETMASK。
how | 功能 |
---|---|
SIG_BLOCK | 添加对特定信号的阻塞状态,相当于mask | set |
SIG_UNBLOCK | 取消对特定信号的阻塞状态,相当于mask & ~set |
SIG_SETMASK | 使用set设置信号阻塞状态,相当于mask = set |
sigpending函数 -- 获取进程当前的未决信号集
函数原型:int sigpending(sigset_t *set)
函数参数:set为输出型参数,用于获取当前进程的未决信号集
返回值:运行成功返回0,失败返回-1
如果我们在程序中将所有信号的处理方法都设置为不退出,并在之后执行死循环,那么,是不是进程就无法被杀死了呢?
答案显然是否定的,OS的设计者早就考虑到了这一点,9号信号SIGKILL为管理员权限信号,用户不可以重新注册其处理方法,代码和运行结果如下。
代码4.1:所有信号重新注册方法为不退出
#include
#include
#include
// 用户自定义的信号处理函数
void siganl_handler(int sig)
{
std::cout << "recieve a signal, signum:" << sig << std::endl;
}
int main()
{
std::cout << "this is a process, pid:" << getpid() << ", ppid:" << getppid() << std::endl;
// 1. 将全部普通信号处理函数重新注册
for(int sig = 1; sig <= 31; ++sig)
{
signal(sig, siganl_handler);
}
// 2. 执行死循环
while(true)
{ }
return 0;
}
代码4.2通过sigprocmask函数,在第5s设置2号SIGINT信号为阻塞状态,,在第8s通过raise函数向进程发送2号SIGINT信号,在第15s取消SIGINT信号的阻塞状态,并实时输出未决状态信息,代码运行结果如图4.2,2号信号的阻塞状态由0至1,并在阻塞状态取消后,执行用户自定义的2号SIGINT信号处理函数,退出进程。
代码4.2:未决信号集的打印
#include
#include
#include
// 用户自定义的信号处理函数
void siganl_handler(int sig)
{
std::cout << "recieve a signal, signum:" << sig << std::endl;
exit(0);
}
// 未决状态打印函数
void showPending(const sigset_t& pending)
{
for(int sig = 1; sig <= 31; ++sig)
{
if(sigismember(&pending, sig)) std::cout << 1;
else std::cout << 0;
}
std::cout << std::endl;
}
int main()
{
// 重新注册2号信号的处理方法
signal(SIGINT, siganl_handler);
// bset用于设置阻塞状态,obset用于接收原阻塞状态
sigset_t bset, obset;
// pending用于获取阻塞集
sigset_t pending;
//对信号集设置初始状态
sigemptyset(&bset);
sigaddset(&bset, SIGINT);
int count = 0;
while(true)
{
sleep(1);
std::cout << ++count << ": " << std::flush;
sigpending(&pending); // 接收阻塞状态
showPending(pending); // 阻塞状态打印
if(count == 5)
{
std::cout << "设置SIGINT信号处于阻塞状态 ..." << std::endl;
sigprocmask(SIG_BLOCK, &bset, &obset);
}
if(count == 8)
{
std::cout << "发送SIGINT信号" << std::endl;
raise(SIGINT);
}
if(count == 15)
{
std::cout << "取消对SIGINT信号的阻塞" << std::endl;
sigprocmask(SIG_SETMASK, &obset, nullptr);
}
}
return 0;
}
设置所有信号阻塞,然后死循环,是不是发送任何信号都无法终止进程的运行?
答案当然也不是,9号SIGKILL信号和19号SIGSTOP信号都无法被阻塞,在19号信号使进程终止运行期间,18号SIGCONT信号也无法被阻塞。
代码4.3:验证9号SIGKILL信号无法被阻塞
#include
#include
#include
// 未决状态打印函数
void showPending(const sigset_t& pending)
{
for(int sig = 1; sig <= 31; ++sig)
{
if(sigismember(&pending, sig)) std::cout << 1;
else std::cout << 0;
}
std::cout << std::endl;
}
int main()
{
// 设置所有信号阻塞
sigset_t bset, obset;
sigset_t pending;
sigfillset(&bset);
sigprocmask(SIG_BLOCK, &bset, &obset);
// 依次向进程发送1 - 31号信号,如何输出未决状态
for(int sig = 1; sig <= 31; ++sig)
{
std::cout << "kill -" << sig << std::endl;
raise(sig);
// 获取阻塞信号集并打印
sigpending(&pending);
showPending(pending);
sleep(1);
}
return 0;
}