Linux之进程信号

目录

一、进程信号

1、信号的概念

2、信号的产生

2.1硬件产生(按键盘中的按键):

2.2软件产生:

1、kill函数

2.raise函数:     

 3.kill- [num] [pid] 可以给进程发送信号

 3.信号的种类

kill -l 可以罗列信号

4.信号的处理方式

 5.信号的注册

5.1基础概念了解:

5.2信号的注册:

5.3 实时信号和非实时信号在注册时的区别:

6.统一下对信号的理解

7.信号的注销:

7.1非可靠信号的注销

7.2可靠信号的注销

8.信号的自定义处理方式

8.1 概念:

8.2 函数

8.3两个函数在内核中的原理:

插播一下对进程内核代码的处理 

9.信号的捕捉流程

10.信号的阻塞

10.1信号阻塞的特性:

10.2接口:

11.配合信号解决僵尸进程:

 12.volatile关键字:


一、进程信号

1、信号的概念

信号是一个软件中断

生活当中“媳妇过马路的例子”

媳妇叫吃饭只是一个信号告诉你要吃饭,但不是强制要吃饭

1.1只是告诉有这样一个信号,但是具体这个信号怎么处理,什么时候处理由进程决定,所以是软中断

2、信号的产生

2.1硬件产生(按键盘中的按键):

  • ctrl+c:2号信号 SIGINT,按下ctrl+c其实是进程收到了2号信号,2号信号导致进程的退出。

  • ctrl+z :20号信号SIGTSTP,

    ctrl+|:3号信号SIGQUIT

kill -l 命令可以查看操作系统定义的信号值以及信号所对应的名称

 那这些信号又都是干什么的呢?

我们可以看到SIGINT的默认动作是term终止

 

 通过kill命令也是可以发送信号的

 

2.2软件产生:

1、kill函数

int kill(pid_t pid,int sig);

  • 参数:
    • pid_t:要给那个进程发,就填那个进程的pid
    • sig:要给进程发送的信号

使用:  我们可以给当前进程发送一个2号信号,然后再运行一下看看结果

                            

运行一下:

                         

 发现这个进程终止了

2.raise函数:     

  • 作用:谁调用给谁发送信号
  • 参数:给调用的进程发送信号的信号值
  • 使用:给自己发送一个2号信号,终止进程

我们的代码是在这个进程里执行了raise(2)

 一运行:我们发现进程同样在raise调用后就终止了

                  

 3.kill- [num] [pid] 可以给进程发送信号

              

 我们可以看到进程被暂停了

 3.信号的种类

kill -l 可以罗列信号

  • 非实时信号(非可靠信号):
    • 特点:信号可能会丢失(1-31)
  • 实时信号(可靠信号):
    • 特点:信号不会丢失(33-64)

4.信号的处理方式

