信号概念:
信号是软件中断。
首先每个信号都有一个名字,这些名字都以3个字符SIG开头。例如,SIGARBT是夭折信号,当进程调用abort函数时产生这种信号。SIGALRM是闹钟信号,由alarm函数设定的定时器超时后产生此信号。Linux3.2.0支持31种信号(不同的系统支持的信号数不一样)。
产生信号的条件:
1.当用户按某些终端键时,引发终端产生的信号,比如(Ctrl+C)产生中断信号(SIGINT)。
2.硬件异常产生信号:除数为0、无效的内存引用等。这些条件通常由硬件检测到,并通知内核。然后内核为该条件发生时正在运行的进程产生适当的信号。例如,对执行一个无效内存引用的进程产生SIGSEGV信号。
3.进程调用kill(2)函数可将任意信号发生给另一个进程或进程组。自然,对此有所限制:接收信号进程和发送信号进程的所有者必须相同,或者发送信号进程的所有者必须是超级用户。
4.用户可用kill(1)命令将信号发送给其他进程。此命令只是kill(2)函数的接口。常用此命令终止一个失控的后台进程。
5.当检测到某种软件条件已经发生,并应将其通知有关进程时也产生信号。 这里指的不是硬件产生条件(如除以0),而是软件条件。例如前面提到的SIGALRM闹钟信号。
在某个信号出现时,可以告诉内核按下列3种方式之一进行处理,我们称之为信号的处理或者信号相关的动作:
1.忽略此信号。大多数信号都可使用这种方式进行处理,但有两种信号却绝不能被忽略。它们是SIGKILL和SIGSTOP。这两种信号不能被忽略的原因是:它们向内核和超级用户提供了使进程终止或停止的可靠方法。
2.捕捉信号。为了做到这一点,要通知内核在某种信号发生时,调用一个用户函数。在用户函数中,可执行用户希望对这种事情进行的处理。例如捕捉到SIGCHLD信号,则表示一个子进程已经终止,所以此信号的捕捉函数可以调用waitpid以取得该子进程的进程ID以及它的终止状态。防止僵尸进程的fork编程
3.执行系统默认动作。图10-1给出了对每一种信号的系统默认动作。注意:对大多数信号的系统默认动作是终止该进程。在系统默认动作列,"终止+core"表示进程当前工作目录的core文件中复制了该进程的内存映像,在Linux中,core文件名通过/proc/sys/kernel/core_pattern进行配置,ulimit -c控制了core文件的大小。
信号机制最简单的接口是signal函数:
SIGNAL(2) Linux Programmer's Manual SIGNAL(2)
NAME
signal - ANSI C signal handling
SYNOPSIS
#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
返回值:若成功,返回以前的信号处理配置;若出错,返回SIG_ERR
例子1,给出了一个简单的信号处理程序,它捕捉两个用户定义的信号并打印信号编号:
#include <signal.h>
#include <stdio.h>
#include <errno.h>
#include <stdlib.h>
/* 信号处理函数 */
void sig_usr(int signum)
{
if (signum == SIGUSR1)
printf("recvived SIGUSR1\n");
else if (signum == SIGUSR2)
printf("recvived SIGUSR2\n");
else {
printf("recvived signal %d\n", signum);
exit(1);
}
}
int main()
{
if (signal(SIGUSR1, sig_usr) == SIG_ERR) {
perror("can't catch SIGUSR1");
exit(1);
}
if (signal(SIGUSR2, sig_usr) == SIG_ERR) {
perror("can't catch SIGUSR2");
exit(1);
}
for ( ; ; )
pause(); /* 调用进程接到一信号前挂起 */
return 0;
}
编译运行结果(使用kill(1)命令向进程发送信号):
注:kill 3491产生SIGTERM信号,默认动作是终止进程。
例子2,捕获无效指针引起的段错误
#include <stdio.h>
#include <signal.h>
#include <stdlib.h>
void sig_usr(int signum)
{
if (signum == SIGSEGV) {
printf("recvived SIGSEGV\n");
exit(1);
}
}
int main()
{
if (signal(SIGSEGV, sig_usr) == SIG_ERR) {
perror("can't catch SIGSEGV");
exit(1);
}
char *str = "";
str[100] = 'a';
printf("hello world\n");
return 0;
}
编译运行:
深入:
1.程序启动
当执行一个程序时,所有信号的状态都是系统默认或忽略。通常所有信号都被设置为它们的默认动作,除非调用exec的进程忽略该信号。
2.进程创建
当一个进程调用fork时,其子进程继承父进程的信号处理方式。因为子进程在开始时复制了父进程内存映像,所以信号捕捉函数的地址在子进程是有意义的。
参考:《unix环境高级编程》·第三版
End;