信号(软中断)
概念
信号用于通知一个事件的发生,会打断当前操作,去处理这个事件;
当然有一个前提:必须识别这个信号
信号种类有很多,每个都代表不同事件
信号是有生命周期:产生->注册->注销->处理
kill -l //查看信号列表
信号的分类
信号总共有62种,有两大类型
非可靠信号:1~31号信号
可靠信号:34~64号信号
信号的产生
硬件产生:ctrl+c ctrl+l ctrl+z
软件产生:kill -signum pid 向进程发送一个signum信号
kill(pid,signum)
raise(signum)
abort()
alarm(nsec)
实现
#include<stdio.h>
#include<string.h>
#include<unistd.h>
#include<signal.h>
#include<stdlib.h>
int main()
{
//向pid进程发送sig信号
//原型:int kill(pid_t pid,int sig);
// kill(getpid(),SIGKILL);
//给当前进程发送信号
//原型:int raise(int sig);
//raise(SIGTERM);
//给当前信号发送SIGABRT
//原型:void abort(void);
//abort();
//经过固定秒后,给当前进程发送SIGALRM信号
//unsigned int alarm(unsigned int seconds);
//当seconds == 0时,表示取消上一个时间为到的定时器
alarm(5);
alarm(0);
while(1)
{
printf("hello!!\n");
sleep(1);
}
return 0;
}
信号注册
信号在进程中的注册:在进程pcb中做标记,标记进程收到了哪些信号
非可靠信号注册:判断pcb中的pending位图中相应信号是否已经注册(位置是否已经置一);若未注册则位图修改为1,向sigqueue链表中添加一个信号节点;若已经注册,则不做任何操作(事件丢失)。
可靠信号注册:不管信号是否已经注册,都会向链表中添加一个新的信号节点(事件不会丢失)
信号在进程中的注销
非可靠信号:节点只有一个,注销就是删除节点,位图置零
可靠信号:节点有可能有多个,注销就是删除一个节点,判断链表中是否还有相同信号节点;若没有则位图置零;否则位图不变依然需要标记有这个信号待处理。
v
信号的处理
信号的处理并不是立即被处理;而是选择一个合适的时机去处理信号,即进程的运行从内核态返回用户态的时候。
**进程如何从用户态切换到内核态:**发起系统调用;程序异常;中断
进程运行的代码若是库函数或者用户自己写的函数,就说进程当前运行在用户态;
信号处理有多种方式:
默认处理方式—既定义好的处理方式
忽略处理方式—处理动作中什么都没做
自定义处理方式—用户自己确定信号如何处理—自定义信号的处理函数替换原有的处理函数
如何修改信号处理方式:
#include<stdio.h>
#include<unistd.h>
#include<signal.h>
void sigcb(int signo)
{
printf("recv a signo:%d\n",signo);
signal(signo,SIG_DFL);//将信号的处理方式还原成默认方式
}
int main()
{
// sighandler_t signal(int signum,sighandler_t handler);
signal(SIGINT,SIG_IGN);
/*
//int sigaction(int signum,const struct sigaction *act,struct sigaction *oldact);
struct sigaction new_act,old_act;
new_act.sa_flags = 0;
new_act.sa_handler = sigcb;
//清空set信号集合
//int sigemptyset(sugset_t *set);
sigemptyset(&new_act.sa_mask);
//sigaction使用new_act替换2号信号的处理动作,将原有动作保存到old_act
sigaction(SIGINT,&new_act,&old_act);
*/
while(1)
{
printf("~~~~~~leihaoa!!!!!!!!!");
sleep(5);
}
return 0;
}
在代码中,用sigaction将信号SIGINT的处理动作替换成了我们自定义的函数sigcb,并将原处理方式保存在old_act中,当接收到信号SIGINT时,会打印信号编号,接下来我们将信号的处理方式又还原成默认处理方式,所以当我们第二次再给一个SIGINT信号的时候,程序就可以直接退出了,运行结果如下:
自定义处理方式信号的捕捉流程
信号的阻塞
阻止信号被递达—信号依然可以注册,只是暂时不处理
递达:一个动作—信号的处理
在pcb中还有一个集合—阻塞信号集合—标记哪些信号暂时不被处理
[外链图片转存失败(img-mV6WAdTf-1566029002971)(D:\Desktop\笔记\笔记\笔记\Linux笔记\Linux笔记\Linux笔记\图片\信号\2.png)]
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset)
how:
SIG_BLOCK 像阻塞集合中加入set集合中的信号 block = mask | set
SIG_UNBLOCK 向阻塞集合中移除set集合中的信号 block = mask & (~set)
SIG_SETMASK 将set集合的信号设置为阻塞信号 block = set
oldset 用于保存修改前,阻塞集合中的信号
在所有信号中9号信号SIGKILL和19号信号SIGSTOP无法被阻塞,无法被自定义。
//实现信号阻塞:先将所有信号全部阻塞,getchar()在用户按下回车之前,这些信号一直被阻塞对所有信号解除
////阻塞 ---这时候信号解除阻塞,将被处理。体会可靠信号和不可靠信号
#include<stdio.h>
#include<unistd.h>
#include<signal.h>
void sigcb(int signo)
{
printf("recv signo:%d\n",signo);
}
int main()
{
signal(40,sigcb);
signal(SIGINT,sigcb);
//int sigprocmask(int how, const sigset_t *set, sigset_t *oldset)
sigset_t set,oldset;
sigemptyset(&set);
//向set集合中添加所有信号
//int sigfillset(sigset_t *set)
//向set集合中添加signum信号
//int sigfillset(sigset_t *set,int signum);
sigfillset(&set);
//阻塞set中的信号
sigprocmask(SIG_BLOCK,&set,&oldset);
getchar();
// sigprocmask(SIG_BLOCK,&oldset,&oldset);
sigprocmask(SIG_UNBLOCK,&set,NULL);
return 0;
}
运行后,信号全都会被阻塞,直到用户按下回车后,阻塞解除,信号处理正常。
可重入函数与不可重入函数
//体会函数的重入造成的影响
#include<stdio.h>
#include<unistd>
#include<signal.h>
int a =1;
int b = 1;
int sum(int *a,int *b)
{
(*a)++;
sleep(3);
(*b)++;
return *a + *b;
}
void sigcb(int no)
{
printf("signal------%d\n",sum(&a,&b));
}
int main()
{
signal(SIGINT,sigcb);
printf("main--------%d\n",sum(&a,&b))
return 0;
}
函数的重入:多个执行流程同时执行进入相同的函数
函数的可重入与不可重入
可重入:多个执行流程同时执行进入相同的函数,不会造成数据二义性以及代码逻辑混乱
不可重入:多个执行流程同时执行进入相同的函数,有可能造成数据二义性以及代码逻辑混乱
当用户设计一个函数或使用一个函数的视乎在多个执行流中,那么这时候就需要考虑是否可重入情况
函数可重入与不可重入的关键点
这个函数是否对临界资源(全局数据)进行了非原子操作
如果一个函数符合以下条件之一则是不可重入的
1.调用了malloc或free,因为malloc也是用全局链表来管理堆的。
2.调用了标准I/O库函数。标准I/O库的很多实现都以不可重入的方式使用全局数据结构。