信号产生前

文章目录

信号与信号量完全没有任何关系

信号入门

生活中还有没有信号的场景呢?
:闹钟,烽火台的烽火,鸡鸣声。。。—》都是给人看的
当我们面对这些场景的时候,我们就立马能够想到某些东西的时候,这些都是信号
信号的产生就代表上面的场景触发,
信号的产生—》信号是要给进程发的—》进程要在合适的时候,要执行对应的动作

但是,并不是这些场景真正放在我面前的时候,我才知道应该怎么做,这些场景是否被触发,没有直接的关系
因为对于信号的处理动作,我们早就知道了,甚至远远早于信号的产生
如:听的铃声响了,我们就知道下课了

我们怎么做到 呢?
我们对特定事件的反应,这是被教育的结果(本质是:我们这些都记住了)

  1. 所以进程在没有收到信号的时候,进程知道如何识别是什么信号,怎么处理这些信号
    而之前在写OS 的时候,程序员就已经设置好了
    进程具有识别信号并且处理信号的能力,远远早于信号的产生

在生活当中,我们收到某种信号的时候,并不一定被立即处理(因为信号随时都能产生,但是我当前可能做着更重要的事情,不去处理)

  1. 进程收到某种信号的时候,并不是立即处理的,而是在合适的时候才去处理,

  2. 进程收到信号之后,需要先将信号保存起来,以供在合适的时候进行对信号的处理
    我们把这个信号放在进程 的PCB 里面,所以信号本质也是数据
    信号的发送—>往进程task_struct 里面写数据

  3. 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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#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 的话都会处理我们自己对信号的处理

我们发现信号的产生方式其中一种是通过键盘产生的
在这里插入图片描述
但是键盘产生的信号只能终止前台进程,不能处理后台进程,只能杀掉前台进程
但是我们可以

1
kill -9 pid

杀掉后台进程

信号处理方式概述

总结:
一般而言,收到信号的处理方式有3种

  1. 默认动作:终止自己(ctrl c)暂停(ctrl z)等
    
  2. 忽略动作:是一种信号处理的方式,只不过动作就是什么也不干,
    
  3. (信号的捕捉)自定义动作:类似我们刚刚用signal方法,就是在修改信号的处理动作,有:默认–》 自定义动作,我们可以对不同的信号设置不同的响应动作
    
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

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可以获取退出信号

我要解决他
崩溃的话就可以用这个方法去运行

我们得知道在哪一行崩溃了

  1. 在linux中当一个进程退出的时候,他的退出码和退出信号都会被设置(正常情况下)
  2. 当一个进程异常退出的时候,进程的退出信号会被设置,表面当前进程退出的原因
  3. 如果必要,OS 会设置退出消息中的core dump标志位,并将进程在内存中的数据传输到磁盘当中,方便后续调试

默认情况下在云服务器上core dump是被关掉的
在这里插入图片描述
我们这个core dump放开
ulimit -c 10240

在这里插入图片描述
放开之后会产生core dump文件

在这里插入图片描述

如果进程异常退出的时候,被core dump设置为1

不一定是所有信号都会被core dump

11号信号SIGSEGV

1
2
3
4
5
6
7
8
9
void sigtest()
{
while(1)
{
int *p=NULL;
p=(int*)100;
*p=100;//对指针解引用,指向空间为NULL,我们是不可以写入的,进程的崩溃
}
}

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

SIGSEGV段错误—-》进程崩溃是因为接收到了11号信号而崩溃的,

8号信号SIGFPE

1
2
3
4
5
6
7
8
9
while(1)
{
// int *p=NULL;
// p=(int*)100;
// *p=100;//对指针解引用,指向空间为NULL,我们是不可以写入的,进程的崩溃
int a=10;
a/=0;

}

浮点数错误
在这里插入图片描述
在win or linux下进程崩溃的本质是进程收到了对应的信号,然后进程处理信号的默认动作(杀死进程)
但是为什么会收到信号呢?

在这里插入图片描述

3

kill

给别人发信号
通过系统调用产生信号
在这里插入图片描述
可以向特定进程发送特定信号

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#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

给自己发信号

1
2
3
4
5
6
while(1)
{
printf("1");
sleep(3);
raise(8);//给自己发一个8号信号
}

在这里插入图片描述

4

通过软件条件,也能产生信号
alarm相当于一个闹钟

在这里插入图片描述
在seconds秒之后给我们发送一个14号信号
在这里插入图片描述
返回值为0,或剩余的秒数

在这里插入图片描述

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#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]

1
2
3
4
5
6
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 号信号,

在进程中采用位图,来标识该进程是否收到信号,


信号产生前
http://example.com/2022/05/02/信号产生前/
作者
Zevin
发布于
2022年5月2日
许可协议