一.信号的概念
信号是进程之间事件异步通知的一种方式,属于软中断 告诉有这样一个信号,但是这个信号的具体处理方式以及什么时候处理由进程决定,所以是软中断。
二.信号的种类
kill —l
可以罗列出所有信号(总共定义了62个信号) 没有32,33号信号
三.信号的产生
1.硬件产生
kill命令向进程发送信号kill -[信号值] [pid]
ctrl +c :2号信号 SIGINT 键盘按下ctrl +c 结束一个进程的时候,其实是进程收到了2号信号。2号信号导致进程的退出ctrl +z :20号信号SIGTSTP 使用ctrl +z 会使一个程序进入暂停状态T;ctrl +|
2.软件产生
-
kill函数int kill(pid_t pid,int sig); pid:进程号,要给哪个进程发送信号,则填写哪个进程的进程号 sig:要发送信号的值 -
raiseint raise(int sig); 谁调用则给谁发送信号。该函数的实现也是调用kill函数
int raise(int sig){
return kill(gitpid(),sig);
}
3.扩展:崩溃程序收到的信号
- 引用空指针,野指针,垂悬指针(收到11号信号)
- 内存访问越界(收到11号信号)
- 除0(收到8号信号)
- double free(收到6号信号)
我们在C/C++当中除零,内存越界等异常,在系统层面上,是被当成信号处理的。
四.信号的处理方式
man 7 signal 查看
1.默认处理方式
SIG_DFL:操作系统当中已经定义信号的处理方式了 2–>终止程序 11—>终止程序,并且产生核心转储文件
2.忽略处理方式
SIG_IGN:该信号为忽略处理 进程收到忽略处理信号后,不进行处理
SIGCHLD 17号信号
子进程先于父进程退出,子进程退出的时候会给父进程发送SIGCHLD,而父进程接收到这个信号后,是忽略处理的,导致父进程没有回收到子进程的退出状态信息,从而子进程变成僵尸进程。
3.自定义处理方式
程序员也可以更改信号的处理方式,定义一个函数,当进程收到该信号的时候,调用自己写的函数
五.信号的注册
一个信号收一个信号的过程叫做信号的注册 信号的注册和信号的注销不是一个过程,是两个独立的过程
内核当中信号注册位图和sigqueue 队列的了解 操作系统内部并没有将sig[] 当做数组使用,而是当做位图使用 总结:
- 信号注册时位图更改为1,添加sigqueue节点和sigqueue队列
- 信号注册时,会将信号对应的比特位从0修改为1,表示当前进程收到了该信号
- 需要在sigqueue队列中添加sigqueue节点,队列在操作系统内核中的本质是一个双向链表(先进先出
两种信号注册的区别
- 非实时信号注册
第一次注册:修改sig位图(从0到1),修改sigqueue队列 第二次注册相同信号:修改sig位图(从1到1),并不会添加sigqueue节点 - 非实时信号注册
第一次注册:修改sig位图(从0到1),修改sigqueue队列 第二次注册相同信号:修改sig位图(从1到1),添加sigqueue节点到sigqueue队列当中
六.信号的注销
七.信号的自定义处理方式
程序员自己定义某一个信号的处理方式
1.实现函数
函数sighandler_t signal(int signum,sighandler_t handler);
- signum:信号值
- handler:更改为的函数处理,接收一个函数地址,函数指针
typedef void(*sighandler_t)(int); 注意: 在调用singnal函数的时候,给第二个参数传递函数地址的时候并没有调用传递的函数,而是等到进程收到某个信号的时候,才回调刚刚注册的函数。 9号信号SIGKILL(强杀)不能被自定义处理。
函数int sigaction(int signum, const struct sigaction *act,struct sigaction *oldact);
- signum:信号值
- act:将信号的处理方式改为act
- oldact:原来信号的处理方式
struct sigaction oldact;
void sigcallback(int sig)
{
printf("sig num is %d\n",sig);
sigaction(2,&oldact,NULL);
}
int main(){
//调用sigaction函数,自定义2号信号的处理方式
//定义struct sigaction 机构体对象
struct sigaction act;
act.sa_handler=sigcallback;
//将act.sa_mask内部清0,防止后续误导进程收到某些信号
sigemptyset(&act.sa_mask);
sigaction(2,&act,&oldact);
while(1){
sleep(1);
}
return 0;
}
主函数中调用sigaction 将2号信号的处理方式改为调用sigcallback ,在sigcallback 函数中重新将2号信号处理方式改回来 现象:运行程序第一次按下crrl+c 运行sigcallback 函数内部代码,第二次按下结束进程
2.原理:内核中的代码
八.信号的捕捉流程
1.过程
2.信号的处理时机
当从内核态切换回用户态的时候,会调用do_signal 函数处理信号 do_signal :判断当前信号是否被阻塞 有:处理该信号(信号的处理方式:默认,忽略,自定义) 没有:直接返回用户态
3.不同的处理方式
默认,忽略:直接在内核中就结束处理 自定义处理:
- 执行用户自定义的处理函数(用户空间)
- 执行
sigreturn() 再次回到操作系统内核(内核空间) - 再次调用 会调回
do_signal 函数处理信号 - 调用
sys_sigreturn 函数回到用户态,继续执行代码
4.常见进入内核的方式
- 调用系统调用函数
- 内存访问越界,访问空指针
- 调用库函数
九.信号的阻塞
信号的阻塞并不会影响信号的注册,而是在进程收到信号的时候,由于阻塞,暂时不处理该信号
1.内核代码
struct task_struct{
.........
sigset_t blocked;(位图)
........
}
当需要阻塞一个信号的时候,将信号对应的比特位设置为1 0:不阻塞 1:阻塞
当加上信号阻塞后,理解信号的处理
- 进入内核,返回之前,会调用do_signal函数处理该信号
- 有信号要处理的时候,先判断信号是否阻塞,有阻塞则不处理,无阻塞则处理信号
2.函数接口
函数int sigprocmask(int how,sigset_t *set, sigset_t *oldset);
- how:想让
sigprocmas k做什么事情 SIG_BLOCK:设置某个信号进入阻塞状态 SIG_UNBLOCK:设置某个信号进入非阻塞状态 SIG_SETMASK:用第二个参数‘set’,替换原理的阻塞位图 - set:设置新的阻塞位图
- oldset:原理老的阻塞位图
原理解析:
- 当how为SIG_BLOCK时,函数会根据set,计算新的阻塞位图方式为
block(new)=block(old) | set - 当how为SIG_UNBLOCK时
block(new)=block(old) & ~set - 当how为SIG_SETMASK时
block(new)=set
9号信号和19号信号不能被阻塞
|