https://www.linuxjournal.com/article/3985

每个信号在 signal.h 头文件中通过宏进行定义,实际是在 signal.h 中定义,对于编号以及信号名的映射关系可以通过 kill -l 命令查看。

其中,[1, 31] 是普通信号,[34, 64] 是实时信号,前者是从 UNIX 系统继承过来的信号,不支持排队可能会导致信号丢失, 比如发送多次相同的信号, 进程只能收到一次,其信号值小于 SIGRTMIN 。

后来 Linux 改进了信号机制,增加了 32 种新的信号,这些信号都是可靠信号,支持排队,主要位于 [SIGRTMIN, SIGRTMAX] 区间,通常用于用户使用。

对于实时信号,可以使用 sigqueue 发送信号。

对于信号,通常有如下的几种处理方式:

  1. 忽略。大部分信号都可以通过这种方式处理,不过 SIGKILL 和 SIGSTOP 两个信号有特殊用处,不能被忽略。
  2. 默认动作。大多数信号的系统默认动作终止该进程。
  3. 捕捉信号。也就是在收到信号时,执行一些用户自定义的函数。

信号处理过程

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

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

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

信号触发

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

  1. 在终端通过组合按键触发,终端驱动程序发送信号给前台进程。例如 Ctrl-C(SIGINT)Ctrl-\(SIGQUIT)Ctrl-Z(SIGTSTP) 。
  2. 硬件异常产生信号,由硬件检测到并通知内核并由内核向当前进程发送适当的信号。例如除 0 导致 CPU 产生异常,内核将该异常解释为 SIGFPE 信号发送给进程;访问非法内存地址导致 MMU 产生异常,内核将该异常解释为 SIGSEGV 信号发送给进程。
  3. 进程通过 kill(2) 发送信号,或者调用 kill(1) 命令发送,默认发送 SIGTERM 信号,该信号的默认处理动作是终止进程。
  4. 通过 raise(3) 给自己进程发送信号,其中 raise(sig) 等价于 kill(getpid(), sig) 。
  5. 通过 killpg(2) 给进程组发送信号,使用 killpg(pgrp, sig) 等价于 kill(-pgrp, sig) 。
  6. 利用 sigqueue 给进程发送信号,支持排队,可以附带信息。
  7. 当内核检测到某种软件条件发生时也可以通过信号通知进程。例如闹钟超时产生 SIGALRM 信号;向读端已关闭的管道写数据产生 SIGPIPE 信号;子进程退出发送 SIGCHILD 信号。

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

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

函数调用

通过 raise() 可以给当前进程发送指定的信号;kill() 函数向指定进程发送信号;而 abort() 函数使当前进程接收到 SIGABRT 信号,其函数声明如下:

#include<signal.h>
int kill(pid_t pid,int signo);
int raise(int signo); #include<stdlib.h>
void abort(void);

类似于 exit() 函数,abort() 函数总是会成功的,所以没有返回值。

信号阻塞

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

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

每个信号都由两个标志位分别表示阻塞和未决,以及一个函数指针表示信号的处理动作。

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

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

信号产生但是不立即处理,前提条件是要把它保存在 pending 表中,表明信号已经产生。

信号集操作函数

信号集用来描述信号的集合,每个信号占用一位,总共 64 位,Linux 所支持的所有信号可以全部或部分的出现在信号集中,主要与信号阻塞相关函数配合使用。

执行信号的处理动作称为信号递达 (Delivery),信号从产生到递达之间的状态,称为信号未决 (Pending),进程可以选择阻塞 (Block) 某个信号,被阻塞的信号产生时将保持在未决状态,直到进程解除对此信号的阻塞,才执行递达的动作。

注意,阻塞和忽略是不同的,信号被阻塞就不会递达,而忽略是在递达之后可选的一种处理动作。

每个进程都有一个用来描述哪些信号递送到进程时将被阻塞的信号集,该信号集中的所有信号在递送到进程后都将被阻塞,信号在内核中的表示可以看作是这样的:

如下是常见的信号集的操作函数:

#include <signal.h>
int sigemptyset(sigset_t *set); /* 所有信号的对应位清0 */
int sigfillset(sigset_t *set); /* 设置所有的信号,包括系统支持的所有信号 */
int sigaddset(sigset_t *set, int signo); /* 在该信号集中添加有效信号 */
int sigdelset(sigset_t *set, int signo); /* 在该信号集中删除有效信号 */
int sigismember(const sigset_t *set, int signo); /* 用于判断一个信号集的有效信号中是否包含某种信号 */ int sigprocmask(int how, const sigset_t *restrict set, sigset_t *restrict oset);

调用 sigprocmask() 函数可以读取或更改进程的信号屏蔽字:

