进程信号(信号、信号的注册与注销、信号的处理方式)

信号的概念

信号是一个软件中断

信号的产生

硬件产生

ctrl + c:SIGINT(2)
ctrl + z:SIGTSTP(20)
ctrl + |:SIGQUIT(3)

操作系统对信号的处理动作:man 7 signal 查看
在这里插入图片描述

term:终止
ign:忽略
core:终止并产生coredump文件
stop:停止
cont:继续运行

如果产生下面的错误可以去看看这个博客,下载后就可以解决了
https://blog.csdn.net/qq_28779503/article/details/54893745
在这里插入图片描述
信号具体的信息:信号的名称+信号的值(整数)+action+描述
在这里插入图片描述

软件产生

kill命令:
	kill [pid] 可以终止一个进程
	kill -[num] [pid] 给进程号为 pid 的进程发送一个信号值为 num 的信号

kill函数:
	int kill(pid_t pid, int sig);
	功能:给 pid 进程发送 sig 信号
	需要包下面的头文件:
    	#include <signal.h>
  1 #include <stdio.h>
  2 #include <unistd.h>
  3 #include <signal.h> 
  4                                       
  5 int main()                            
  6 {                                     
  7   printf("-----begin-----\n");
  8   kill(getpid(), 9); 
  9   printf("------end------\n");        
 10   return 0;                           
 11 }

在这里插入图片描述

raise函数:
	int raise(int sig);
	功能:谁调用给谁发送 sig 信号
  1 #include <stdio.h>
  2 #include <unistd.h>
  3 #include <signal.h>
  4 
  5 int main()
  6 {
  7   printf("-----begin-----\n");
  8   raise(2);
  9   kill(getpid(), 9);
 10   printf("------end------\n");
 11   return 0;
 12 }

在这里插入图片描述
说明此时在 raise 就结束掉了,并没有走到 kill

信号的种类:

目前linux的信号数量为62个,分为两种类型
	1、非实时信号(非可靠信号)    1~31
	特点:有可能信号会丢失
	2、实时信号(可靠信号)    34~63
	特点:信号不会丢失

信号的注册

注册:一个进程收到一个信号的过程称为注册

信号的注册和信号的注销并不是一个过程,是两个独立的过程

信号的注册分为两种情况:非可靠信号的注册和可靠信号的注册

1、在操作系统内核“struct task_struct”结构体内部有一个变量:
		“struct sigpending pengding”
		
2、内核定义的结构体“struct sigpending”当中有两个变量
	一个是内核定义的双向链表
	第二个是:sigset_t signal
	
3、内核定义的类型“sigset_t”为一个结构体,在结构体内部有一个变量,该变量为一个数组(无符号长整型数组)
	unsigned long sig[_NSIG_WORDS];

信号的注册本质上就是在使用sig数组,但是并不是按照数组类型的方式在使用,而是按照位图(比特位)的方式在使用:
	eg:某个信号注册,则将某个信号队友的比特位置为1
	sig数组的比特位远远大于62,剩下的比特位为保留位

struct sigpending ⇒ sigset_t signal ⇒ sig[xxx]
一般在信号注册的时候,称之为操作sig位图,即sig[xxx]

内核当中对于注册的时候,还有一个sigqueue队列,信号的注册逻辑为将信号队友的sig位图当中的比特位置为1,并且在sigqueue队列当中
添加一个sigqueue节点

通过注册第一个信号两次,来区分可靠信号和非可靠信号的注册逻辑

非可靠信号的注册

第一次:
	1、更改信号对应在sig位图当中的比特位(0 --> 1)
	2、在sigqueue队列当中添加sigqueue节点

第二次:
	1、更改信号对应的sig位图当中的比特位(1 --> 1)
	2、对于第二次的信号,不添加sigqueue节点到sigqueue队列当中

如果有多次同一个信号来注册,对于非可靠信号而言,只会添加一次sigqueue节点,换言之,只注册了一次

可靠信号的注册

第一次:
	1、更改信号对应在sig位图当中的比特位(0 --> 1)
	2、在sigqueue队列当中添加sigqueue节点

第二次:
	1、更改信号对应的sig位图当中的比特位(1 --> 1)
	2、再次在sigqueue队列当中添加sigqueue节点

如果有多次同一个信号来注册,对于可靠信号而言,会添加多次sigqueue节点,即注册多次

信号的注销

非可靠信号的注销

1、将信号对应到sig位图当中的比特位置为0
2、将对应的非可靠信号的sigqueue节点进行出队操作

可靠信号的注销

1、先将可靠信号对应的sigqueue节点进行出队操作
2、判断sigqueue队列当中是否有同类的可靠信号的sigqueue节点
	如果有:不会将sig位图当中对于的比特位置为0
	如果没有:将sig位图当中对于的比特位置为0

