1.信号------信号量(两者没有任何关系)
2.信号讲什么----->整个信号的生命周期
信号的产生-----信号的保存------信号的处理
之前的kill命令,用的就是信号。
kill -l查看系统支持的信号
名字本身就是宏,其实就是编号,我们在使用的时候,既可以使用名字也可以使用编号。
可以发现只有1~31和34~64个信号。
生活中的信号:发令枪,闹铃,红路灯,消息提醒,烽火台狼烟。
同步:老师 让我取快递,这时候上课了,老师说等我来了,再上课。
信号是给进程发的
进程本身是程序员编写的属性和逻辑的集合-----程序员编码完成的
进程收到信号的时候,进程可能正在执行更中要的代码,所以信号不一定会被立即处理
进程本身必须要有对信号保存的能力
进程处理信号的时候,一般有三种动作(默认,自定义,忽略){信号被处理被称为信号被捕获}
保存在task_struct中
保存是否收到了信号[1~31]
struct task_struct
{
.....
unsigned int signal;
}
发送信号的本质,就是修改task_struct(PCB)中的信号位图。
PCB的管理系统是内核的数据结构,
PCB的管理者OS有权利修改里面的内容,所以无论我们学习多少种发送信号的方式,本质都是通过OS向目标进程发送信号!!-----》OS必须要提供发送信号的系统调用接口。
kill命令底层一定调用了底层系统接口
ctrl +c热键-----本质就是一个组合键---》OS将ctrl+c解释为2号信号(SIGINT)
使用man 7 signal详细查看2号信号的具体工作
OS有发信号的能力,有能力不代表有使用他的能力,就比如你有写代码的能力,但你的老板在使用你的这种能力
当使用man 7 signal时,会出现Term和Core两种终止进程的行为
在a[100],a[1000]并没有发生报错。越界并不一定会使编译器报错,当在栈上创建变量的时候,栈开多大空间,你不知道,你只是使用了你所开的空间。给你分配的空间可能会比较大,所以越界,也可能在有效栈区内,不会报错。除非你访问不是你的空间。即你可能在不知情的情况修改一些你的数据。
Term代表正常结束,OS不做其他操作
Core代表不仅结束进程,OS还做其他操作
9号信号为管理员信号
当信号被置于pending位图中,就说明该信号处于未决状态
当信号被置于block位图中,就说明该信号处于阻塞状态
信号在OS内的被处理
所以我们可以得到signal(signo,handler)函数的本质就是:拿着信号编号signo,到指定的数组中找。将handler对应方法的地址填入表中。
后面当信号产生的时候,修改上面的pending表中的值,根据block表查看是否阻塞,如果没有阻塞,就进行处理。OS根据信号位置找到编号,根据编号找到对应到函数指针数组中函数的地址,调用方法去处理该信号。
每个进程都有自己独立的用户级页表,除此之外,OS内部还维护了一张内核级别页表,它是为了映射从虚拟到物理内存之间的OS的代码。在开机的时候,OS代码也会加载到内存,但是只有一份,所以内核级页表只有一份就够了。也可以理解为CPU有一个寄存器,对应着OS的内核级页表,进程切换的时候,该寄存器不变。
每一个进程都要有自己的地址空间(是独占的),内核空间(被影射到每一个进程的内核空间,占3~4G),所以要访问OS的接口,其实只需要在自己的地址空间上跳转就 可以了。本质就是,跳转到内核空间找到对应的地址,通过内核页表,找到内存中OS的代码,然后再返回到用户空间,进行继续执行。
每个进程都共享一个内核级页表,无论进程如何切换,都不会更改任何3~4G的内核空间。
只要要跳转的时候,更改一下CPU中的CR3寄存器的运行级别就可以了。
系统调用的接口,起始位置会帮你把CR3的值由3(用户态)改为0(内核态)
在Linux有一个终端编号,汇编指令int 80-----陷入内核,修改为内核态
信号产生的时候,不会被立即处理,而是在合适的时候,那么这个合适的时候是什么时候呢?
从内核态到信号态的时候,会被处理。-----》曾经我一定进入了内核态!
什么时候进入过内核态呢?
进程由用户态到内核态,好不容易来一次,肯定要干点事情,于是就找到,task_struct中的信号位图,开始按位处理信号,那么,我们能不能以内核态的身份,执行用户的代码呢?
答案是并不能,因为OS不相信任何人。所以当处理信号的时候,会通过特定的方法,将自己的身份重新更改为用户态在执行,执行完,通过特定的系统调用,跳转到内核,将所有信号处理结束,再跳转到用户态。
ABCD代表四次,用户切换。如果是默认或者忽略的时候,就走到信号的检查过程,就停止了,不再往后走了。
每个信号只有一个bit的未决标志,非0即1,不记录该信号产生了多少次,阻塞标志也是这样表示的。因此,未决和阻塞标志可以用相同的数据类型sigset_t来存储,sigset_t称为信号集,这个类型可以表示每个信号的“有效”或“无效”状态,在阻塞信号集中“有效”和“无效”的含义是该信号是否被阻塞,而在未决信号集中“有效”和“无效”的含义是该信号是否处于未决状态。下一节将详细介绍信号集的各种操作。 阻塞信号集也叫做当前进程的信号屏蔽字(Signal Mask),这里的“屏蔽”应该理解为阻塞而不是忽略。
#include <signal.h>
sigset_t set;//自定义信号集64bit 128bit
int sigemptyset(sigset_t *set);//清空,全设0
int sigfillset(sigset_t *set);//全设 1
int sigaddset (sigset_t *set, int signo);//添加一个信号
int sigdelset(sigset_t *set, int signo);//删一个信号
int sigismember(const sigset_t *set, int signo);//判断信号在吗
函数sigemptyset初始化set所指向的信号集,使其中所有信号的对应bit清零,表示该信号集不包含 任何有效信号。
函数sigfillset初始化set所指向的信号集,使其中所有信号的对应bit置位,表示 该信号集的有效信号包括系统支持的所有信号。
注意,在使用sigset_ t类型的变量之前,一定要调 用sigemptyset或sigfillset做初始化,使信号集处于确定的状态。初始化sigset_t变量之后就可以在调用sigaddset和sigdelset在该信号集中添加或删除某种有效信比特科技号。
这四个函数都是成功返回0,出错返回-1。sigismember是一个布尔函数,用于判断一个信号集的有效信号中是否包含某种 信号,若包含则返回1,不包含则返回0,出错返回-1。
这两个函数在第3.1和3.2。