发布时间:2023-04-26 08:30
kill -l
命令来查看所有的信号 #include
int kill (pid_t pid,int sig)
int raise(int sig);
int raise(int sig)
{
return kill(get(pid),sig);
}
回想之前在学习gdb调试的时候,有一种情况就是对崩溃后产生的coredump文件进行调试,进而确定程序崩溃的原因
现在我们可以通过对崩溃程序产生的coredump文件进行调试,通过信号值判断程序崩溃原因。
产生coredump文件的方式以及限制因素:
默认处理方式
SIG_DFL
在操作系统当中已经定义好信号的处理方式
忽略处理方式
SIG_ING 忽略处理
联想到僵尸进程的产生原因:
现在给出详细解释:
子进程先于父进程退出, 子进程在退出的时候会给父进程发送SIGCHLD
信号,而父进程接收到这个信号后,是忽略处理的,从而导致了父进程没有回收子进程的退出状态信息,因此子进程就变成了僵尸进程!
自定义处理方式
程序员可以更改信号的处理方式,定义一个函数,当进程收到该信号的时候,调用程序员自己写的函数。
概念
一个进程收到一个信号,这个过程称之为注册
信号的注册和信号的注销是两个独立的过程
内核中信号注册位图以及sigqueue队列的理解
2.1 task_struct
结构体内部有一个结构体变量 struct sigpending pending
,struct sigpending结构体有两个成员变量,一个是 struct list_head list
(双向链表),另一个是 sigset_t signal
(数组)
2.2 我们具体研究这个 sigset_t signal 数组:
sigset_t
本质上是一个结构体,它的内部成员变量是一个数组:unsigned long sig[_NSIG_WORDS]
对于这个数组,操作系统并没有将它当做数组来使用,而是把它看做是位图
2.3 在Linux的64位平台下,long 以及 unsigned long占8个字节,也就是64个bit位,而目前信号的数量只有62个,所以每个bit位表示一个信号是足够的
上面说了这么多,理解起来可能会有一点绕,下面我就通过图示的方式解释一下:
在理清楚他们之间的关系后,现在开始整整意义上的理解这个数组是如何被当做位图来使用的。
可能有人又有疑问了:
既然数组的一个元素就可以搞定,为何又大费周章,给一个数组呢?
原因是为了后续可能会扩展的信号提供空间
含义:让程序员自己定义某一个信号的处理方式,当进程收到该信号后就会执行程序员自定义的处理方式
sighandler_t signal(int signum,sighandler_t handler);
9号信号(强杀)是不能被程序员自定义处理的函数
测试代码:
2. sigaction函数+测试代码
int sigaction(int signum, const struct sigaction *act,struct sigaction *oldact)
后两个参数都是一个结构体指针,这里展开说明,该结构体的定义如下图:
下面对这五个成员变量进行说明:
测试代码:
情景一:自定义信号处理方式执行后手动结束进程
情景二:进程收到2号信号后先去执行自定义处理方式的函数,然后在该函数内部,再将2号信号的处理方式恢复为原来的方式
struct task_struct
{
........
sigset_t blocked;
.........
}
sigset_t blocked是一个位图,当要阻塞一个信号的时候,将该信号对应的比特位设置为1即可
int sigprocmask(int how,const sigset_t* set,sigset_t* oldset)
结论:9号和19号信号不能被阻塞
do_signal函数
处理信号,该函数会判断是否有信号并做出相应的操作:我们都知道,父进程回收子进程退出信息接收到的是SIGCHLD信号,因此我们可以将该信号的处理方式自定义一下,然后在接收到该信号的时候,再转去执行自定义处理方式里面的wait函数来回收子进程的退出状态信息。
通过上面的方式,我们就会“解放”父进程,让他在等待子进程退出的同时能够去执行其他代码!
情景二:使用volatile关键字。构造一个循环场景,通过信号执行自定义的信号处理函数改变相应的循环判断条件,观察现象
正常退出,原因是加了volatile关键字,因此每次都会从内存读取数据,说以值改变编译器每次都会读取到新的数据。