linux signal

  • 了解linux signal

1.Linux信号概述

  软中断信号(signal,又简称为信号)用来通知进程发生了异步事件。在软件层次上是对中断机制的一种模拟,在原理上,一个进程收到一个信号与处理器收到一个中断请求可以说是一样的。信号是进程间通信机制中唯一的异步通信机制,一个进程不必通过任何操作来等待信号的到达,事实上,进程也不知道信号到底什么时候到达。进程之间可以互相通过系统调用kill发送软中断信号。内核也可以因为内部事件而给进程发送信号,通知进程发生了某个事件。信号机制除了基本通知功能外,还可以传递附加信息。

  收到信号的进程对各种信号有不同的处理方法。处理方法可以分为三类:忽略、捕捉和默认动作

  • 忽略信号,大多数信号可以使用这个方式来处理,但是有两种信号不能被忽略(分别是 SIGKILL和SIGSTOP)。因为他们向内核和超级用户提供了进程终止和停止的可靠方法,如果忽略了,那么这个进程就变成了没人能管理的的进程,显然是内核设计者不希望看到的场景
  • 捕捉信号,需要告诉内核,用户希望如何处理某一种信号,说白了就是写一个信号处理函数,然后将这个函数告诉内核。当该信号产生时,由内核来调用用户自定义的函数,以此来实现某种信号的处理。
  • 系统默认动作,对于每个信号来说,系统都对应由默认的处理动作,当发生了该信号,系统会自动执行。不过,对系统来说,大部分的处理方式都比较粗暴,就是直接杀死该进程。具体的信号默认动作可以使用man 7 signal来查看系统的具体定义。

  Linux支持的信号列表:

uos@uos-PC:~$ kill -l
 1) SIGHUP       2) SIGINT       3) SIGQUIT      4) SIGILL       5) SIGTRAP
 6) SIGABRT      7) SIGBUS       8) SIGFPE       9) SIGKILL     10) SIGUSR1
11) SIGSEGV     12) SIGUSR2     13) SIGPIPE     14) SIGALRM     15) SIGTERM
16) SIGSTKFLT   17) SIGCHLD     18) SIGCONT     19) SIGSTOP     20) SIGTSTP
21) SIGTTIN     22) SIGTTOU     23) SIGURG      24) SIGXCPU     25) SIGXFSZ
26) SIGVTALRM   27) SIGPROF     28) SIGWINCH    29) SIGIO       30) SIGPWR
31) SIGSYS      34) SIGRTMIN    35) SIGRTMIN+1  36) SIGRTMIN+2  37) SIGRTMIN+3
38) SIGRTMIN+4  39) SIGRTMIN+5  40) SIGRTMIN+6  41) SIGRTMIN+7  42) SIGRTMIN+8
43) SIGRTMIN+9  44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13
48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-12
53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9  56) SIGRTMAX-8  57) SIGRTMAX-7
58) SIGRTMAX-6  59) SIGRTMAX-5  60) SIGRTMAX-4  61) SIGRTMAX-3  62) SIGRTMAX-2
63) SIGRTMAX-1  64) SIGRTMAX

信号的种类:

  • 编号为1 ~ 31的信号为传统UNIX支持的信号,是不可靠信号(非实时的);
  • 编号为32 ~ 63的信号是后来扩充的,称做可靠信号(实时信号)。

Note : 不可靠信号和可靠信号的区别在于前者不支持排队,可能会造成信号丢失,而后者不会。

2.信号处理流程

  对于一个完整的信号生命周期(从信号发送到相应的处理函数执行完毕)来说,可以分为三个阶段:

  • 信号诞生
  • 信号在进程中注册
  • 信号的执行和注销