#include <signal.h>
int sigprocmask(int how, const sigset_t *set, sigset_t *oset); 其中how:
SIG_BLOCK 信号屏蔽字是其当前信号屏蔽字和set指向信号集的并集,set包含了希望阻塞的信号
SIG_UNBLOCK 信号屏蔽字是其当前信号屏蔽字和set所指向信号集补集的交集,set包含了希望解除阻塞的信号
SIG_SETMASK 信号屏蔽字将被set指向的信号集的值代替

一个进程的信号屏蔽字规定了当前阻塞而不能递送给该进程的信号集,如果调用该函数解除了对当前若干个未决信号的阻塞,则在该函数返回前,至少将其中一个信号递达。

内核处理

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

也就是说,处理信号最好的时机是程序从内核态切换到用户态时。

多线程

在多线程环境下,产生的信号是传递给整个进程的,会随机选择一个线程发送。

多进程的信号一般是异步处理,在信号处理函数中会有很多的约束,例如 errno 是线程安全但是非信号安全、不能调用 malloc()free() 等函数、使用全局变量时增加 volatile 以防不恰当优化等。

信号同步处理

在 POSIX.1 规范定义了 sigwait()、 sigwaitinfo() 和 pthread_sigmask() 等接口,可以实现在专用的线程中以同步方式处理信号。

Signal VS. Sigaction

实际上,上述的 signal() 是最早的函数,现在大多系统,包括 Linux 都用 sigaction() 重新实现了 signal(),其区别如下:

  1. signal() 注册的回调函数,会在调用前先清除掉,所以需要在回调函数中重新注册;而 sigaction() 函数如果要删除需要显示调用。
  2. signal() 处理不能阻塞信号,而 sigaction() 则可以阻塞指定的信号。

这也就意味着,signal() 函数可能会丢失信号。

如下是两个函数的声明。

#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler); #include <signal.h>
struct sigaction {
void (*sa_handler)(int); /* 信号处理方式 */
void (*sa_sigaction)(int, siginfo_t *, void *); /* 实时信号的处理方式 */
sigset_t sa_mask; /* 额外屏蔽的信号 */
int sa_flags;
void (*sa_restorer)(void);
};
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);

对于 sigaction() 函数,如果 act 非空,则会根据 act 结构体中的信号处理函数来修改该信号的处理动作;如果 oldact 非空则会通过该变量将信号原来的处理动作返回。

其中,sa_handler 变量用于指定信号的处理函数,有三种方式:

  1. SIG_IGN 忽略信号;
  2. SIG_DFL 执行系统默认动作;
  3. 赋值为函数指针表示用自定义函数捕捉信号。

当某个信号的处理函数被调用时,内核自动将当前信号加入进程的信号屏蔽字,当信号处理函数返回时自动恢复原来的信号屏蔽字,这样就保证了在处理某个信号时,如果这种信号再次产生,那么它会被阻塞到当前处理结束为止。

常用程序

实时信号 VS. 非实时信号

简单来说,就是通过测试程序,发现非实时信号不排队,而实时信号支持排队不会丢失。

SIGKILL VS. SIGSTOP

这两个信号比较特殊,无法在程序中进行屏蔽,用于一些特殊的用途。

SIGKILL

也就是直接的 kill -9 操作,为 root 提供了一种使进程强制终止方法,此时将会有操作系统直接回收该进程占用的资源,对于一些保存状态的应用就可能会导致异常。

SIGSTOP

对于前台运行的程序,可以通过 Ctrl-Z 终止程序,切换到后台,此时进程处于 TASK_STOPPED 状态,ps 命令显示处于 T 状态。如果要恢复运行,应该使用 fg JOB-ID 恢复运行,如果直接发送 SIGCONT 将会使进程退出。

可以参考 WikiPedia SIGSTOP 中的介绍,抄录如下:

When SIGSTOP is sent to a process, the usual behaviour is to pause that process in its
current state. The process will only resume execution if it is sent the SIGCONT signal.
SIGSTOP and SIGCONT are used for job control in the Unix shell, among other purposes.
SIGSTOP cannot be caught or ignored.

也就是说,这个信号是用于 Shell 的任务管理,不能被用户屏蔽。其中常用的是 rsync 的同步任务,例如要清理一些空间,可以暂停运行,清理完成后重新启动运行。

# kill -s STOP `pidof rsync`
# kill -s CONT `pidof rsync`

如下是启动一个 sleep 进程,可以看下如何停止、继续执行进程,如下示例中会启动一个前台进程,并通过发送信号进行停止、启动操作。

$ sleep 1000
$ kill -STOP <PID>
$ kill -CONT <PID>

当停止后,通过 ps aux 查看进程状态处于 T 也就是暂停状态。

注意,通过 -CONT 重新启动后会进入到后台运行,如果需要可以通过 fg <JOB-ID> 重新恢复到前台运行。

 