信号的处理方式

默认处理方式

在操作系统内核当中已经定义好了

宏:SIG_DFL

忽略处理方式

操作系统定义进程收到某个信号后忽略掉(进程即使收到了某个信号,进程也不会做任何事情)

宏:SIG_IGN

僵尸进程:子进程先于父进程退出,子进程会向父进程发送SIGCHLD信号,父进程对SIGCHLD信号是忽略处理的,所以父进程并不会做任何事情,导致子进程的资源没有进程进行回收,从而子进程变成僵尸进程

自定义处理方式

程序员可以定义某个信号的处理方式

函数

typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
signum:待要更改的信号的值
handler:函数指针,接收一个函数的地址,这个函数没有返回值,有一个int类型的参数

自定义signum这个信号的处理方式,定义为handler这个函数指针保存的函数地址对应的函数
换句话说,当进程收到signum这个信号的时候,就会调用handler当中保存的函数
1 #include <stdio.h>
  2 #include <unistd.h>
  3 #include <signal.h>
  4 
  5 void sigcallback(int signo)
  6 {
  7   printf("i am sigcallback, signo is %d\n", signo);
  8 }
  9 
 10 int main()
 11 {
 12   signal(SIGINT, sigcallback);
 13 
 14   while(1)
 15   {
 16     printf("i am main\n");
 17     sleep(1);                   
 18   }                             
 19   return 0;                     
 20 } 

在这里插入图片描述

signal函数向内核注册了一个信号的处理函数,调用signal函数的时候,并没有调用注册的函数(注册的函数在进程收到信号之后才调用),
这种方式被称为“回调”
int sigaction(int signum, const struct sigaction *act,struct sigaction *oldact);

struct sigaction {
               void     (*sa_handler)(int);//默认的信号处理函数保存的函数指针
               void     (*sa_sigaction)(int, siginfo_t *, void *);
               //↑函数指针,要配合sa_flags一起使用,当sa_flags当中的值为SA_SIGINFO的时候,
               //信号处理是按照“sa_sigaction”当中保存的函数地址来处理的
               sigset_t   sa_mask;
               //当进程在处理某一个信号时,有可能还会收到其他的信号,此时其他的信号暂时存放在sa_mask中
               int        sa_flags;
               void     (*sa_restorer)(void);//保留字段
           };
signum:待要自定义处理的信号
act:要将信号处理方式更改为act
oldact:原来的处理方式
  1 #include <stdio.h>
  2 #include <unistd.h>
  3 #include <signal.h>
  4 
  5 void sigcallback(int signo)
  6 {
  7   printf("i am sigcallback, signo is %d\n", signo);
  8 }
  9 
 10 int main()
 11 {
 12   struct sigaction sa;
 13   sa.sa_handler = sigcallback;
 14   //int sigemptyset(sigset_t *set);
 15   sigemptyset(&sa.sa_mask);
 16   sa.sa_flags = 0;//默认就等于0
 17 
 18   sigaction(2, &sa, NULL);
 19 
 20   while(1)
 21   {
 22     printf("i am main\n");
 23     sleep(1);
 24   }
 25 
 26   return 0;
 27 }

在这里插入图片描述

信号的捕捉流程

信号注册在进程的PCB当中,是操作系统和PCB打交道

捕捉流程指的是信号什么时候进行处理

信号的处理是在内核态完成的

1、从内核态切换回用户态的时候,一定会调用do_signal函数,来处理进程收到的信号
	①sig位图当中有信号注册,则执行信号注销的逻辑
	②sig位图当中没有信号注册,则直接返回用户态

2、处理信号(信号注销)
	①默认处理方式(直接在操作系统内核的代码中就完成了)
	②忽略处理方式(直接在操作系统内核的代码中就完成了)
	③自定义处理方式(调用程序员自己定义的代码)

3、自定义处理方式
	①在用户态执行程序员自己定义的函数
	②调用sigreturn函数再次回到操作系统内核
	③再次调用do_signal函数判断是否有信号注册,如果有,返回到处理信号
	④调用sys_sigreturn函数回到用户态继续去执行代码

信号的阻塞

信号的阻塞,不会影响信号的注册

接口:

int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
how:想让sigprocmask函数做什么
	SIG_BLOCK:设置某个信号为阻塞状态
	SIG_UNBLOCK:设置信号为非阻塞状态
	SIG_SETMASK:设置新的block位图
set:使用set去设置block位图
	SIG_BLOCK:
		block(new) = block(old)| set
		eg:block(old):0101 0000
				    set:0000 1000
				    ==》 0101 1000
	SIG_UNBLOCK:
		block(new) = block(old)& (~set)
		eg:block(old):0101 0000
					&
				   ~set:1011 1111
				   ==》  0001 0000
	SIG_SETMASK:
		block(new) = set
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值