Linux进程信号

什么是信号

生活中的信号:闹钟,红绿灯,鸡叫声,,,,,,
这些场景触发的时候我们就知道自己该做什么了。 在信号产生之前,我们也知道遇到什么信号该做什么事情。
同样的,我们受到信号以后也不一定马上会去执行,也有可能信号发出来了,我们收不到的情况。
例如闹钟在客厅响了,你戴着耳机在卧室和女朋友一起愉快的开黑玩游戏,这时候你就听不到信号,听到了也不想管。

同样的信号产生后,是OS给进程发送的,向进程内核数据结构task_struct写入信号数据
同样的道理,进程收到信号以后也像我们人一样收到信号。 未收到的时候知道该如何处理,不立即处理,暂时保存信号数据,
到时候再处理,也有可能不处理或者收不到信号

也就是进程处理信号的方式:
1,忽略此信号。
2,执行该信号的默认处理动作。
3,提供一个信号处理函数,要求内核在处理该信号时切换到用户态执行这个处理函数,这种方式称为捕捉
(Catch)一个信号。

如何产生信号:
1,键盘产生(只作用于前台进程,./test & 就变成了后台进程)
后台程序基本上不和用户交互,优先级别稍微低一点
前台的程序和用户交互,需要较高的响应速度,优先级别稍微高一点
2,程序中的异常问题,导致进程受到信号而退出
3,通过系统调用去产生信号
4,软件条件产生信号。(例如在匿名管道中,读端关闭,写端收到13信号后关闭)

例如进程在死循环的时候,我们在键盘上输入ctrl+c,实际上就是通过OS给该进程发送了2号信号
在这里插入图片描述

kill -l 可以查看信号对应的编号信息。
在这里插入图片描述

man 7 signal 可以查看受到编号信号后进程的默认动作。
在这里插入图片描述

signal函数的功能(捕捉信号后自己处理)

其中9号进程无法被定义。
在这里插入图片描述

Core Dump(核心转储)

1,在Linux中,当一个进程退出的时候,它的退出码和退出信号都会被设置(正常情况)
2,如果进程异常,进程的退出信号会被设置表明进程退出的原因,如果必要,OS会设置core dump标志位。
在这里插入图片描述

什么是Core Dump。当一个进程要异常终止时,可以选择把进程的用户空间内存数据全部 保存到磁
盘上,文件名通常是core,这叫做Core Dump
默认是不允许产生core文件的,
因为core文件中可能包含用户密码等敏感信息,不安全。在开发调试阶段可以用ulimit命令改变这个限制,允许
产生core文件。 首先用ulimit命令改变Shell进程的Resource Limit,允许core文件最大为1024K: $ ulimit -c
1024

有什么作用呢? 方便我们调试,寻找到错误所在的地方。
但是要注意一下,Linux下默认是release版本,编译的时候要 + -g

在这里插入图片描述

说到这里,顺便复习下gdb的调试功能把
r 是运行
l 列出代码
s 逐语句
b 是设置断点
info b 查看断点
p sum 查看sum的值,值显示一次
display sum 常显示
undispaly 不显示
d x 删除编号x的断点

kill,raise,alarm系统调用

在这里插入图片描述

kill命令是调用kill函数实现的。kill函数可以给一个指定的进程发送指定的信号。
raise函数可以给当前进程发送指定的信号(自己给自己发信号)。
alarm()函数 比较简单,就不做演示了。

 int main()
 10 {
 11      int pid = fork();
 12      if(pid < 0)
 13      {
 14        perror("fork");
 15        exit(-1);
 16      }
 17 
 18      if(pid == 0)
 19      {
 20        sleep(5);
 21        printf("I am a child");                                                                                                               
 22        kill(getppid(),2); // 给父进程发送2号信号ctrl c终止
 23      }
 24      else
 25      {
 26         while(1)
 27         {
 28           printf("I am panret\n");
 29           sleep(1);
 30         }
 31 
 32      }
         
 35      return 0;
 36 }

4 int main()                            
 15 {                                     
 16     int count = 0;                    
 17     while(1)                          
 18     {                                 
 19        printf("hello chen\n");        
 20        count++;                       
 21                                       
 22        if(count == 10)                
 23          raise(9); //自己给自己发送9号信号                                                                                                   
 24     }
 25   
 26     return 0;
 27 }            
 28     

再度理解OS给进程发送信号