信号-linux的更多相关文章

  1. linux信号Linux下Signal信号太详细了,终于找到了

    linux信号Linux下Signal信号太详细了,终于找到了 http://www.cppblog.com/sleepwom/archive/2010/12/27/137564.html

  2. Linux信号(signal) 机制分析

    Linux信号(signal) 机制分析 [摘要]本文分析了Linux内核对于信号的实现机制和应用层的相关处理.首先介绍了软中断信号的本质及信号的两种不同分类方法尤其是不可靠信号的原理.接着分析了内核 ...

  3. <转>Linux环境进程间通信(二): 信号(上)

    原文链接:http://www.ibm.com/developerworks/cn/linux/l-ipc/part2/index1.html 原文如下: 一.信号及信号来源 信号本质 信号是在软件层 ...

  4. Linux C 程序 信号及信号的处理(19)

    信号及信号的处理 1.Linux信号的介绍  信号是一种软件中断.Linux系统中根据POSIX标准扩展的信号机制.  1.信号来源      1.硬件方式           1.当用户按下某个键, ...

  5. 练习--LINUX进程间通信之信号SIGNAL

    同样的,信号也不要太迷信可靠信号及不及靠信号,实时或非实时信号. 但必须要了解这些信号之间的差异,函数升级及参数,才能熟练运用. ~~~~~~~~~~~~~~~~ 信号本质 信号是在软件层次上对中断机 ...

  6. Linux环境进程间通信(二): 信号(上)

    linux下进程间通信的几种主要手段: 管道(Pipe)及有名管道(named pipe):管道可用于具有亲缘关系进程间的通信,有名管道克服了管道没有名字的限制,因此,除具有管道所具有的功能外,它还允 ...

  7. Linux高性能server编程——信号及应用

     信号 信号是由用户.系统或者进程发送给目标进程的信息.以通知目标进程某个状态的改变或系统异常. Linux信号可由例如以下条件产生: 对于前台进程.用户能够通过输入特殊的终端字符来给它发送信号. ...

  8. Linux信号实践(2) --信号分类

    信号分类 不可靠信号 Linux信号机制基本上是从UNIX系统中继承过来的.早期UNIX系统中的信号机制比较简单和原始,后来在实践中暴露出一些问题,它的主要问题是: 1.进程每次处理信号后,就将对信号 ...

  9. 第9章 Linux进程和信号超详细分析

    9.1 进程简单说明 进程是一个非常复杂的概念,涉及的内容也非常非常多.在这一小节所列出内容,已经是我极度简化后的内容了,应该尽可能都理解下来,我觉得这些理论比如何使用命令来查看状态更重要,而且不明白 ...

随机推荐

  1. S3C6410触摸屏驱动分析

    一. device的注册1.0 两个注册//在smdk6410_machine_init中既注册了touchscreen的私有信息也注册了ts资源 1 在arch/arm/mach-s3c64xx/m ...

  2. docker自定义网络里的dns实现原理

    简单说一下流程吧,不写了. docker会修改容器里的/etc/resolv.conf文件,把dns服务器设置成127.0.0.11,因为127.0.0.0/8地址都是本机回环地址,所以dns查询的时 ...

  3. Git之多人协同开发

    一.获取远程库信息 1 2 3 $ git remote -v origin  https://github.com/xxxxx/node.git (fetch) origin  https://gi ...

  4. 请求https接口时报错:Caused by SSLError(SSLError(1, u'[SSL: CERTIFICATE_VERIFY_FAILED] certificat,安装certifi

    如何解决SSL 根证书验错误: 一种解决方法是:verify=False 在session.request 里面: session.post(post_url,data=post_data,heade ...

  5. 【C语言C++编程入门】程序的可读性和函数的调用!

    一个简单程序的结构 你已经看过一个具体的例子,下面可以了解一些 C程序的基本规则了. 程序由一个或多个函数组成,其中一定有一个名为 main()的函数.函数的描述由函数头和函数体组成.函数头包括预处理 ...

  6. npoi 设置单元格格式

    using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.T ...

  7. jdk可视化工具系列——检视阅读

    jdk可视化工具系列--检视阅读 参考 java虚拟机系列 RednaxelaFX知乎问答 RednaxelaFX博客 JConsole--Java监视与管理控制台 jconsole介绍 JConso ...

  8. 彩贝网app破解登入参数(涉及app脱壳,反编译java层,so层动态注册,反编译so层)

    一.涉及知识点 app脱壳 java层 so层动态注册 二.抓包信息 POST /user/login.html HTTP/1.1 x-app-session: 1603177116420 x-app ...

  9. CentOS 网卡固定地址配置

    修改4个文件后重启网卡 vim /etc/default/grub GRUB_CMDLINE_LINUX="resume=UUID=05dbb36b-dbba-40a3-ba99-1b044 ...

  10. Linux显示系统信息sh脚本

    #!/bin/bash # #******************************************************************** #Author: wangxia ...