目录
信号入门
生活角度的信号
生活中有很多信号的场景,比如红绿灯,闹钟,信号枪,鸡叫声等等...,这些信号都是给人看的,如果这些信号脱离了人类,红绿灯给牛看,信号枪给鸟打,鸡给鸡叫,这些都是没有意义的。当这些场景触发的时候,我们人类立马就知道要做什么。那么是不是这些场景真正放在我们面前,我们才知道做什么呢?其实和场景触发没有直接关联。对于信号的处理动作,我们早就知道了,甚至远远早于信号产生。那么我们是怎么做到没有信号就知道该怎么做呢?我们对特定事件的反应,是被教育的结果,本质是我们记住了。
结论1:所以信号的产生是给进程看的,进程要在合适的时候,执行对应的动作,而进程在没有收到信号的时候,进程是知道对应进程应该怎么处理!那么进程是如何做到处理对应的信号呢?其实就是曾经编写操作系统的工程师在写进程代码的时候,就已经设置好了。
在生活中,我们受到某种”信号“的时候,并不一定立即处理,下课铃响了我们就能下课吗,可能老师没有讲完这个知识点;鸡叫了我们就一定要起床吗,我们仍旧想睡觉等等这种例子。所以信号随时都可能产生,但是进程当前可能做更重要的事情,这就造成了异步。
结论2:所以进程收到信号的时候,并不是立即处理的,而是在合适的时候去处理它。信号来的时候,进程说等一等,我先把手头的进程处理完,再去处理信号。
结论3:信号既然不能被处理,那么已经到来的信号,就被保存下来了。举个例子,当外卖员给你打电话,这时候是外卖到了的信号,但是可能因为我们正在写一个代码,刚有思路不能被打断,所以我们让信号等待,不去处理它,但是我们已经将信号保存下拉,记在心里了。那么进程的信号被保存放在哪里呢?信号放在了struct tash_struct进程控制块PCB中。PCB中包含了进程的所有属性信息,包括该进程是否收到信号以及到来的信号。信号的本质也是数据,信号发送给进程,就是往task_struct中写入数据。
结论4:task_struct是一个内核数据结构,用来定义进程对象的数据结构,但是内核不相信任何人,只相信子集,所以用户是不可能修改内核的task_struct的。所以向task_struct中写入信号的是OS操作系统!所以信号的任何发送,本质都是再底层通过OS发送的。
以下是信号要了解的三个步骤,我们逐步讲解
信号产生的各种方式
我们用kill -l查看系统支持的信号列表,发现它有64个,但是其中并没有31,32这两个信号,所以一共有62个信号。前31个信号是普通信号,后31个是实时信号。
当我们写一个死循环并执行的时候,我们要停止他可以用ctrl+c。当我们向进程写入ctrl+c的时候,实际上就是向目标进程发送2号信号。
在1-31信号中,除了有大写的名字之外,前面还带了个编号。在系统当中,信号编号就是整数,每一个信号都有它的宏值,可以理解成所有的信号就是被#define定义出来的,在使用信号的时候,既可以使用信号名,又可以使用数字。
那么怎么证明ctrl+c发送的是2号信号呢?
我们可以用一个函数signal证明,signal是一个函数,它的作用,可以修改指定的一个信号,它的类型是一个函数指针。
第一个参数是一个整数,就是用来传递信号的,第二个参数是用来修改信号的默认处理动作。我们可以将系统默认的信号操作,改成我们自定义对信号的操作。所以第二个参数传的是一个函数指针,只要传入一个函数的地址,那么我们就可以得到这个函数对信号的修改操作。我们知道如果只传函数名,不加后面的参数括号,那么就是传递函数的地址。
函数指针的返回值是void,参数是int。在下面的handler函数中,它是一个信号修改函数,因为函数指针的返回值是void,参数是int,所以handler的返回值是void,参数是一个int,用来接收signal传递的信号整数。
【测试】:
[wjy@VM-24-9-centos 7_signal]$ cat test.c
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
void handler(int signo)
{
printf("get a signal:signal no:%d,pid:%d\n",signo,getpid()); //打印信号树,并打印信号是给哪个进程发的
exit(1);
}
int main()
{
//通过signal注册对2号信号的处理动作,改成我们自定义的动作
//当我们收到2号信号时,2这个数字就会传递给handler函数。
signal(2,handler);//第二个参数是一个函数指针
while(1)
{
printf("hello world,pid:%d\n",getpid());//打印出进程号
sleep(1);
}
return 0;
}
//运行结果
[wjy@VM-24-9-centos 7_signal]$ ./mytest
hello world,pid:25208
hello world,pid:25208
hello world,pid:25208
^Cget a signal:signal no:2,pid:25208
通过运行结果可以发现,当执行死循环的时候,进程pid是21608。当我们发出ctrl+c发出2号信号,进程开始执行signal函数,调用handler函数,传入2号信号,并进行相应的修改,当每次ctrl+c时都会向signal传入2号信号,每一次ctrl+c都会调用handler函数。此时发现,ctrl+c确实是2号信号,而且对应的进程也是21608。这就证明了上面的问题,ctrl+c所对应的信号是2号信号。(ps:要退出这个进程ctrl+\)
那么signal本身是一个注册函数,当注册函数的时候,handler方法还没有被调用,只有当信号到来的时候,handler函数才会被调用。
信号处理常见方式概览
我们现在将上面代码改一下,当输入某个信号,就打印出信号对应的数字(在普通信号范围内)。
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#include <stdlib.h>
void handler(int signo)
{
printf("get a signal:%d\n",signo);
exit(1);
}
int main()