实际上就是OS向进程控制块写入数据
信号递达(如何处理信号的动作):1,自定义捕捉。 2,默认处理。3,忽略
未决:信号暂存于task_struct中,(还未处理)
阻塞:OS运行进程暂时屏蔽指定信号
1,该信号还是未决状态,2,该信号不能被处理,直到解除阻塞

下图的SIG_DEL是默认动作,SIG_IGN是忽略动作。
在这里插入图片描述

总结:
上面所说的所有信号产生,最终都要有OS来进行执行,为什么?
因为OS是进程的管理者,所以无论信号如何产生,最终都是通过OS来发送给进程的。

信号的处理是否是立即处理的?
在合适的时候(就是由内核态转换为用户态的时候,后面详细说明)

信号如果不是被立即处理,那么信号是否需要暂时被进程记录下来?记录在哪里最合适呢?
信号肯定会被立即记录,记录在进程的PCB中

一个进程在没有收到信号的时候,能否能知道,自己应该对合法信号作何处理呢?
肯定知道。如果不知道的话,进程收到信号难道原地发呆吗?

如何理解OS向进程发送信号?能否描述一下完整的发送处理过程?
当信号产生后,OS找到对应进程的PCB,向PCB中写入相关的信号数据,将进程PCB中的pending位图相应位置置1,
如果block位图中的相应信号编号位置是阻塞,则等到解除阻塞后再执行。
如果不是阻塞,则从handler表(函数指针数组)进行递达。
递达方式有忽略,默认,自定义。
忽略直接将pending位图直接置0.
默认,OS直接执行。
如果是自定义,由内核态进入用户态,切换成用户身份后来执行相应的代码和数据,然后返回内核态,再返回用户态最终处理完成。

信号集操作函数

OS提供系统调用接口,那肯定也提供了相应的数据类型。
例如sigset_t 是信号集类型,只能由OS提供的系统调用去处理
#include <signal.h>

int sigemptyset(sigset_t *set);  //全部置0
int sigfillset(sigset_t *set);   //全部置1
int sigaddset (sigset_t *set, int signo);  //相应位置置1
int sigdelset(sigset_t *set, int signo);   //相应位置置0
int sigismember(const sigset_t *set, int signo)  //检查相应位置是否为1 

sigprocmask 和sigpending
sigprocmask

来段代码操作一下

8 void showsigpending(sigset_t* set)
  9 {    
 10     int i = 1;                                         
 11     for(i = 1; i <= 31; i++)
 12     {       
 13         if(sigismember(set,i))  //如果未决存在,则输出1
 14          printf("1");
 15         else       
 16           printf("0");
 17     }
 18     fflush(stdout);
 19     printf("\n");                                                                                                                            
 20 }         
 21  
 22                  
 23 int main()          
 24 {                                              
 25     sigset_t s,p;
 26     sigemptyset(&s);                                           
 27     sigisemptyset(&p);   //这个用来获取未决状态                         
 28 
 29     sigaddset(&s,2); //将s里面信号集中的二号信号设为1  ctrl + c
 30     sigprocmask(SIG_SETMASK,&s,NULL); // 阻塞2号,不关心之前的信号屏蔽字
 31 
 32     while(1)
 33     {
           sigpending(&p);  //获取当前信号的未决 集
 35         showsigpending(&p);
 36         sleep(1);
 37     }
 
 41     return 0;
 42 }

   

在这里插入图片描述

自定义捕捉详解

我们分析一下递达方式:自定义捕捉,当我们发送特定信号的时候,去执行用户给的自定义函数。

要说清这个,需要知道用户态和内核态的区别。
用户态:只能执行用户的代码,使用用户的数据所处的状态,其中用户以进程为代表(可理解为进程就是用户,但并不准确)
内核态:只能执行OS的代码和数据,所处的状态。
之间的根本区别:就是权限问题。 OS是进程的管理者,意味着内核态的权限非常大。
在这里插入图片描述

解析:
1过程,为什么要切换内核态呢? OS是进程的管理者,进程出现异常,肯定由OS发送信号通知。
3过程为什么要返回用户态去执行呢? 如果不返回在内核态执行用户态的代码这肯定是不允许的,为什么呢?
内核态的权限非常大,万一来一个 rm -rf / ,OS不就凉凉了吗。
4过程,执行完后为什么要返回呢?main 和handler不属于调用的关系,完全是两个独立的执行流,所以无法从handler返回到main()
所以handler()执行完后,只能借助相应的系统调用函数,返回内核后,在从内核返回main的执行流
在这里插入图片描述

简单记忆:数学符号无穷大
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

通过全部用例

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值