信号集操作函数
内核通过读取未决信号集来判断信号是否应被处理,信号屏蔽字 mask 可以影响未决信号集。而我们可以在应用程序中自定义 set 来改变 mask。以达到屏蔽指定信号的目的。
信号集设定
sigset_t set; //typedef unsigned long sigset_t;
int sigemptyset(sigset_t *set); //将某个信号集清零 成功:0;失败:-1
int sigfillset(sigset_t *set); //将某个信号集置1 成功:0;失败:-1
int sigaddset(sigset_t *set, int signum); //将某个信号加入信号集 成功:0;失败:-1
int sigdelset(sigset_t *set, int signum); //将某个信号从信号集中删除 成功:0;失败:-1
int sigismember(const sigset_t *set, int signum); //判断某个信号是否在信号集中 在集合:1;不在:0
sigset_t 类型的本质是位图。但不应该直接使用位操作,而应该使用上述函数,保证跨系统操作有效。
1. sigprocmask 函数
用来屏蔽信号、解除屏蔽也使用该函数。其本质,读取或修改进程的信号屏蔽字(PCB 中)
【注意】屏蔽信号:只是将信号处理延后执行(延至解除屏蔽);而忽略表示将信号丢弃
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
【参数】
1. set:传入参数,是一个位图,set中哪个位置1,就表示屏蔽哪个信号
2. oldset:传出参数,保存旧的信号屏蔽集
3. how:假设当前的信号屏蔽字为 mask
- SIG_BLOCK:当 how 设置为此值,set 表示需要屏蔽的信号,相当于 mask = mask | set
- SIG_UNBLOCK:当 how 设置为此值,set 表示需要屏蔽的信号,相当于 mask = mask & ~set
- SIG_SETMASK:当 how 设置为此值,set 表示用于替代原始屏蔽集的新屏蔽集,相当于 mask = set。
若调用 sigprocmask 解除了对当前若干信号的阻塞,则在 sigprocmask 返回之前,至少将其中一个信号递达。
2. sigpending 函数
读取当前进程的未决信号集,不能读取阻塞信号集,但是可以根据未决信号集推出阻塞信号集的状态
int sigpending(sigset_t *set);
【参数】
set:传出参数
【返回值】
成功:0;失败:-1,设置errno
【练习】打印未决信号集
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
void print(sigset_t *ped)
{
int i;
for(i = 1; i < 32; i++)
{
if(sigismember(ped, i) == 1)
{
putchar('1');
}
else
{
putchar('0');
}
}
printf("\n");
}
int main()
{
sigset_t myset, oldset, ped;
sigemptyset(&myset); //现将 myset 中所有信号置0
sigaddset(&myset, SIGQUIT); //将3号置1
sigaddset(&myset, SIGTSTP); //将20号置1
sigprocmask(SIG_BLOCK, &myset, &oldset);
while(1)
{
sigpending(&ped); //取出未决信号集
print(&ped);
sleep(1);
}
return 0;
}
【运行结果】
信号捕捉
1. signal 函数
注册一个信号捕捉函数:
typedef void(*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
该函数由ANSI定义,由于历史原因在不同版本的 Unix 中可能有不同的行为。因此应该尽量避免使用它,取而代之的是 sigaction 函数
【举个栗子】
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
typedef void (*sighandler_t)(int);
void fun()
{
printf("---------------catch\n");
}
int main()
{
sighandler_t handler;
handler = signal(SIGQUIT, fun); //按下 ctrl+\,捕捉信号
if(handler == SIG_ERR)
{
perror("signal error");
return -1;
}
while(1);
return 0;
}
【运行结果】每按下 Ctrl + \,都会打印 catch
2. sigaction 函数
修改信号处理动作(通常在 Linux 用来注册一个信号的捕捉函数)
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
【返回值】
成功:0;失败:-1,设置 errno
struct sigaction {
void (*sa_handler)(int);
void (*sa_sigaction)(int, siginfo_t *, void *); //不研究
sigset_t sa_mask;
int sa_flags;
void (*sa_restorer)(void); //过时,不用
};
【参数】
1. void (*sa_handler)(int); //捕捉函数
2. sa_mask:用于指定在信号捕捉函数执行期间所屏蔽的信号集
3. sa_flags:
[参数说明]
sa_handler
指定信号捕捉后的处理函数。也可赋值为 SIG_IGN 表示忽略 或 SIG_DEL 表示执行默认动作sa_mask
调用信号处理函数时,所要屏蔽的信号集合。注意,仅在处理函数调用期间屏蔽生效,是临时性设置
假设此时正在处理2号信号的信号捕捉函数,这时,又有新的信号到达(比如3,9号信号都到了),这时候会跳出2号信号的捕捉函数,去处理3号和9号的捕捉函数。假设我们用 sa_mask 设置了3号和9号信号屏蔽,那么在处理2号信号的信号捕捉函数期间,3,9号信号到达,那么2号捕捉函数会继续执行,不会跳出来去执行3和9号的捕捉函数。简而言之,sa_mask 的作用就是防止当前正在处理的信号被一些其他信号所打断。sa_flags
通常设置为 0,表示使用默认属性,在信号处理函数执行期间,自动屏蔽本信号
【举个栗子】写一个捕捉 SIGQUIT 的信号处理函数,在信号处理函数期间,屏蔽 SIGTSTP 信号。
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
typedef void (*sighandler_t)(int);
void fun()
{
while(1)
{
printf("---------------catch\n");
sleep(1);
}
}
int main()
{
sighandler_t handler;
sigset_t myset;
struct sigaction act, oldact;
sigemptyset(&myset);
sigaddset(&myset,SIGTSTP);
act.sa_handler = fun;
act.sa_flags = 0;
act.sa_mask = myset;
sigaction(SIGQUIT, &act, &oldact);
while(1);
return 0;
}
【运行结果】当 按下 ctrl + \,执行 SIGQUIT 的信号处理函数(循环打印catch),在 SIGQUIT 信号处理函数期间,若按下 ctrl + z,SIGQUIT 的信号处理函数不会退出,会继续执行,但是如果按下 ctrl + c,那么会去执行 ctrl + c的信号处理函数,默认退出进程。
信号捕捉特性
- 进程正常运行时,默认 PCB 中有一个信号屏蔽字,它决定了进程自动屏蔽哪些信号。当注册了某个信号捕捉函数,捕捉到该信号以后,要调用该函数。而该函数有可能执行很长时间,在这期间所屏蔽的信号不由默认信号屏蔽字来决定,而是由 sa_mask 来指定。调用完信号处理函数,在恢复成默认的。
- xxx信号捕捉函数执行期间,xxx 信号自动被屏蔽
- 阻塞的常规信号不支持排队,产生多次只记录一次。(后32个实时信号支持排队)