操作系统对信号的处理方式(man 7 signal

    term   core  cont   ign   stop

  • 默认处理方式:
    • SIG_ DFL,操作系统当中已经定义号信号的处理方式了
    • 例如2号信号->终止进程
    • 11->终止进程,并且产生核心转储文件

忽略处理方式:

  • SIG_ IGN, 该信号为忽略处理(僵尸进程)
  • 进程收到忽略处理的方式的信号后,是不进行处理的,例如之前学过的僵尸进程的产生:
    •  子进程先于父进程退出,子进程在退出的时候会给父进程发送一个SIGCHLD信号,而父进程对这个信号的处理方式是忽略处理的,导致父进程并没有回收子进程的退出状态信息,从而子进程变成了僵尸进程

  • 自定义处理方式:
    • 程序员可以更改信号的处理方式,定义一 个函数,当进程收到该信号的时候, 调用程序猿自己写的函数。(第7个小点涉及到)

 5.信号的注册

5.1基础概念了解:

  • 一个进程收到一个信号,这个过程称之为注册
  • 信号的注册和注销并不是一个过程,是两个独立的过程

 内核中信号注册位图以及s igqueue队列的的了解

  • 都是task_ struct结构体内部的内容
  • 每一个进程都有自己独有的注册位图和sigqueue队列

5.2信号的注册:

  • 位图更改为1,添加Sigqueue节点到sigqueue队列
  • 信号在注册的时候,会将信号对应的比特位从0修改为1,表示当前进程收到了该信号。
  • 还需要哎sigqueue队列中添加一个sigqueue节点,队列在操作系统内核当中本质上是一个双向链表(先进先出的特性

注册的过程大致如下:

  • 5.3 实时信号和非实时信号在注册时的区别:

  • 非实时信号(非可靠信号)的注册
    • 第一次注册:修改sig位图(0-1),修改sigqueue队列。
    • 第二次注册:相同信号值的信号,在前一个信号未被处理的前提下:修改sig位图(1->1),并不会添加sigqueue节点。
    • 总结:再次添加,不会添加sigqueue节点
  • 实时信号(可靠信号)的注册
    • 第一次注册:修改sig位图(0-1),修改sigqueue队列。
    • 第二次注册:相同信号值的信号:修改sig位图(1->1),添加sigqueue节点到sigqueue队列中。
    • 再次添加,会再次添加siquque节点

非实时信号容易信号丢失的原因就是再次注册的时候不会添加sigqueue节点

6.统一下对信号的理解

这里分为:收到信号之前,收到信号,处理信号三个部分

7.信号的注销:

  • 7.1非可靠信号的注销

    • 1.将信号对应的s ig位图当中的比特位置为0(1-0)
    • 2.将对应的信号的sigqueue节点进行出队操作
  • 7.2可靠信号的注销

    • 1.将对应的信号的sigqueue节点进行出队操作
    • 2.判断s igqueue队列当中还有相同信号的S igqueue节点吗
      • 如果有:则比特位不变
      • 如果没有:则比特位改变位0

8.信号的自定义处理方式

  • 8.1 概念:

  • 自定义处理方式, 就是让程序猿自己定义某一个信号的处理方式,例如原来我们的2号信号是一个终止命令我们可以通过自己定义来让他做其他的事,例如打印一句话。

8.2 函数

  • 1.sighandler_t    signal (int signum, sighandler_t handler);

  • 作用
  • 在调用signal函数的时候,我们给函数的第二个参数传递一个回调函数的地址,当我们收到第一个参数所定义的信号值时,就会调用回调函数,执行回调函数的功能。
  • 参数
  • signum:信号值
  • handler:更改为哪一 个函数处理,接受一 个函数地址,函数指针(回调函数)。
  • typedef void (*sighandler_ t)(int);
  • 代码验证
  • 我们写一个代码测试当进程收到2号信号的时候,会发生什么,看还是不是终止进程了

 知道了信号的自定义处理方式以后,我们就有了一个大胆的想法:

来看下下面的这段代码:

 

 那么,我这个进程不就是“刀枪不入”了吗?

我们来看看下面的情况

 kill  其他的信号的时候,都不起作用,只有9号信号起作用了,这是为什么呢?

因为9号信号是强杀信号,不能被自定义处理,(否则那不就和癌细胞一样了吗。。。)

 3.int sigaction(int signum, const struct sigaction *act,struct sigaction *oldact)

 对于这个函数我们也可以来写代码验证一下:

 我们可以发现,这个函数能起到和signal函数一样的效果

 我们还可以利用这个函数进行风骚一点的操作

我们来看下面这段代码:

 

 这个代码里面我们实际上对2号信号进行了两次的更改,我们来执行下这个代码:

8.3两个函数在内核中的原理:

内核当中的结构体

插播一下对进程内核代码的处理 

首先我们要知道进程内核代码的位置

 知道了位置之后,我们就能找到了

 vim sched.h以后我们便进入了界面

 那么怎样更好的查找各个结构体的定义及其使用的地方呢?

我们可以通过ctags 来给源代码创建索引关系

9.信号的捕捉流程

  • 当我们的进程从内核态切换回用户态时,会调用do_signal,检查进程是否收到了信号。

一个进程由操作系统给他注册了一个信号,这个进程是从用户空间切换到内核空间的时候,才去处理这个信号,

调用dosignal函数判断进程有没有收到信号,有就处理,处理完成的时候,这个信号要从这个进程当中注销掉,

注销掉之后就回到用户态继续执行他的代码,也有可能在处理信号的时候,这个进程直接就终止掉了

10.信号的阻塞

  • 10.1信号阻塞的特性:

  • 信号的注册是信号注册, 信号阻塞是信号阻塞。信号的阻塞并不会干扰信号的注册,而是说进程收到这个信号之后,由于阻塞, 暂时不处理该信号 。

  • 10.2接口:

  • int  sigprocmask (int how, const sigset_ t *Set, sigset_ t *oldset);(无法阻塞9号和19号信号)

参数:

  • how:想让s igp rocmask做什么事情
    • SIG_ BLOCK: 设置某个信号为阻塞状态
    • SIG_ UNBL.OCK :设置 某个信号为非阻寒状态
    • SIG_ SETMASK :用第二个参数“set”,替换原来的阻寨位图。 ( 替换的意思)
  • set :新设置的阻塞位图
  • 根据传递进的函数变量计算新的阻塞位图:
    • 1.阻塞单个信号,阻塞多个信号(只要将相应的信号位图设置为1即可)
    • 2.接触阻塞单个信号/解除阻塞多个信号。
  • oldset :原来老的阻塞位图

原理解析:

  • 当how为SIG_ _BLOCK时,函 数会根据set,计算新的阻塞位图, 方式为:
  • block(new) = block(old) | set;新的block位图和旧的block位图按位或运算。

  • 当how为为SIG_ _UNBLOCK时,函数会根据set,计算新的阻塞位图, 方式为:
  • block(new) = block(old) & (^ 'set);将传入新的位图先取反,然后和老的位图进行按位与操作

  • 当how为为SIG_ SETMASK时,函 数会根据set,计算新的阻塞位图,方式为:
  • block (new) = set;

测试代码:

我们调用sigprocmask函数,想要将set位图当中2号信号对应的比特位置为1

为了实现代码我们先要知道set的一些函数

 

 这时候确实是验证了阻塞的设置,但并没有验证阻塞并不影响信号的注册,所以要再次修改下代码

我们现在来验证下阻塞并不影响信号的注册,且可靠信号不会丢信号,非可靠信号会丢信号

思路:

 代码:

 我们来运行一下,观察结果:

 可以看到,可靠信号是不会丢失的,但非可靠信号可以丢失

11.配合信号解决僵尸进程:

在我们之前解决僵尸进程只能调用wait函数或者waitpid函数,但是在调用这两个函数都面临一个问题那就是在调用时,

父进程要不一直处于阻塞等待子进程退出状态,要不一直要配合循环和waitpid的非阻塞状态使用,

我们的父进程就无法做任何的事情。这里我们可以运用信号,对子进程进行回收。

我们来看下这段代码

 我们运行一下代码:

 我们可以看到,能够收到信号,而且将父进程从wait中解放出来

 12.volatile关键字:

  • 作用:
  • 保证内存可见性
  • 每次CPU要计算的数据都是从内存中获取,拒绝编译时优化的方案(从寄存器当中获取)gcc/gt+的编译选项“-00, 01, -02,-03,优化级别时越来越高。(理解优化级别越高,程序可能执行的越快)优化级别越高就从寄存器中取值的可能性越大

我们来写一个代码来实验一下

 

我们运行发现按下ctrl+c向进程输入2号信号,进程直接结束了,这是因为我们在编译时没有对程序进行优化,这时候是从内存中拿的

 

 我们优化为O3级别

 这时候我们再按下Ctrl,可以发现并没有退出,说明并没有从内存中取值,而是从寄存器中直接取值

但当我们给另一个volatile关键字之后,即使怎样优化,也是从内存中拿值的

猜你喜欢

转载自blog.csdn.net/flyingcloud6/article/details/127585663
今日推荐