信号在生活中无处不在,上下课铃,红绿灯,闹钟等,这一切都是信号,可是我们是如何识别信号的呢?无非就是有人定义将信号与意义绑定,并灌输。在操作系统中也存在信号,那么我们一起来看看吧
1 理解信号
信号不管是在生活中还是在操作系统中其实都可以简单看为三部分
- 是什么
- 为什么
- 怎么处理
而在操作系统层面来看并不需要知道为什么(毕竟信号也是工程师定义的),他只需要知道是啥信号和收到信号后要如何处理,其实生活中收到信号也是如此,过马路红灯停,绿灯行,并不会有人问为啥。
1.1 见识Linux中的信号
在linux中查看信号需要用到命令 kill -l ,一共62个信号,1~31为默认信号,24~64为实时信号
浅谈: 默认信号 VS 实时信号
普通信号会丢失,实时信号不会丢失,也就是是否要存储信号,后文会提
在Linux下的全部信号都是宏,也就是一个标志位,这样做的目的节省空间,用一个bit位标识一种状态,默认信号存放在该路径下/usr/include/bits/signum.h
1.2 如何产生信号
一切事情都是有前因后果的,当然信号也是,先要如何产生才可以知道如何处理
- 键盘产生
- 命令行向进程发送信号
- 调用函数向os发送请求
- 代码出错(访问硬件出错)
键盘产生
ctrl+c 终止进程 ctrl+\ 终止进程并保存core文件 ,ctrl+z 挂起一个文件,他们其实和信信号列表一一对应,后文信号捕捉验证
命令行向进程发送信号
使用命令 kill -信号(数字or宏名) 进程pid
调用函数向os发送请求
其实就是和os说快给我发信号,C语言接口alarm(延迟xx秒让os发送14号信号给该进程) ,abort(让os给当前发6号信号) ,raise(指定让os发送x号信号) ,kill(给别的进程发送信号)
alarm 代码:
int main()
{
alarm(5);
int count=0;
while(1)
{
cout<<count++<<" "<<getpid()<<endl;
sleep(1);
}
return 0;
}
结果:
kill
代码:
#include<iostream>
#include<csignal>
#include<unistd.h>
#include<sys/types.h>
using namespace std;
int main()
{
kill(1213,1);
}
结果:
代码出错(访问硬件出错)
当子进程退出的时候可以用wait函数等待回收他的资源,且也可以知道他的退出码,core dump ,信号 。
代码:
#include<iostream>
#include<csignal>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>
using namespace std;
int main()
{
pid_t id=fork();
while(id==0)
{
int count=0;
while(count!=5)
{
count++;
cout<<"I am child proc"<<endl;
sleep(1);
}
int *c=0x000000;
*c=1;
}
int status=0;
waitpid(id,&status,0);
cout<<(status&0x7f)<<endl;
return 0;
}
结果:
问
os是如何知道代码出错而发对应的信号呢?
答
除0对应的硬件 == cup中的状态寄存器 野指针 == 内存/页表MMU os是软硬件的管理者,自然就知道哪里出错,在根据硬件在对进程发送信号
core dump
1.3 信号的处理
在现实中我们处理信号一般有三种方式
- 默认(识别信号马上处理 , 红绿灯)
- 忽略 (识别信号不处理,打游戏妈妈叫吃饭)
- 捕捉自定义 (识别后用自己的方法,听到闹钟声跳个舞🐶🐶)
在操作系统中也是如此处理信号也是以上三种方式,且也知道一个进程收到信号不一定马上处理 ,那么不处理就要保存起来 (该进程的PCB中)。按照老的套路先描述在组织,这里的组织方式就是一个位图
当os发送信号给进程时,看信号是xx号并在对应的pending位图中,把xx号位置设为1 ,再看看block位图xx号位置是否设为1,如果为1屏蔽信号,不然直接执行方法。其实信号被屏蔽还有一个专业的名词(未决),信号没有被屏蔽执行的方法(递达)
递达信号流程图
1.3.1 屏蔽信号与获取pending
在C语言中有一个类型sigset_t 可以表示一个每个信号的有效与无效,也就是一个位图,我们可以用它和信号集操作函数等来告诉os屏蔽那些信号
信号集操作函数
#include <signal.h>
int sigemptyset(sigset_t *set);
int sigfillset(sigset_t *set);
int sigaddset (sigset_t *set, int signo);
int sigdelset(sigset_t *set, int signo);
int sigismember(const sigset_t *set, int signo);
上述的接口都是语言接口,也就说明,我们还没有告诉os要屏蔽那个信号,那么就要用到系统接口,sigprocmask
sigprocmask使用介绍
使用这个接口就可以让os吧pcb中block变成自己所设置位图的样子,屏蔽信号
sigpending使用介绍
输入形参数,获取pending当前状态
代码: 实现屏蔽信号获取pending,删除屏蔽,抵达信号
#include<iostream>
#include<unistd.h>
#include<signal.h>
#include<unistd.h>
using namespace std;
void showpending(sigset_t * set)
{
for(int i =1; i<=31;i++)
{
if(sigismember(set,i))
{
cout<<'1';
}
else
{
cout<<'0';
}
}
cout<<endl;
}
int main()
{
sigset_t set,oset;
sigemptyset(&set);
sigemptyset(&oset);
sigaddset(&set,2);
sigprocmask(SIG_SETMASK,&set,&oset);
int count=0;
while(1)
{
sigpending(&set);
showpending(&set);
sleep(1);
count++;
if(count==7)
{
break;
}
}
return 0;
}
结果:
1.3.2 信号捕捉(自定义处理)
上述说过信号的处理方式, 默认,忽略,捕捉,其实捕捉就是自定义信号(递达),也就是修改pcb中的hander函数数组中的方法,系统接口signal ,sigaction
接口介绍:
接口使用
signal
#include<iostream>
#include<unistd.h>
#include<signal.h>
#include<unistd.h>
using namespace std;
void hander(int signo)
{
cout<<"signo: "<<signo<<endl;
}
int main()
{
signal(2,hander);
int count=0;
while(1)
{
cout<<count++<<endl;
sleep(1);
}
return 0;
}
结果:
sigaction
#include<iostream>
#include<unistd.h>
#include<signal.h>
#include<unistd.h>
using namespace std;
void hander(int signo)
{
cout<<"signo: "<<signo<<endl;
}
int main()
{
struct sigaction act;
struct sigaction oact;
act.sa_handler=hander;
sigaction(2,&act,&oact);
int count=0;
while(1)
{
cout<<count++<<endl;
sleep(1);
}
return 0;
}
结果:
问
既然可以屏蔽捕捉信号,那我全部屏蔽我这个进程不就杀不死了吗?
答
既然你可以想到,没错工程师也想到了,在linux下有SIGKILL与SIGTOP信号是无法捕捉的和屏蔽 ,专门防止你小心思
问
问信号时如何捕捉的呢?
答
抽象理解,捕捉函数是一个地雷,而触发的条件就是信号。
回顾进程虚拟地中空间:
之前说每个进程都有自己的一张独有的页表,其实还不太准确,其实有俩张一张是用户级页表,一张是系统级页表(每个进程共享) ,在执行代码时,没有遇到系统接口前用的都是用户级页表,当执行到系统接口时转换为系统页表
图解捕捉捕捉的流程图: 捕捉的过程其实很像数学中的一个符号∞
1.4 SIGCHILD
之前说过子进程退出的时候父进程会回收子进程,一方面是防止内存泄漏,一方面是获取子进程的退出结果。但等待,父进程就在阻塞等待里(or非阻塞轮询),着都让父进程无法一心一意的执行自己的代码?那么如何解决呢?
解决方案
当子进退出的时候会给父进程发送一个信号SIGCHILD ,🙉🙉🙉,那配合signal不就…………子进程退出发送信号,捕捉,改变SIGCHILD的函数实现方法
代码:
using namespace std;
void hander(int signo)
{
cout<<signo<<endl;
pid_t id;
while((id=wait(NULL))>0)
{
printf("wait pid %d success",id);
}
}
int main()
{
signal(17,hander);
pid_t id =fork();
if(id==0)
{
int count=4;
while(count>0)
{
count--;
cout<<count<<" "<<getpid()<<endl;
sleep(1);
}
exit(1);
}
while(true)
{
cout<<"I am father proce"<<endl;
sleep(1);
}
return 0;
}
结果:
|