目录
什么是信号?
为什要有信号呢?
进程接受信号的过程
1.信号的产生
1.1kill命令产生信号
1.2键盘产生信号
1.3系统调用接口
1.3.1killl()
1.3.2raise()
1.3.3abort()
1.4软件条件
1.5异常
1.6对各种情况产生信号的理解
1.6.1kill命令
1.6.2键盘产生信号
1.6.3异常
除零错误
野指针
信号和信号量是两个完全不相干的技术,不要混淆。
什么是信号?
Linux让系统提供让用户给其他进程异步发送消息的一种方式。
站在进程的角度看待信号
1.信号在没被发送之前,进程是认识这个信号。
2.在接受到信号之后,进程是被设定好去怎么处理这个信号的。
3.信号到来的时候,如果进程有更重要的事情要做,这个信号会被临时保存。
4.收到信号之后可以不立即处理,等到机会合适在处理信号。
5.信号产生是不定时,随时都可能接受到信号,所以信号是异步发送的,是别的用户和进程。
为什要有信号呢?
操作系统要求进程有对外部信号响应的能力。(例:该进程发生除0错误,这个时候操作系统就会给进程发信号,让其停止)。
进程接受信号的过程
1.信号的产生
信号是如何产生的呢?
1.1kill命令产生信号
1-34都是标准信号,34之后的是实时信号。
其中每个数字都是一个宏,使用kill命令可以 kill -9 也可以 kill -SIGKILL
1.2键盘产生信号
ctrl + c 和 ctrl + \
信号捕捉:当我们向进程发送一个信号,进程接受信号后,会做出系统设定好的动作,
捕捉信号就是将这个信号的默认动作,改为自定义的动作。
signal(2,handler);
第一个参数就是要捕捉的信号,第二个参数为int,返回之为void的函数指针,捕捉到信号之后,就执行handler函数。
demo代码
大概意思就是进程收到2号信号时,会把2号信号的默认动作替换为handler函数。
#include <iostream>
#include <cerrno>
#include <cstring>
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
#include <signal.h>
using namespace std;
void handler(int sig)
{
cout<<endl;
cout<<"get a signal ,number is:"<< sig <<endl;//打印捕捉到的信号
exit(0);
}
int main()
{
signal(2,handler);//捕捉2号信号
while(true)
{
;
}
return 0;
}
把程序跑起来,然后ctrl + c,进程果然接受到了2号信号。
ctrl + \ 会对进程发送3号信号 SIGQUIT
采用同样的方法,只不过把捕捉信号2,换成捕捉信号3。
1.3系统调用接口
1.3.1killl()
这个不是命令,是系统调用接口
kill()系统调用接口,能被用于发送任意信号,给任意的进程。
参数pid就是要发送信号进程的pid,sig就是发送几号信号。
成功返回0,失败-1被返回,适当的错误码被设置。
1.3.2raise()
对当前进程发送信号
成功返回0,失败返回非0.
1.3.3abort()
终止进程。
1.4软件条件
我的理解就是,软件触发了某种条件,导致需要操作系统介入发送信号。
例:管道写端关闭时,读端就会收到13号信号
现打开服务端,再打开客户端,客户端向管道写入,服务端从管道中读取。
关闭读端,看写端收到的信号。
果然收到了13号信号。
alarm(警报)
参数seconds:在多少秒之后警报会响起。
发送SIGALRM信号给调用进程,在参数seconds秒。
如果参数seconds是0,任何还未响应的警报会被删除。
在任何情况下,之前设置的警报都会被取消。
返回之前设定好警报的剩余时间,如果之前没设置返回0.
demo代码:设置一个5秒的警报
void handler(int sig)
{
cout<<"get a signal, number:"<< sig <<endl;
exit(0);
}
int main()
{
alarm(5);
signal(14,handler);
int cnt = 5;
while(cnt)
{
cout<<cnt<<endl;
sleep(1);
cnt--;
}
return 0;
}
5秒之后收到14号信号。
1.5异常
这里就不验证了
例:除0错误 8)SIGFPE
野指针 11) SIGSEGV
1.6对各种情况产生信号的理解
1.6.1kill命令
命令本身就是一个可执行的程序,本质就是使用了系统调用接口kill,让操作系统发送信号。
1.6.2键盘产生信号
操作系统怎么知道键盘输入了什么数据呢?
难道轮训的去查看键盘输入吗?这样效率也太低下了。
首先计算机启动时,会创建一个中断向量表,这是一个函数指针数组。
cpu是有很多针脚的,每个针脚都有自己的编号,其中键盘是直接与cpu2号针脚相连。
当键盘输入的时候,就会发生硬件中断,2号针脚就会产生高电平,操作系统拿着这针脚号,去中断向量表中,执行对应函数就可以读取到键盘的数据了。
读取键盘数据之后,要判断一下,如果是组合键,就执行对应的命令,如果是字符,就向对应文件的缓冲区写入。
1.6.3异常
除零错误
demo代码
void handler(int sig)
{
cout<<"/0 error"<<endl;
}
int main()
{
signal(8,handler);
int a = 10;
int b = a/0;
return 0;
}
这个代码有个奇怪的现象,运行起来发现cout<<"/0 error"<<endl;会一直打印,这是为什么呢?
一个原因是我捕捉之后没有退出,即使不退出也不应该循环打印。
进程将10写入eax中,0写入ebx中,eax和ebx相除,发生除零错误(硬件上的错误),发生错误之后,溢出标志位会被设置为1,这时候os看见cpu出问题了,就给进程发送了 SIGFPE 让其停止。
cpu中是有很多组数据的,liunx是分时操作系统,cpu会在不同的进程来回切换,每当切换到除零错误这个进程,os一看出现错误,就会发送一次信号,切换到一次就会发送一次信号。
野指针
demo代码
void handler(int sig)
{
cout<<"pointer error"<<endl;
exit(0);
}
int main()
{
signal(11,handler);
int * p = nullptr;
*p = 100;
return 0;
}
首先eax拿到0号地址,发现要对0号地址进行写入,mmu就拿着0号地址,和cr3中的页表起始地址,进行转化,发现根本转化不了,因为0号地址的区域是只读的,或者访问的区域压根没到0号地址,就会将出错的地址储存在cr3中,os一看不对劲,赶紧给进程发SIGSEGV信号,让进程停止。