打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
Linux信号处理机制

在Linux中,信号是进程间通讯的一种方式,它采用的是异步机制。当信号发送到某个进程中时,操作系统会中断该进程的正常流程,并进入相应的信号处理函数执行操作,完成后再回到中断的地方继续执行。

需要说明的是,信号只是用于通知进程发生了某个事件,除了信号本身的信息之外,并不具备传递用户数据的功能。

1 信号的响应动作

每个信号都有自己的响应动作,当接收到信号时,进程会根据信号的响应动作执行相应的操作,信号的响应动作有以下几种:

  • 中止进程(Term)
  • 忽略信号(Ign)
  • 中止进程并保存内存信息(Core)
  • 停止进程(Stop)
  • 继续运行进程(Cont)

用户可以通过signalsigaction函数修改信号的响应动作(也就是常说的“注册信号”,在文章的后面会举例说明)。另外,在多线程中,各线程的信号响应动作都是相同的,不能对某个线程设置独立的响应动作。

2 信号类型

Linux支持的信号类型可以参考下面给出的列表。

2.1 在POSIX.1-1990标准中的信号列表

信号动作说明
SIGHUP1Term终端控制进程结束(终端连接断开)
SIGINT2Term用户发送INTR字符(Ctrl+C)触发
SIGQUIT3Core用户发送QUIT字符(Ctrl+/)触发
SIGILL4Core非法指令(程序错误、试图执行数据段、栈溢出等)
SIGABRT6Core调用abort函数触发
SIGFPE8Core算术运行错误(浮点运算错误、除数为零等)
SIGKILL9Term无条件结束程序(不能被捕获、阻塞或忽略)
SIGSEGV11Core无效内存引用(试图访问不属于自己的内存空间、对只读内存空间进行写操作)
SIGPIPE13Term消息管道损坏(FIFO/Socket通信时,管道未打开而进行写操作)
SIGALRM14Term时钟定时信号
SIGTERM15Term结束程序(可以被捕获、阻塞或忽略)
SIGUSR130,10,16Term用户保留
SIGUSR231,12,17Term用户保留
SIGCHLD20,17,18Ign子进程结束(由父进程接收)
SIGCONT19,18,25Cont继续执行已经停止的进程(不能被阻塞)
SIGSTOP17,19,23Stop停止进程(不能被捕获、阻塞或忽略)
SIGTSTP18,20,24Stop停止进程(可以被捕获、阻塞或忽略)
SIGTTIN21,21,26Stop后台程序从终端中读取数据时触发
SIGTTOU22,22,27Stop后台程序向终端中写数据时触发

:其中SIGKILLSIGSTOP信号不能被捕获、阻塞或忽略。

2.2 在SUSv2和POSIX.1-2001标准中的信号列表

信号动作说明
SIGTRAP5CoreTrap指令触发(如断点,在调试器中使用)
SIGBUS0,7,10Core非法地址(内存地址对齐错误)
SIGPOLLTermPollable event (Sys V). Synonym for SIGIO
SIGPROF27,27,29Term性能时钟信号(包含系统调用时间和进程占用CPU的时间)
SIGSYS12,31,12Core无效的系统调用(SVr4)
SIGURG16,23,21Ign有紧急数据到达Socket(4.2BSD)
SIGVTALRM26,26,28Term虚拟时钟信号(进程占用CPU的时间)(4.2BSD)
SIGXCPU24,24,30Core超过CPU时间资源限制(4.2BSD)
SIGXFSZ25,25,31Core超过文件大小资源限制(4.2BSD)

:在Linux 2.2版本之前,SIGSYSSIGXCPUSIGXFSZ以及SIGBUS的默认响应动作为Term,Linux 2.4版本之后这三个信号的默认响应动作改为Core。

2.3 其它信号

信号动作说明
SIGIOT6CoreIOT捕获信号(同SIGABRT信号)
SIGEMT7,-,7Term实时硬件发生错误
SIGSTKFLT-,16,-Term协同处理器栈错误(未使用)
SIGIO23,29,22Term文件描述符准备就绪(可以开始进行输入/输出操作)(4.2BSD)
SIGCLD-,-,18Ign子进程结束(由父进程接收)(同SIGCHLD信号)
SIGPWR29,30,19Term电源错误(System V)
SIGINFO29,-,-电源错误(同SIGPWR信号)
SIGLOST-,-,-Term文件锁丢失(未使用)
SIGWINCH28,28,20Ign窗口大小改变时触发(4.3BSD, Sun)
SIGUNUSED-,31,-Core无效的系统调用(同SIGSYS信号)

注意:列表中有的信号有三个值,这是因为部分信号的值和CPU架构有关,这些信号的值在不同架构的CPU中是不同的,三个值的排列顺序为:1,Alpha/Sparc;2,x86/ARM/Others;3,MIPS。

例如SIGSTOP这个信号,它有三种可能的值,分别是17、19、23,其中第一个值(17)是用在Alpha和Sparc架构中,第二个值(19)用在x86、ARM等其它架构中,第三个值(23)则是用在MIPS架构中的。

3 信号机制

文章的前面提到过,信号是异步的,这就涉及信号何时接收、何时处理的问题。

我们知道,函数运行在用户态,当遇到系统调用、中断或是异常的情况时,程序会进入内核态。信号涉及到了这两种状态之间的转换,过程可以先看一下下面的示意图:

接下来围绕示意图,将信号分成接收、检测和处理三个部分,逐一讲解每一步的处理流程。

3.1 信号的接收