2.1 信号诞生

  信号事件的发生有两个来源:硬件来源(比如我们按下了键盘或者其它硬件故障);软件来源,最常用发送信号的系统函数是kill, raise, alarm和setitimer以及sigqueue函数,软件来源还包括一些非法运算等操作。

  • 硬件方式
    • 用户输入:比如在终端上按下组合键ctrl+C,产生SIGINT信号;
    • 硬件异常:CPU检测到内存非法访问等异常,通知内核生成相应信号,并发送给发生事件的进程;
  • 软件方式
    • 通过系统调用,发送signal信号:kill(),raise(),sigqueue(),alarm(),setitimer(),abort()

  这里按发出信号的原因简单分类,以了解各种信号:

  • (1) 与进程终止相关的信号。当进程退出,或者子进程终止时,发出这类信号。

  • (2) 与进程例外事件相关的信号。如进程越界,或企图写一个只读的内存区域(如程序正文区),或执行一个特权指令及其他各种硬件错误。

  • (3) 与在系统调用期间遇到不可恢复条件相关的信号。如执行系统调用exec时,原有资源已经释放,而目前系统资源又已经耗尽。

  • (4) 与执行系统调用时遇到非预测错误条件相关的信号。如执行一个并不存在的系统调用。

  • (5) 在用户态下的进程发出的信号。如进程调用系统调用kill向其他进程发送信号。

  • (6) 与终端交互相关的信号。如用户关闭一个终端,或按下break键等情况。

  • (7) 跟踪进程执行的信号。

2.2 信号注册和注销

  在进程表的表项中有一个软中断信号域,该域中每一位对应一个信号。内核给一个进程发送软中断信号的方法,是在进程所在的进程表项的信号域设置对应于该信号的位。如果信号发送给一个正在睡眠的进程,如果进程睡眠在可被中断的优先级上,则唤醒进程;否则仅设置进程表中信号域相应的位,而不唤醒进程。如果发送给一个处于可运行状态的进程,则只置相应的域即可。

  进程的task_struct结构中有关于本进程中未决信号的数据成员: struct sigpending pending:

   26 struct sigpending {                                                                                                                                                                                                                    
>> 27         struct list_head list;
>> 28         sigset_t signal;                 
   29 };

  第一项 list 就是信号等待队列的头,而第二项 signal 则是一个 64bit 的一个数据,它的每一个 bit 代表着对应编号的一个信号。如果对应的 bit 位被置位,那么就表示对应进程收到了这个序号的信号。__send_signal 中的 sigaddset 就是操作 signal,在对应的 bit 位上写1。

  • 注册
    在进程task_struct结构体中有一个未决信号的成员变量 struct sigpending pending。每个信号在进程中注册都会把信号值加入到进程的未决信号集。

    • 非实时信号发送给进程时,如果该信息已经在进程中注册过,不会再次注册,故信号会丢失;
    • 实时信号发送给进程时,不管该信号是否在进程中注册过,都会再次注册。故信号不会丢失;
  • 注销

    • 非实时信号:不可重复注册,最多只有一个sigqueue结构;当该结构被释放后,把该信号从进程未决信号集中删除,则信号注销完毕;
    • 实时信号:可重复注册,可能存在多个sigqueue结构;当该信号的所有sigqueue处理完毕后,把该信号从进程未决信号集中删除,则信号注销完毕;

3.信号处理过程

  进程收到一个信号后不会被立即处理,而是在恰当时机进行处理!一般是在中断返回的时候,或者内核态返回用户态的时候 (这个情况出现的比较多)。

  也就是说,信号不一定会被立即处理,操作系统不会为了处理一个信号而把当前正在运行的进程挂起,因为这样的话资源消耗太大了,如果不是紧急信号,是不会立即处理的,操作系统多选择在内核态切换回用户态的时候处理信号。

  因为进程有可能在睡眠的时候收到信号,操作系统肯定不愿意切换当前正在运行的进程,于是就得把信号储存在进程唯一的 PCB(task_struct) 当中。

