此文编写参考朱有鹏老师的视频课程和《嵌入式Linux系统编程》(该书作者为秦立春、周中孝)
一、信号由谁产生,产生后怎么处理
1、产生
信号是进程在运行过程中,由自身产生或由进程外部发过来的消息(事件),是硬件中断的软件模拟(软中断),它是UNIX进程通信最古老的方法。信号的生成来自内核,让内核生成信号的请求来自3个地方。
用户:用户能够通过输入Ctrl+C、Ctrl+\,或是终端驱动程序分配给信号控制字符的其他任何键来请求内核产生信号;
内核:当进程执行出错时,内核会给进程发送一个信号,例如,非法段存取(内存访问违规)、浮点数溢出等;
进程:一个进程可以通过系统调用kill给另一个进程发送信号,一个进程可以通过信号和另外一个进程进行通信。
2、处理
(1)隐式处理
接收默认处理:接收默认处理的进程通常会导致进程本身消亡。例如,连接到终端的进程,用户按下Ctrl+C,将导致内核向进程发送一个SIGINT信号,进程如果不对该信号做特殊处理,系统将采用默认方式处理该信号,即终止进程的执行;
(2)显式处理
忽略信号:进程可以通过代码,显示地忽略某个信号的处理,但是某些信号是不能被忽略的;
捕捉信号并处理:进程可以事先注册信号处理函数,当接收到某个信号时,由信号处理函数自动捕捉并且处理该信号。
(3)特殊情况
有两个信号既不能被忽略也不能被捕捉,它们是SIGKILL和SIGSTOP,即进程接收到这两个信号后只能接受系统的默认处理,即终止进程。
(4)注意
(1)signal函数绑定一个捕获函数后信号发生后会自动执行绑定的捕获函数,并且把信号编号作为传参传给捕获函数
(2)signal的返回值在出错时为SIG_ERR,绑定成功时返回旧的捕获函数
二、意义
信号可以直接进行用户空间进程和内核进程之间的交互,内核进程利用它来通知用户空间进程发生了哪些系统事件。它可以在任何时候发给某一进程,而无须知道该进程的状态。如果该进程当前并未处于执行态,则该信号就由内核保存起来,直到该进程恢复执行再传递给它为止;如果一个信号被进程设置为阻塞,则该信号的传递被延迟,直到其阻塞被取消时才被传递给进程。一个完整的信号生命周期可以分为3个阶段,这3个阶段由4个事件来刻画:信号产生、信号在进程中注册、信号在进程中注销、执行信号处理函数。信号的生命周期如下图所示
三、如何查看
1、查看方法
每个信号用一个整型常量宏表示,以SIG开头,如SIGCHLD、SIGINT等,它们在系统头文件<signal.h>中定义,也可以通过在shell下输入kill-l查看信号列表,或者输入man 7 signal查看更详细的说明。
2、常见信号
3.5.2.常见信号介绍
(1)SIGINT 2 Ctrl+C时OS送给前台进程组中每个进程
(2)SIGABRT 6 调用abort函数,进程异常终止
(3)SIGPOLL SIGIO 8 指示一个异步IO事件,在高级IO中提及
(4)SIGKILL 9 杀死进程的终极办法
(5)SIGSEGV 11 无效存储访问时OS发出该信号
(6)SIGPIPE 13 涉及管道和socket
(7)SIGALARM 14 涉及alarm函数的实现
(8)SIGTERM 15 kill命令发送的OS默认终止信号
(9)SIGCHLD 17 子进程终止或停止时OS向其父进程发此信号
(10)
SIGUSR1 10 用户自定义信号,作用和意义由应用自己定义
SIGUSR2 12
3、较全的表格如下:
Signal | Description |
---|---|
SIGABRT | 由调用abort函数产生,进程非正常退出 |
SIGALRM | 用alarm函数设置的timer超时或setitimer函数设置的interval timer超时 |
SIGBUS | 某种特定的硬件异常,通常由内存访问引起 |
SIGCANCEL | 由Solaris Thread Library内部使用,通常不会使用 |
SIGCHLD | 进程Terminate或Stop的时候,SIGCHLD会发送给它的父进程。缺省情况下该Signal会被忽略 |
SIGCONT | 当被stop的进程恢复运行的时候,自动发送 |
SIGEMT | 和实现相关的硬件异常 |
SIGFPE | 数学相关的异常,如被0除,浮点溢出,等等 |
SIGFREEZE | Solaris专用,Hiberate或者Suspended时候发送 |
SIGHUP | 发送给具有Terminal的Controlling Process,当terminal 被disconnect时候发送 |
SIGILL | 非法指令异常 |
SIGINFO | BSD signal。由Status Key产生,通常是CTRL+T。发送给所有Foreground Group的进程 |
SIGINT | 由Interrupt Key产生,通常是CTRL+C或者DELETE。发送给所有ForeGround Group的进程 |
SIGIO | 异步IO事件 |
SIGIOT | 实现相关的硬件异常,一般对应SIGABRT |
SIGKILL | 无法处理和忽略。中止某个进程 |
SIGLWP 由Solaris Thread Libray内部使用 | |
SIGPIPE | 在reader中止之后写Pipe的时候发送 |
SIGPOLL | 当某个事件发送给Pollable Device的时候发送 |
SIGPROF | Setitimer指定的Profiling Interval Timer所产生 |
SIGPWR | 和系统相关。和UPS相关。 |
SIGQUIT | 输入Quit Key的时候(CTRL+\)发送给所有Foreground Group的进程 |
SIGSEGV | 非法内存访问 |
SIGSTKFLT | Linux专用,数学协处理器的栈异常 |
SIGSTOP | 中止进程。无法处理和忽略。 |
SIGSYS | 非法系统调用 |
SIGTERM | 请求中止进程,kill命令缺省发送 |
SIGTHAW | Solaris专用,从Suspend恢复时候发送 |
SIGTRAP | 实现相关的硬件异常。一般是调试异常 |
SIGTSTP | Suspend Key,一般是Ctrl+Z。发送给所有Foreground Group的进程 |
SIGTTIN | 当Background Group的进程尝试读取Terminal的时候发送 |
SIGTTOU | 当Background Group的进程尝试写Terminal的时候发送 |
SIGURG | 当out-of-band data接收的时候可能发送 |
SIGUSR1 | 用户自定义signal 1 |
SIGUSR2 | 用户自定义signal 2 |
SIGVTALRM | setitimer函数设置的Virtual Interval Timer超时的时候 |
SIGWAITING | Solaris Thread Library内部实现专用 |
SIGWINCH | 当Terminal的窗口大小改变的时候,发送给Foreground Group的所有进程 |
SIGXCPU | 当CPU时间限制超时的时候 |
SIGXFSZ | 进程超过文件大小限制 |
SIGXRES | Solaris专用,进程超过资源限制的时候发 |
四、signal信号处理机制
1、注册信号处理函数
可以用函数signal注册一个信号处理函数,原型为:
#include<signal.h>
typedef void(*sighandler_t)(int); //函数指针 void(*)(int a)
sighandler_t signal(int signum,sighandler_t handler);
signal的第1个参数signum表示要捕捉处理的信号,第2个参数是个函数指针,表示要对该信号进行捕捉处理的函数,该参数也可以是SIG_DFL(表示交由系统默认处理,相当于白注册了)或SIG_IGN(表示忽略掉该信号而不做任何处理)。signal如果调用成功,返回该信号处理函数的地址,否则返回SIG_ERR。
sighandler_t类型的形参(函数指针)指向一个信号处理函数,由signal函数注册,注册以后,在整个进程运行过程中均有效,并且对不同的信号可以注册同一个信号处理函数。该函数只有一个整型参数,表示信号值。
2、例程
#include<stdio.h>
#include <signal.h>
#include <unistd.h>
void func(int signum)
{
printf("signum=%d\n",signum);
int i=0;
while(i<5)
{
printf("i=%d\n",i);
i++;
sleep(1);
}
}
int main()
{
signal(SIGINT, func);
while(1)
{
printf("running \n");
sleep(1);
}
return 0;
}
五、signal函数的优点和缺点
(1)优点:简单好用,捕获信号常用
(2)缺点:无法简单直接得知之前设置的对信号的处理方法
六、sigaction函数介绍
(1)2个都是API,但是sigaction比signal更具有可移植性
(2)用法关键是2个sigaction指针
sigaction比signal好的一点:sigaction可以一次得到设置新捕获函数和获取旧的捕获函数(其实还可以单独设置新的捕获或者单独只获取旧的捕获函数),而signal函数不能单独获取旧的捕获函数而必须在设置新的捕获函数的同时才获取旧的捕获函数。
具体做法可参考下书的5.3 sigaction信号处理机制
书名:嵌入式Linux系统编程
编者:秦立春 周中孝
七、alarm和pause
1、alarm函数
(1)内核以API形式提供的闹钟
alarm()也称为闹钟函数,它可以在进程中设置一个定时器,当定时器指定的时间到时,它就向进程发送SIGALARM信号。要注意的是,一个进程只能有一个闹钟时间,如果在调用alarm()之前已设置过闹钟时间,则任何以前的闹钟时间都被新值所代替。
2、pause函数
内核挂起
pause()函数是用于将调用进程挂起直至捕捉到信号为止,挂起期间交出CPU给其他进程去执行。当当前进程进入pause状态后当前进程会表现为“卡住、阻塞住”,要退出pause状态当前进程需要被信号唤醒,这个函数很常用,通常可以用于判断信号是否已到。
3、例程
#include<stdio.h>
#include <signal.h>
#include <unistd.h>
void func(int sig);
int main()
{
signal(SIGALRM, func);
printf("running \n");
alarm(3);//3秒
int i=0;
for(i=0;i<3;i++)//数约3秒
{
sleep(1);
printf("%d\n",i+1);
}
pause();
return 0;
}
void func(int sig)
{
if(sig==SIGALRM)
{
printf("this event has happened\n");
}
}