信号与信号量完全没有任何关系
信号入门
生活中还有没有信号的场景呢?
:闹钟,烽火台的烽火,鸡鸣声。。。—》都是给人看的
当我们面对这些场景的时候,我们就立马能够想到某些东西的时候,这些都是信号
信号的产生就代表上面的场景触发,
信号的产生—》信号是要给进程发的—》进程要在合适的时候,要执行对应的动作
但是,并不是这些场景真正放在我面前的时候,我才知道应该怎么做,这些场景是否被触发,没有直接的关系
因为对于信号的处理动作,我们早就知道了,甚至远远早于信号的产生
如:听的铃声响了,我们就知道下课了
我们怎么做到 呢?
我们对特定事件的反应,这是被教育的结果(本质是:我们这些都记住了)
- 所以进程在没有收到信号的时候,进程知道如何识别是什么信号,怎么处理这些信号
而之前在写OS 的时候,程序员就已经设置好了
进程具有识别信号并且处理信号的能力,远远早于信号的产生
在生活当中,我们收到某种信号的时候,并不一定被立即处理(因为信号随时都能产生,但是我当前可能做着更重要的事情,不去处理)
-
进程收到某种信号的时候,并不是立即处理的,而是在合适的时候才去处理,
-
进程收到信号之后,需要先将信号保存起来,以供在合适的时候进行对信号的处理
我们把这个信号放在进程 的PCB 里面,所以信号本质也是数据
信号的发送—>往进程task_struct 里面写数据 -
task_struct 是一个内核数据结构,定义进程对象,内核不相信任何人,只相信自己!只有OS 可以往task_struct 里面写入数据(无论我们的信号如何发送,本质上都是在底层通过 OS.进行传输的)
信号产生的各种方式(信号产生前)
demo
kill -l可以查看信号列表
前31个信号是常规信号,后31个是实时信号
键盘ctrl+c的时候,本质上就是我们向进程里面发送2号信号SIGINT
signal
signal:可以修改进程对信号的默认处理工作
第一个参数就是我们的信号对应的宏值,我们既可以使用kill -l信号前面的数字,也可以使用宏
typedef void (*sighandler_t)(int);
函数指针,返回值为void,参数为int
#include <unistd.h>
#include<stdio.h>
#include<signal.h>
void handler(int signo)//函数名就是我们目标函数的地址,这里的signo对应的就是信号数值
{
printf("get a signal: signo:%d\n,pid:%d\n",signo,getpid());
}
int main()
{
//我们可以通过signal注册对2号信号(终止)处理动作,改成我们自定义的动作
signal(2,handler);//我们对2号信号进行处理
//注册的时候,并不会调用这个函数,只有当信号到来的时候,这个函数才会被调用
while(1)
{
printf("hello world\n,pid: %d\n",getpid());
sleep(1);
}
return 0;
}
我们发现我们ctrl c 或者kill 2 的话都会处理我们自己对信号的处理
我们发现信号的产生方式其中一种是通过键盘产生的
但是键盘产生的信号只能终止前台进程,不能处理后台进程,只能杀掉前台进程
但是我们可以
kill -9 pid
杀掉后台进程
信号处理方式概述
总结:
一般而言,收到信号的处理方式有3种
- 默认动作:终止自己(ctrl c)暂停(ctrl z)等
- 忽略动作:是一种信号处理的方式,只不过动作就是什么也不干,
- (信号的捕捉)自定义动作:类似我们刚刚用signal方法,就是在修改信号的处理动作,有:默认–》 自定义动作,我们可以对不同的信号设置不同的响应动作
void handler(int signo)//函数名就是我们目标函数的地址
{
switch(signo)
{
case 2:
printf("hello bite %d",signo);
break;
case 3:
printf("hello world %d",signo);
break;
case 9:
printf("quit");
break;
}
//printf("get a signal: signo:%d\n,pid:%d\n",signo,getpid());
//exit(1);
}
9号信号无法被捕捉
信号产生的方式
1
我们通过键盘对进程发送信号
ctrl c ,ctrl d, ctrl z
2
程序中存在异常问题,导致我们收到信号退出
进程崩溃的时候我们最想知道什么
当然是崩溃的原因,我收到了信号,waitpid(),status可以获取退出信号
我要解决他
崩溃的话就可以用这个方法去运行
我们得知道在哪一行崩溃了
- 在linux中当一个进程退出的时候,他的退出码和退出信号都会被设置(正常情况下)
- 当一个进程异常退出的时候,进程的退出信号会被设置,表面当前进程退出的原因
- 如果必要,OS 会设置退出消息中的core dump标志位,并将进程在内存中的数据传输到磁盘当中,方便后续调试
默认情况下在云服务器上core dump是被关掉的
我们这个core dump放开
ulimit -c 10240
放开之后会产生core dump文件
如果进程异常退出的时候,被core dump设置为1
不一定是所有信号都会被core dump
11号信号SIGSEGV
void sigtest()
{
while(1)
{
int *p=NULL;
p=(int*)100;
*p=100;//对指针解引用,指向空间为NULL,我们是不可以写入的,进程的崩溃
}
}
SIGSEGV段错误----》进程崩溃是因为接收到了11号信号而崩溃的,
8号信号SIGFPE
while(1)
{
// int *p=NULL;
// p=(int*)100;
// *p=100;//对指针解引用,指向空间为NULL,我们是不可以写入的,进程的崩溃
int a=10;
a/=0;
}
浮点数错误
在win or linux下进程崩溃的本质是进程收到了对应的信号,然后进程处理信号的默认动作(杀死进程)
但是为什么会收到信号呢?
3
kill
给别人发信号
通过系统调用产生信号
可以向特定进程发送特定信号
#include<stdio.h>
#include<sys/types.h>
#include<signal.h>
#include<stdlib.h>
void usage(const char* proc)
{
printf("Usage:\n \t,%s,sign who\n",proc);
}
int main(int argc,char*argv[])
{
if(argc!=3)
{
usage(argv[0]);
return 1;
}
int signo=atoi(argv[1]);
int who=atoi(argv[2]);
kill(who,signo);
printf("%d %d",signo,who);
return 0;
}
raise
给自己发信号
while(1)
{
printf("1");
sleep(3);
raise(8);//给自己发一个8号信号
}
4
通过软件条件,也能产生信号
alarm相当于一个闹钟
在seconds秒之后给我们发送一个14号信号
返回值为0,或剩余的秒数
#include<unistd.h>
#include<stdio.h>
#include<signal.h>
#include<stdlib.h>
int count=0;
//统计一下1s,我们的server可以对int递增到多少
void HandAlarm(int signo)
{
printf("%d\n",count);
exit(1);
}
int main()
{
signal(SIGALRM,HandAlarm);//我们对14号信号进行注册一下
//设置闹钟
alarm(1);//我们没有设置alarm信号的捕捉动作(没有自定义调用signal函数),执行默认动作
while(1)
{
count++;
//printf("hello %d\n",count++);
}
return 0;
}
永远都是操作系统发送的信号
那么我们怎么理解OS给进程发送数据,task_struct ----->本质是,OS向执行进程的task_struct 中的信号位图写入比特位1,即完成了信号的发送–>信号的写入
信号的编号是有规律的[1,31]
struct task_struct
{
//进程的各种属性
//进程内部一定要有对应的数据变量,来保存是否收到了对应的信号,
}
我们用什么数据变量来标识是否收到了信号,
uint32_t sigs:一定会存在位图结构
0000 0000 0000 0000 0000 0000 0000 0000
我们认为左大,右边第一个是第一个比特位,
所谓的比特位的位置(第几个),代表的就是哪一个信号,比特位的内容(0,1)代表的就是是否收到了信号
0000 0000 0000 0000 0000 0000 0000 0101
当前进程收到了1号信号,3 号信号,
在进程中采用位图,来标识该进程是否收到信号,