Linux进程信号——未决、递达

1.Core Dump

1.1ulimit命令

ulimit命令是查看或者设置当前用户或者进程使用资源的阀值
在这里插入图片描述
如上图所示我们的core fiel的大小是0,因此需要 ulimit -c +大小,对其进行大小的设置,才能生成对应的Core Dump文件
在这里插入图片描述

1.2Core Dump是什么

当一个程序崩溃之后,操作系统会将程序奔溃前的内存之中的信息转储于磁盘之中,因为一个进程的终止,需要操作系统发送信号,而一个进程收到信号,有三种处理方式,分别为:执行默认的动作、执行自定义动作、忽略它。
所以说接收到了一个信号,并不是立即执行的,因此程序在崩溃前有时间将内存之中的信息,转存至硬盘之中

1.3Core Dump是一种事后调试

生成的core文件是二进制文件,因为core文件是给个编译器看的,方便调试,是一种事后调试(逐步逐过程为事前调试)
类似于linux的弹窗,报出错误在哪里

1.4Core Dump调试演示

在这里插入图片描述

1.5验证段错误是11号信号

在这里插入图片描述

1.6为什么云服务器默认关闭Core Dump

1.保护服务器
由2.1的图中可知,云服务器默认是关闭Core Dump的,这是因为当发生段错误时,会在磁盘之中生成临时文件,而我们的服务器出现了错误,一般都是先让服务器先恢复使用再进行错误的调式
如果服务器重启就发生错误,这样会导致生成很多临时文件,那么生成的临时文件将磁盘堆满,甚至将系统盘堆满,那么我们的系统就会出错,导致无法第一时间恢复使用

2.并不是所有的信号都需要Core Dumo
我们进程退出的时候,会有一个输出型参数status,低8位中的低7位表示退出信号,第8位表示Core Dump,如果为0则表示不需要,为1表示需要。比如我们的 kill -9号信号,系统直接终止进程,是不需要调式的

2.信号的几种概念

在这里插入图片描述

执行信号的处理动作叫做信号递达
信号从产生到递达之间的状态,称为信号未决
被阻塞的信号,产生时,将保持未决状态,当进程解除对此信号的阻塞,才会执行递达动作
阻塞和忽略是不一样的,忽略是一种处理动作,而信号只要被阻塞就不会被递达

3.信号的保存和发送

3.1信号的保存

信号的保存前提是,是否收到信号
因此我们需要记录的信息是,这个信号是谁,这个信号是否产生
而我们的实时信号有31种,因此使用一张位图可以进行保存
比特位的位置表示是那种信号,比特位为1表示该种信号产生了,为0表示该种信号没有产生
同理,我们对信号的阻塞信息,也会被保存在一张位图之中,对应的是当前信号是否被阻塞

3.2信号的发送

这张位图保存在进程的PCB之中,与其说是操作系统发出信号,不如说是操作系统向进程PCB中的位图写入信号
信号的写入是由操作系统来执行的,如何处置这个信号则是由进程来决定的

4.信号在内核中的示意图

4.1保存示意图

我们的内核之中有两张结构一样的位图,来表示当前信号是否被阻塞,和是否处于未决状态,还有一个函数指针表示处理的动作
信号产生时,内核在进程控制块中设置该信号的未决标志,直到信号递达,信号标志才会被清除
在这里插入图片描述

4.2普通信号易丢失

在这里插入图片描述

5.sigset_t

每个信号的阻塞或未决都是由一个比特位来表示的,不是0就是1,因此未决和阻塞标志可以使用同一样数据类型sigset_t来进行存储
sigset_t被称为信号集,表示每个信号是有效还是无效

在阻塞状态中,有效、无效表示是否被阻塞,阻塞信号集(block表)也被叫做当前进程的信号屏蔽字
在未决状态中,有效、无效表示信号是否处于未决状态

6.信号集操作函数

6.1sigset_t变量设置函数