接收信号的任务是由内核代理的,当内核接收到信号后,会将其放到对应进程的信号队列中,同时向进程发送一个中断,使其陷入内核态。

注意,此时信号还只是在队列中,对进程来说暂时是不知道有信号到来的。

3.2 信号的检测

进程陷入内核态后,有两种场景会对信号进行检测:

  • 进程从内核态返回到用户态前进行信号检测
  • 进程在内核态中,从睡眠状态被唤醒的时候进行信号检测

当发现有新信号时,便会进入下一步,信号的处理。

3.3 信号的处理

信号处理函数是运行在用户态的,调用处理函数前,内核会将当前内核栈的内容备份拷贝到用户栈上,并且修改指令寄存器(eip)将其指向信号处理函数。

接下来进程返回到用户态中,执行相应的信号处理函数。

信号处理函数执行完成后,还需要返回内核态,检查是否还有其它信号未处理。如果所有信号都处理完成,就会将内核栈恢复(从用户栈的备份拷贝回来),同时恢复指令寄存器(eip)将其指向中断前的运行位置,最后回到用户态继续执行进程。

至此,一个完整的信号处理流程便结束了,如果同时有多个信号到达,上面的处理流程会在第2步和第3步骤间重复进行。

4 信号的使用

4.1 发送信号

用于发送信号的函数有raisekillkillpgpthread_killtgkillsigqueue,这几个函数的含义和用法都大同小异,这里主要介绍一下常用的raisekill函数。

raise函数:向进程本身发送信号

函数声明如下:

#include <signal.h>int raise(int sig);

函数功能是向当前程序(自身)发送信号,其中参数sig为信号值。

kill函数:向指定进程发送信号

函数声明如下:

#include <sys/types.h>#include <signal.h>int kill(pid_t pid, int sig);

函数功能是向特定的进程发送信号,其中参数pid为进程号,sig为信号值。

在这里的参数pid,根据取值范围不同,含义也不同,具体说明如下:

  • pid > 0 :向进程号为pid的进程发送信号
  • pid = 0 :向当前进程所在的进程组发送信号
  • pid = -1 :向所有进程(除PID=1外)发送信号(权限范围内)
  • pid < -1 :向进程组号为-pid的所有进程发送信号

另外,当sig值为零时,实际不发送任何信号,但函数返回值依然有效,可以用于检查进程是否存在。

4.2 等待信号被捕获

等待信号的过程,其实就是将当前进程(线程)暂停,直到有信号发到当前进程(线程)上并被捕获,函数有pausesigsuspend

pause函数:将进程(或线程)转入睡眠状态,直到接收到信号

函数声明如下:

#include <unistd.h>int pause(void);

该函数调用后,调用者(进程或线程)会进入睡眠(Sleep)状态,直到捕获到(任意)信号为止。该函数的返回值始终为-1,并且调用结束后,错误代码(errno)会被置为EINTR。

sigsuspend函数:将进程(或线程)转入睡眠状态,直到接收到特定信号

函数声明如下:

#include <signal.h>int sigsuspend(const sigset_t *mask);

该函数调用后,会将进程的信号掩码临时修改(参数mask),然后暂停进程,直到收到符合条件的信号为止,函数返回前会将调用前的信号掩码恢复。该函数的返回值始终为-1,并且调用结束后,错误代码(errno)会被置为EINTR。

4.3 修改信号的响应动作

用户可以自己重新定义某个信号的处理方式,即前面提到的修改信号的默认响应动作,也可以理解为对信号的注册,可以通过signalsigaction函数进行,这里以signal函数举例说明。

首先看一下函数声明:

#include <signal.h>typedef void (*sighandler_t)(int);sighandler_t signal(int signum, sighandler_t handler);

第一个参数signum是信号值,可以从前面的信号列表中查到,第二个参数handler为处理函数,通过回调方式在信号触发时调用。

下面为示例代码:

#include <stdio.h>#include <signal.h>#include <unistd.h>/* 信号处理函数 */void sig_callback(int signum) {    switch (signum) {        case SIGINT:            /* SIGINT: Ctrl+C 按下时触发 */            printf("Get signal SIGINT. \r\n");            break;        /* 多个信号可以放到同一个函数中进行 通过信号值来区分 */        default:            /* 其它信号 */            printf("Unknown signal %d. \r\n", signum);            break;    }    return;}/* 主函数 */int main(int argc, char *argv[]) {    printf("Register SIGINT(%u) Signal Action. \r\n", SIGINT);    /* 注册SIGINT信号的处理函数 */    signal(SIGINT, sig_callback);    printf("Waitting for Signal ... \r\n");    /* 等待信号触发 */    pause();    printf("Process Continue. \r\n");    return 0;}

源文件下载:链接

例子中,将SIGINT信号(Ctrl+C触发)的动作接管(打印提示信息),程序运行后,按下Ctrl+C,命令行输出如下:

./linux_signal_exampleRegister SIGINT(2) Signal Action. Waitting for Signal ... ^CGet signal SIGINT. Process Continue.

进程收到SIGINT信号后,触发响应动作,将提示信息打印出来,然后从暂停的地方继续运行。这里需要注意的是,因为我们修改了SIGINT信号的响应动作(只打印信息,不做进程退出处理),所以我们按下Ctrl+C后,程序并没有直接退出,而是继续运行并将"Process Continue."打印出来,直至程序正常结束。

本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
【热】打开小程序,算一算2024你的财运
linux内核中的信号机制
unix中的信号处理机制
linux下基于C语言的信号编程实例
Linux进程间通信
linux c信号相关函数学习记录
linux信号 linux signal
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服