目录
1.预备
2.信号如何产生
1.引入
2.原理
3.总结
3.接口
1.singal函数
2.kill函数
3.raise函数(给自己发信号)
4.abort函数(给自己发送6号信号)
4.异常
1.现象
2.原理
5.core和term区别
6.由软件条件产生信号
3.信号如何保存
1.原理
2.接口
3.代码运用
4.信号的处理
1.信号是什么时候被处理的?
1.重新谈地址空间:
2.操作系统的本质
3.用户态和内核态的切换
1.预备
1.进程必须能够识别+处理信号,也要具备处理信号的能力,这是属于进程内置功能的一部分。
(也就能说明进程即便没有收到信号,也知道哪些信号是如何被处理的!)
2.当进程真的收到这个信号,进程可能并不会立即处理这个信号。
3.当一个信号产生到信号开始被处理,就一定会有时间窗口,进程具有临时保存信号以及哪些信号被处理的能力
2.信号如何产生
1.引入
ctrl+c为什么能够杀死我们的进程?本质是接受到了我们的2号信号:
我们一般只用到前31的信号,称为普通信号(有时间窗口,不一定收到信号后要立即处理,可存储一定时间)。后面的信号是实时信号,就是收到就要理科处理的信号。
但是如果我们./process &
linux中,一次登陆中,一个终端一般会配上一个bash,每一个登陆,只允许一个进程是前台进程,可以允许多个进程是后台进程----相当于只有前台进程可以获取键盘的输入。
2.原理
键盘是如何输入给内核的?ctrl+c又是如何变成信号的?
键盘被摁下,肯定是OS先知道的,那OS怎么知道键盘上有数据了?cpu不可能不停的就去检测键盘,有没有去写东西给文件,这样太浪费。而是键盘写入文件时,直接给cpu发送中断,(根据冯诺依曼结构说,cpu不可以直接访问键盘文件,但是键盘可以给cpu发送数据,因为cput上面有很多引脚,是直接连接这cpu的)然后cpu通过接送到的数据到内存的中断向量表中寻找读取键盘的方法,然后将键盘上的数据读取到键盘(文件)的缓冲区中,然后就是read等函数的事情了!
3.总结
信号产生的方式:无论信号如何产生最终一定是谁发送给进程的?OS!为什么?OS是进程的管理者!
3.接口
1.singal函数
第二个handler就是函数指针,实现接受到该信号的时候,进程该做什么。(但是并不是所有的信号都可以被修改)
这样我就可以通过这个函数中的ctrl是不是产生2好信号!
就有人好奇了?在signal函数的第一个参数就知道是几号信号了!为什么函数指针要加这个参数,因为这个函数指针不一样只代表一个信号的函数指针,里面可能有其他信号的处理方式switch函数方式!
前31个信号只有9号和19号信号是不能通过signal改变,因为一个是杀死进程和暂停进程,如果可以修改的话,那病毒一些东西不就可以执行了吗?
2.kill函数
3.raise函数(给自己发信号)
ctrl + \相当于3号信号
4.abort函数(给自己发送6号信号)
可以看出abort()函数里面不仅有6号的信号!
4.异常
1.现象
但是如果我们通过signal函数修改改信号:
2.原理
而每次调度改进程,该溢出标志位都是1,OS就会发送给pcb中断信号,但是我们自己设计的信号处理方式没有退出,然后就调用其他进程,再次调用该进程时,还是会这样,所以只能死循环!
其实野指针也是一样的:
但是OS是如何识别是溢出还是访问越界呢?其实就是寄存器的不同给OS识别的!
上面这两个都是cpu硬件产生异常,给OS识别到的,有没有软件条件?---管道
但不是所有的异常都会将进程给杀掉:
5.core和term区别
man 7 signal :
还记得父进程要等待子进程结束的waitpid函数吗?
进程等待-wait和waitpid-CSDN博客
而这上面的core dump就和这个有关!
可是core dump都是0啊,其实和下面有关:
可以将其修改(你如果再改回去,再改的话就不可以,应该是OS不允许这么随意修改吧。)
打开系统的core dump功能,一旦进程出异常,OS会将进程在内存中的运行信息,给我dump(转储)到进程的当前目录(磁盘)形成core.pid文件:核心转储(core dump)
而该core.pid文件可以存储哪一行代码出问题了:
可为什么系统要将这个core给删除掉呢?其实你看到到core.pid文件的大小便会明白了,很大。就想异常一开始的现象,如果一直像那样死循环,那整个系统岂不是可能会崩掉?得不偿失!
6.由软件条件产生信号
SIGPIPE是一种,SIGALRM也是一种:
也可以让他一直发送:
返回值:当一个alarm函数没有到时间,但收到了该信号,返回的就是上一个alarm还剩下多少秒。
3.信号如何保存
1.原理
基础概念:
在进程task_struct结构中存在两个整数,一个数组,block和pending自然是整形,用位图来表示每个信号的。block就是是否屏蔽某个信号,pending则表示是否收到该信号,handler就是收到该信号的处理方法!其中SIG_DFL(default)就是默认处理方法,SIG_IGN(ignore)就是忽略的处理方法,User Space就是自定义了。
一样和上面myhandler一样是函数指针:
2.接口
上面得知这些信号存储在位图中,操作系统肯定不会让我们直接去改变位图的,因为操作系统不相信我们,这肯定的!所以,就提供给我们一个结构体,然后通过函数来初始化他们!
当运用玩这些函数,至此对操作系统的信号位图是一点都没有触碰到呢!
相当于修改上面的block的位图:
int how:SIG_BLOCK(增加该信号屏蔽他,mask = set | mask)SIG_UNBLOCK(删除,mask = mask & ~set, SIG_SETMASK,直接覆盖)
oldset则是修改前set拷贝给oldset。
获取当前pending中的位图,再运用的上面的sigismember函数就能得知该进程是否接受到了该信号了!
改变信号的处理方法,上面已经说过了:signal
3.代码运用
一样,如果全屏蔽掉,那岂不是不能收到信号?和上面一样9和19好信号不可以收到!
4.信号的处理
1.信号是什么时候被处理的?
当我们的进程从内核态返回到用户态的时候,进行信号的检测和处理。(比如:调用系统调用--操作系统是自动会做“身份”切换的,用户身份变成内核身份,或者反着来;int 80 从用户态陷入内核态(下面会说))
1.重新谈地址空间:
用户页表:有几个进程,就有几个用户级页表---进程具有独立性
内核级页表:只有1份
每个进程看到的3-4GB(内核空间)是一样的,整个系统中,进程再怎么切换,3,4GB空间的内容是不变的!!!
进程视角:我们调用系统中的方法,就是在自己的地址空间中执行的。
操作系统视角:任何一个时刻,都有进程执行。我们想执行操作系统的代码,可以随时执行。
2.操作系统的本质
基于时钟中断的一个死循环。
其实我们应该想过,我们修改这些位图的信号,那进程怎么知道的?OS告诉它的,那OS又怎么知道的?
计算机硬件中,有一个时钟芯片,每个很短的时间,向计算机发送时钟中断!
3.用户态和内核态的切换
但是有人回想,那我们正常程序,就打印点东西,循环,不用系统调用函数,也进入不了内核态啊。cpu在运行进程时是一个一个进程单独运行的,不可能同时运行两个进程,所以进程都是先跑一会,给其他进程跑一会。当这个进程第二次被调度时,从就绪队列转换成运行队列,操作系统把该进程的PCB,寄存器,页表等等放到cpu上,则肯定时内核态,但是运行你普通代码时,肯定是用户态----这不是转换了嘛!