目录
1、练习:修改屏蔽字,实现人为阻塞信号
1、定义两个信号集类型set和oset
sigset_t set,oset;
2、将信号集初始化
将所有位码初始化为0
sigemptyset(&set);
将所有位码初始化为1
sigfillset(&set);
3、设置set集合中的signo对应的位置
signo对应的位设置为1
sigaddset(&set,signo);
signo对应的位设置为0
sigdelset(&set,signo);
4、判断某一位的位码是0还是1
传入信号集地址和信号编号,返回值为位码(0或1)
1/0=sigismember(&set,signo);
5、替换
使用set覆盖进程原有的屏蔽字,并传出旧的到oset中
第一个参数------替换方式(SIG_SEMASK为直接替换)
第二个参数------新的信号集
第三个参数------原有的传出的信号集
sigprocmask(SIG_SEMASK,&set,&oset);
总代码
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<string.h>
#include<signal.h>
int main()
{
sigset_t set,oset;
sigemptyset(&set);
sigaddset(&set,SIGQUIT);
sigprocmask(SIG_SETMASK,&set,&oset);
while(1)
sleep(1);
}
2、信号失效的方法
|--------------|---------------|-----------|
| 行为修改->忽略 | 行为修改->捕捉 | 行为修改->屏蔽 |
| 已递达,但是没有执行动作 | 已递达,但是执行自定义代码 | 未递达,延迟处理 |
3、高级信号
|---------------|----------------|
| SIGKILL(9) | SIGTOP(19) |
| 9号,只要发出必然杀死进程 | 19号,只要发出必然挂起进程 |
系统触发信号相比于用户发送信号,权限更高,用户不能屏蔽系统发出的直接杀死进程的高级信号
如果普通进程杀死高级别的其他用户进程,会因为权限不足失败,比如普通用户不能杀死root用户的进程
4、查看未决信号集
查看进程的信号屏蔽情况,要查看未决信号集
使用sigpend函数,获取进程的未决信号集
sigpend(&pset);
总代码
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<string.h>
#include<signal.h>
void cat_pset(sigset_t pset)
{
int i;
for(i=1;i<32;i++)
{
if((sigismember(&pset,i)))
putchar('1');
else
putchar('0');
}
putchar('\n');
}
int main()
{
sigset_t set,oset;
sigemptyset(&set);
sigaddset(&set,SIGINT);
sigaddset(&set,SIGQUIT);
sigaddset(&set,SIGSEGV);
sigaddset(&set,SIGBUS);
sigaddset(&set,SIGFPE);
sigprocmask(SIG_SETMASK,&set,&oset);
sigset_t pset;
while(1)
{
sigpending(&pset);
cat_pset(pset);
sleep(1);
}
}
5、信号到达进程及处理流程
1、程序一般运行在用户层,只有调用函数才会到内核层
2、系统发信号是发给PCB的,PCB在内核层
主函数和捕捉函数都存在于用户层
3、想要处理信号,必须满足两个条件,第一个条件:上切到内核层
上切的情况:系统调用(触发上切)、软件中断(时间片耗尽)、进程异常
因此需要:进程执行系统函数,触发切换
4、完成调用,处置中断,处置异常
系统到内核层,不能直接先处理信号,需要先处理上切的原因,如中断异常等,完成后才能处理信号
5、完成任务后,在返回用户空间前,检测是否有未处理信号,如果有,再处理
6、此信号绑定了捕捉函数,但捕捉函数在用户层
7、再次切换到用户层会浪费资源
因此,内核保持高权限,执行用户层捕捉函数
8、函数执行完毕,返回调用位置
9、main函数从中断的位置继续执行
当切换到内核层时,main就中断了,此时继续执行
一般情况下,带有信号捕捉的进程,永远是main函数先执行,但是执行过程触发信号,永远是捕捉函数先一步执行完毕
可能引起的问题
1、全局变量异步IO
进程中使用信号捕捉技术一定要避免:捕捉函数中使用共享资源,全局变量等......
2、可重入和不可重入函数
insert()函数一般被称为不可重入函数,这些函数中使用了全局或静态资源,会造成隐患
函数中不包含全局变量和静态资源的使用,并且不访问共享资源,只使用临时或局部变量,这种函数是安全的,称为可重入资源
6、信号引发的时序竞态问题
问题描述
使用定时器模拟竞态问题
sleep函数分为两个步骤,定时器alarm和pause挂起进程
alarm定时器定时结束后,会发出SIGALRM信号杀死进程
pause函数只要察觉到任意信号,且那个信号必须有处理动作,pause函数就能立即被唤醒
因此有可能定时没结束,其他信号过来了,pause函数也会被唤醒
只能使用捕捉方式,屏蔽未递达,忽略没有处理动作
设置定时器和接收返回值
unsigned int reval;
reval=alarm(seconds);
return reval;
设置信号捕捉
void sig_do(int n){}
设置新信号结构体
struct sigaction act,oact;
act.sa_handler =sig_do;
act.sa_flags=0;
sigemptyset(&act.sa_mask);
替换行为
sigaction(SIGALRM,&act,&oact);
如果在reval=alarm(seconds);后马上调用pause函数,不会有问题,程序正常执行
但是如果加sleep(2),程序不能正常执行
因为alarm定时1秒时,信号已经触发了,此时不会处理信号
sleep是系统函数,会触发系统调用,到内核层,在内核层处理信号后,再去调用pause时,因为信号被处理掉了,因此pause函数不会运行,会被挂起
因此是sleep函数提前处理了SIGALRM函数,导致pause函数因为SIGALRM信号丢失,无法唤醒
解决方法
原子概念:sigsuspend(sigset_t* set);------执行立即挂起,察觉信号唤醒,接触屏蔽字
唤醒时,解除一次屏蔽
设置屏蔽
sigset_t set,oset;
sigemptyset(&set);
sigaddset(&set,SIGALRM);
sigprocmask(SIG_SETMASK,&set,&oset);
唤醒的时候同时解除,临时解除,将1变为0,然后还原
sigsuspend(&act.sa_mask);
总代码
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<string.h>
#include<signal.h>
void sig_do(int n){}
unsigned int mysleep(unsigned int seconds)
{
unsigned int reval;
struct sigaction act,oact;
act.sa_handler =sig_do;
act.sa_flags=0;
sigemptyset(&act.sa_mask);
sigaction(SIGALRM,&act,&oact);
sigset_t set,oset;
sigemptyset(&set);
sigaddset(&set,SIGALRM);
sigprocmask(SIG_SETMASK,&set,&oset);
reval=alarm(seconds);
sleep(1);
sigsuspend(&act.sa_mask);
return reval;
}
int main()
{
while(1)
{
mysleep(1);
printf("sleep two seconds\n");
}
return 0;
}