文章目录
信号:
是一个软中断;通知进程发生了某件事情,中断进程当前操作;让进程处理这个事件
信号有很多种—Linux下有62种,非可靠信号1-31,可靠信号34-64;进程还必须识别这些信号
kill杀死进程的原理:
向进程发送信号通知事件
信号生命周期:
信号的产生–>在进程中注册–>在进程中注销–>信号的处理
信号的产生:
硬件产生:ctrl+c(中断信号) ctrl+l(退出信号) ctrl+z(停止信号)
软件产生:kill -signum -p pid int kill(pid_t pid, int sig);
raise(signm) abort() alarm(nsec)
信号在进程中的注册:
在进程pcb中做标记(task struct中的struct pending),标记进程收到了哪些信号
非可靠信号如果未注册,位图修改为1,向sigqueue添加一个节点,如果位图已经从0变为1,
再来一个不做任何操作,只注册一次(事件丢失)
可靠信号位图已经为1,再来一个同样的信号,会在sigqueue中在添加一个节点(不管信号
是否已经注册)---(事件不会丢失)
信号在进程中的注销:
非可靠信号:节点只有一个,删除节点位图置0
可靠信号:节点可能有多个,删除一个节点之后,判断连表中是否还有相同信号的节点
若没有,位图置0,否则位图不变,依然需要标记有信号处理
未决:是一种状态—信号从产生到处理之前的状态
信号的处理:
信号的处理并不是立即被处理;而是选择一个合适的时机去处理(进程的运行从内核态返回用户态的时候)
Q:进程如何从用户态切换到内核态?
A:发起系统调用、系统异常、中断
处理方式:
默认处理方式:即定义好的处理方式
忽略处理方式:处理动作中什么都不做(依然会注册,会处理)
自定义处理方式:用户自己确定信号如何处理---自定义信号的处理函数替换原有的处理函数
接口:
sighandler_t signal(int signum, sighandler_t handler);
signum:信号编号---替换signum这个信号的处理函数
handler:函数指针,用户传入的处理函数 SIG_DFL:信号的默认处理动作
SIG_IGN:信号的忽略处理动作
typedef void(*_signal_handler)(int);
int sigaction(int signum, const struct sigaction *act,struct sigaction *oldact);
休眠状态中如果有信号到来,会打断休眠的操作
自定义处理方式的捕捉流程:
发起系统调用--->切换到内核态(调用功能完成)--->do signal()处理未处理的信号--->sigcb()
信号自定义回调完毕--->sigreturn返回内核态--->(如果有信号还没处理继续调用do signal();)
没有信号待处理就sys_sigreturn返回用户态
代码示例:
#include<stdio.h>
#include<unistd.h>
#include<signal.h>
void sigcb(int signo)
{
printf("signo is : %d\n",signo);
}
int main()
{
//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;
//处理方式替换为sigcb的方式,每次输入要替换的信号,都会执行sigcb函数
new_act.sa_handler=sigcb;
__sigemptyset(&new_act.sa_mask);
//将2号信号的动作用new_act替换掉,old_act用来保存原来的信号集合
sigaction(2,&new_act,&old_act);
while(1)
{
printf("i love luzihan ~\n");
sleep(3);
}
return 0;
}
信号的阻塞:
阻止信号被递达—信号依然可以被注册,只是暂时不处理
递达:一个动作—信号的处理
在pcb中还有一个集合—阻塞信号集合—标记哪些信号暂时不被处理
pending位图 block位图 handler位图
接口:
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
参数第二个传地址&set;
SIG_BLOCK:向阻塞集合中加入set集合中的信号 block | set
SIG_UNBLOCK:从阻塞集合中移除set集合中的信号 block &(~set)
SIG_SETMASK:将set集合中的信号设置为阻塞集合
oldset:保存原先的集合
int sigfillset(sigset_t *set);
int sigaddset(sigset_t *set, int signum);
代码示例:
#include<stdio.h>
#include<unistd.h>
#include<signal.h>
void sigcb(int signo)
{
printf("signo is : %d\n",signo);
}
int main()
{
//#include <signal.h>
//int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
sigset_t set,old_set;
//创建好set集合之后必须清空,否则信号是不能放入set集合中的
sigemptyset(&set);
//可靠信号与不可靠信号的对比
signal(SIGINT,sigcb);
signal(34,sigcb);
//把所有信号都添加到set集合中
sigfillset(&set);
//把set集合中的所有信号都阻塞
sigprocmask(SIG_BLOCK,&set,&old_set);
getchar();
sigprocmask(SIG_UNBLOCK,&set,NULL);
return 0;
}
kill -9 和 kill -19 是无法被阻塞的无法被忽略无法被自定义 SIGKILL和SIGSTOP
函数的重入:
多个执行流程同时执行进入相同的函数
可重入:不会造成数据二义性以及代码逻辑混乱
不可重入:有可能造成数据二义性以及代码逻辑混乱
当用户设计一个函数或者使用一个函数的时候,在多个执行流中,这时候需要考虑函数是否可重入
是否重入的关键点:
函数是否对临界资源(全局数据)进行了非原子操作
examp:a++分三步,从内存读a,累加,返回到内存---cpu时间片切换,这个操作就可以被打断
代码示例:
#include<stdio.h>
#include<unistd.h>
#include<signal.h>
int x=1;
int y=1;
int sum(int* a,int* b)
{
(*a)++;
sleep(3);//给足够的时间输入信号
(*b)++;
return (*a)+(*b);
}
void sigcb(signo)
{
printf("signo is : %d\n",sum(&x,&y));
}
int main()
{
signal(SIGINT,sigcb);
printf("sum is : %d\n",sum(&x,&y));
return 0;
}