3.1.信号触发

  一般信号的触发大致可以分为如下的几类:

  • 在终端通过组合按键触发,终端驱动程序发送信号给前台进程。例如 Ctrl-C(SIGINT)、Ctrl-(SIGQUIT)、Ctrl-Z(SIGTSTP) 。

  • 硬件异常产生信号,由硬件检测到并通知内核并由内核向当前进程发送适当的信号。例如除 0 导致 CPU 产生异常,内核将该异常解释为 SIGFPE 信号发送给进程;访问非法内存地址导致 MMU 产生异常,内核将该异常解释为 SIGSEGV 信号发送给进程。
    进程通过 kill(2) 发送信号,或者调用 kill(1) 命令发送,默认发送 SIGTERM 信号,该信号的默认处理动作是终止进程。

  • 通过 raise(3) 给自己进程发送信号,其中 raise(sig) 等价于 kill(getpid(), sig) 。

  • 通过 killpg(2) 给进程组发送信号,使用 killpg(pgrp, sig) 等价于 kill(-pgrp, sig) 。

  • 利用 sigqueue 给进程发送信号,支持排队,可以附带信息。

  • 当内核检测到某种软件条件发生时也可以通过信号通知进程。例如闹钟超时产生 SIGALRM 信号;向读端已关闭的管道写数据产生 SIGPIPE 信号;子进程退出发送 SIGCHILD 信号。

  当 CPU 正在执行某个进程时,通过终端驱动程序发送了一个 SIGINT 信号给该进程,该信号会记录在对应进程 PCB 中,则该进程的用户空间代码暂停执行,CPU 从用户态切换到内核态处理信号。

  从内核态回到用户态之前,会先处理 PCB 中记录的信号,发现有一个 SIGINT 信号待处理,而这个信号的默认处理动作是终止进程,所以直接终止进程而不再返回它的用户空间代码执行。

3.2.信号阻塞

  信号在内核中的表示大致分为如下几类:

  • 信号递达 (delivery) 实际执行信号处理信号的动作。
  • 信号未决 (pending) 信号从产生到抵达之间的状态,信号产生了但是未处理。
    忽略,抵达之后的一种动作。
  • 阻塞 (block) 收到信号不立即处理,被阻塞的信号将保持未决状态,直到进程解除对此信号的阻塞,才执行抵达动作

  每个信号都由两个标志位分别表示阻塞和未决,以及一个函数指针表示信号的处理动作。
在这里插入图片描述

  在上图的例子中,其状态信息解释如下:

  • SIGHUP 未阻塞也未产生过,当它递达时执行默认处理动作。
  • SIGINT 信号产生过,但正在被阻塞,所以暂时不能递达。虽然它的处理动作是忽略,但在没有解除阻塞之前不能忽略这个信号,因为进程仍有机会改变处理动作之后再解除阻塞。
  • SIGQUIT 信号未产生过,一旦产生 SIGQUIT 信号将被阻塞,它的处理动作是用户自定义函数 sighandler。
    信号产生但是不立即处理,前提条件是要把它保存在 pending 表中,表明信号已经产生。

4.内核处理

  如果信号的处理动作是用户自定义函数,在信号递达时就调用这个函数,这称为信号捕捉。由于信号处理函数的代码是在用户空间的,处理过程比较复杂。

在这里插入图片描述
  每个进程都会采用一个进程控制块对其进行描述,进程控制块中设计了一个signal的位图信息,其中的每位与具体的signal相对应,这与中断机制是保持一致的。

  当系统中一个进程A通过signal系统调用向进程B发送signal时,设置进程B的对应signal位图,类似于触发了signal对应中断。发送signal只是“中断”触发的一个过程,具体执行会在两个阶段发生:

  • system call返回。进程B由于调用了system call后,从内核返回用户态时需要检查他拥有的signal位图信息表,此时是一个执行点。
  • 中断返回。进程被系统中断打断之后,系统将CPU交给进程时,需要检查即将执行进程所拥有的signal位图信息表,此时也是一个执行点。

  综上所述,signal的执行点可以理解成从内核态返回用户态时,在返回时,如果发现待执行进程存在被触发的signal,那么在离开内核态之后(也就是将CPU切换到用户模式),执行用户进程为该signal绑定的signal处理函数,从这一点上看,signal处理函数是在用户进程上下文中执行的。当执行完signal处理函数之后,再返回到用户进程被中断或者system call(软中断或者指令陷阱)打断的地方。

  Signal 机制实现的比较灵活,用户进程由于中断或者system call陷入内核之后,将断点信息都保存到了堆栈中,在内核返回用户态时,如果存在被触发的signal,那么直接将待执行的signal处理函数push到堆栈中,在CPU切换到用户模式之后,直接pop堆栈就可以执行signal处理函数并且返回到用户进程了。Signal处理函数应用了进程上下文,并且应用实际的中断模拟了进程的软中断过程。

在这里插入图片描述
也就是说,处理信号最好的时机是程序从内核态切换到用户态时。

refer to

  • https://gohalo.me/post/kernel-signal-introduce.html
  • https://www.jianshu.com/p/f445bfeea40a
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值