Linux 信号机制 Signal

blob.png

信号是软件中断,信号提供了一种处理异步事件的办法,也就是说信号是异步的,一个进程没有必要通过任何操作来等待信号的到达。事实上,进程也不知道信号到底什么时候到达。信号可以直接进行用户空间进程和内核进程之间的交互,内核进程也可以利用它来通知用户空间进程发生了哪些系统事件。它可以在任何时候发给某一进程,而无须知道该进程的状态。如果该进程当前并未处于执行态,则该信号就由内核保存起来,直到该进程恢复执行再传递给它为止;如果一个信号被进程设置为阻塞,则该信号的传递被延迟,直到其阻塞被取消时才被传递给进程。

信号机制是进程间通信机制(IPC)中唯一的异步通信机制。

信号事件的产生:硬件来源(硬件故障、键盘按钮等)和软件来源(函数触发、非法运算等)。常用来发送信号的函数有kill()raise()alarm()setitimer()sigqueue()等。

在某个信号出现时,进程响应信号有以下三种方式:

(1)      忽略此信号

忽略信号就是对此信号不做任何处理,其中,有两个信号不能忽略:SIGKILLSIGSTOP

(2)      捕捉此信号

定义信号处理函数,当信号触发时,执行相应的处理函数。注意,不能捕捉SIGKILLSIGSTOP信号。

(3)      执行系统默认动作

Linux对象每种信号规定的默认操作,如下表所示:

表格 1 常见信号的含义及其默认操作

SIGHUP

该信号在用户终端连接(正常或非正常)结束时发出,通常是在终端的控制进程结束时,通知同一会话内的各个进程与控制终端不再关联

终止

SIGINT

该信号在用户输入INTR字符(通常是Ctrl+C)时发出,终端驱动程序发送此信号并送到前台进程中的每一个进程

终止

SIGQUIT

该信号和SIGINT类似,但由QUIT字符(通常是Ctrl+\)来控制

终止

SIGILL

该信号在一个进程企图执行一条非法指令时(可执行文件本身出现错误,或者试图执行数据段、堆栈溢出时)发出

终止

SIGFPE

该信号在发生致命的算术运算错误时发出。这里不仅包括浮点运算错误,还包括溢出及除数为0等其他所有的算术错误

终止

SIGKILL

该信号用来立即结束程序的运行,并且不能被阻塞、处理和忽略

终止

SIGALRM

该信号当一个定时器到时的时候发出

终止

SIGSTOP

该信号用于暂停一个进程,且不能被阻塞、处理或忽略

暂停进程

SIGTSTP

该信号用于交互停止进程,用户在输入SUSP字符时(通常是Ctrl+Z)发出这个信号

停止进程

SIGCHLD

子进程改变状态时,父进程会收到这个信号

忽略

信号的处理函数:

l  发送信号的函数:kill()raise()

l  捕获信号的函数:alarm()pause()

l  处理信号的函数:signal()sigaction()

函数signal

#include <signal.h>

void (*signal(int signo, void (*func)(int)))(int);

      返回值:若成功,返回以前的信号处理配置;若出错,返回SIG_ERR

上述函数声明的理解:

void (*signal(int signo, void (*func)(int)))(int);

func是一个函数指针,指向参数为单参数int,返回类型void的函数

signal是一个函数指针、这个函数指针指向一个参数为一个int型和一个func型的指针、返回值是一个指向参数为int、返回值是void的函数的指针的指针。

通过typedef改写:

typedef void Sigfunc(int);

Sigfunc *signal(int, Sigfunc *);

参数说明:

l  signo:信号名

l  func:三种取值选择:常量SIG_IGN,常量SIG_DFL,或信号处理函数的地址。如果func的取值为SIG_IGN,则信号发生时忽略处理(除了两个信号SIGKILLSIGSTOP)。如果func的取值为SIG_DFL,则调用信号的默认处理函数。

头文件<signal.h>定义:

#define SIG_ERR (void (*)()) -1

#define SIG_DFL (void (*)()) 0

#define SIG_IGN (void (*)()) 1

进程的创建

当调用fork函数时,子进程继承了父进程的信号处理函数。因为子进程拷贝了父进程的内存,所以信号处理函数的地址对于子进程来说也是有意义的。

不可重入函数问题

在多任务系统下,中断可能在任务执行的任何时间发生;如果一个函数的执行期间被中断后,到重新恢复到断点进行执行的过程中,函数所依赖的环境没有发生改变,那么这个函数就是可重入的,否则就不可重入。

满足下面条件之一的多数是不可重入函数:

(1)使用了静态数据结构;

(2)调用了mallocfree;

(3)调用了标准I/O函数;

(4)进行了浮点运算.

malloc/free是不可重入的,它们使用了全局变量来指向空闲区;标准I/O库的很多实现都使用了全局数据结构; 许多的处理器/编译器中,浮点一般都是不可重入的(浮点运算大多使用协处理器或者软件模拟来实现)

信号处理程序及多线程编程时,要特别注意不可重入函数问题。

killraise函数

kill函数将信号发送给进程或进程组。raise函数则允许进程向自身发送信号。

#include <signal.h>

 

int kill(pid_t pid, int signo);

int raise(int signo);

//两者成功都返回0,错误返回-1

调用

raise(signo);

等价于调用

kill(getpid(),signo);

kill函数的参数pid取值情况:

*  pid > 0,信号被发送给进程IDpid的进程;

*  pid == 0,信号被发送给与发送进程属于同一进程组的所有进程(这些进程的进程组ID等于发送进程的进程组ID),而且发送进程具有向这些进程发送信号的权限。注意术语“所有进程”不包括实现定义的系统进程集。对于多数UNIX系统,这个系统进程集包括内核进程和initpid1);

*  pid < 0,将该信号发送给ID等于pid的绝对值,且发送者对其有发送信号的权限的所有进程。如上,所有进程集不包括系统进程。

*  pid == -1,将该信号发送给发送进程有权限向它们发送喜好的系统上的所有进程。和前面一样,不包含特定的系统进程。

alarmpause函数

使用alarm函数可以设置一个时间值(闹钟时间),在将来的某个时刻该时间值会被超过。当所设置的时间值被超过后,产生SIGALRM信号。如果不忽略或不捕捉此信号,则其默认动作是终止调用该alarm函数的进程。

#include <unistd.h>

unsigned int alarm(unsigned int seconds);

//返回0或上次设置的警报到现在的时间。

每个进程只能有一个闹钟时间。如果在调用alarm时,以前已为该进程设置过闹钟时间,而且它还没有超时,则该闹钟时间的余留值作为本次alarm函数调用的值返回。以前登记的闹钟时间则被新值代换。如果有以前登记的尚未超过的闹钟时间,而且seconds值是0,则取消以前的闹钟时间,其余留值仍作为函数的返回值。

pause函数使调用进程挂起直至捕捉到一个信号。

#include <unistd.h>

int pause(void);

//返回-1errno设置为EINTR

只有执行了一个信号处理程序并从其返回时, pause才返回。在这种情况下, pause返回-1errno设置为EINTR

使用alarm函数和pause函数,进程可以使自己休眠一段指定的时间

 

Ref:

l  http://www.farsight.com.cn/news/emb202.htm

l  指针的函数和函数的指针

l  理解UNIX系统signal函数定义

l  Linux中的可重入函数和不可重入函数