sigset_t是表示信号有效、无效状态的信号集,这个类型的实现是由系统来决定的,因此使用者只能调用下列函数的接口来操作sigset_t变量,而不是直接对其内部数据进行解释(比如,直接打印sigset_t类型变量,或者做位运算操作,这些都是没有意义的)
在这里插入图片描述

6.2信号屏蔽字更改函数sigprocmask

在这里插入图片描述

6.3读取当前未决信号集函数sigpending

在这里插入图片描述

6.4效果演示

void handler(int sig)
{
  printf("i am 2 signal\n");
}

void show(sigset_t *rset )
{
  for(int i=1;i<=31;i++)//普通信号范围是0-31
  {
    if(sigismember(rset,i))//判断信号是否存在,存在输出1
      printf("1");
    else
      printf("0");//不存在输出0
  }
  printf("\n");
}

int main()
{
  signal(2,handler);//捕捉2号信号

  sigset_t set;
  sigset_t oset;
  sigset_t rset;

  sigemptyset(&set);//清空当前信号集
  sigaddset(&set,2);//添加2号信号
  sigprocmask(SIG_BLOCK,&set,&oset);//将2号信号屏蔽
  
  int count=0;
  while(1)
  {
    if(count==5)//10秒后,解除对2号信号的屏蔽
      sigprocmask(SIG_SETMASK,&oset,NULL);//使用备份,恢复原来模样

    sigpending(&rset);//获取当前信号集
    show(&rset);//显示当前信号集
    sleep(1);
    count++;
  }

  return 0;
}

在这里插入图片描述

6.5原码验证

在这里插入图片描述

7.信号的捕捉

操作系统向进程发出信号,进程并不是立即执行信号的,而是在合适的时候,这个合适的时候是信号被递达的时候
一个信号递达,是在内核态切换回用户态时,进行信号的相关检测

7.1用户态、内核态

我们的进程是不断在用户态和内核态进行切换的,因为内核的权限高,用户态的权限低,当我们需要执行操作系统代码的时候,用户态是无法执行的,因此需要切换成内核态

内核形态和用户形态是有物理地址支持的,操作系统有且仅有一个,因此只要有进程的存在,操作系统就会存在,每个进程的虚拟空间的内核空间对应的映射关系都是同样的,都是同一份内核代码

在这里插入图片描述

7.2信号捕捉图解

在这里插入图片描述
由上述可知,一个进程是可以产生多条执行流的,比如一条当前进程代码执行流,一条信号处理执行流

7.3信号捕捉函数 sigaction

sigaction与signal是以样的,都是信号捕捉函数,只是sigaction的功能更加的丰富
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

8.再次理解程序崩溃还可以进行打印

在这里插入图片描述

9.可重入函数

两个执行流,进入同一个函数称为重入函数
在这里插入图片描述
常见不可重入函数:
1.调用malloc/free的函数,因为malloc也是用全局链表来管理堆的
2.调用了I/O函数库,标准I/O库中很多实现都以不可重入的方式使用全局数据结构
3.STL库,STL考虑的是效率问题,安全问题需要操作者自己考虑(比如迭代器失效问题,可以侧面反映)

10.volatile

volatile修饰的变量,是不可被"覆盖"的,读取变量必须读取变量真实的存储位置(不可读取缓存、寄存器等位置的值)
在这里插入图片描述

11.SIGCHLD

子进程退出,父进程可以通过阻塞或非阻塞的方式等待子进程结束,然后清理资源,但是不管是那种方式,程序的实现都是比较复杂的

有什么办法可以让父进程不用等待,又不会产生僵尸进程呢?
可以让子进程退出时,给父进程发送一个信号,父进程只需要在信号执行函数中调用wait函数清理资源,这样父进程就不必去检测子进程是否退出的问题了

1.进程终止时,会给父进程发送SIGCHLD信号
2.父进程如果通过signal或sigaction设置忽略这个信号,子进程终止时自动释放自己的资源,父进程不必再等待子进程(linux下可用,不保证其它系统上可用)
或者自定义SIGCHLD信号处理动作,调用wait等资源释放函数,也可以完成子进程资源的释放

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

  • 11
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值