信号是一种类似IRQ的机制,提供了一种非常美妙的处理异步事件的方式,所以又称软件中断。
因为信号是随机出现的,出现时机不会为进程提前预测,所以对于信号的处理需要预先设定给kernel,要求kernel按照以下三种方式中的一个来处理。
1,忽略此信号,这也是大部分信息的处理方式,但是SIGKILL和SIGSTOP信号不可忽略。
2,捕捉信号,提前通知内核在某种信号发生时调用一个用户函数,做定制处理。
3,执行系统默认动作,系统对于每个内核信号(linux共有31个)都有一个默认操作。
信号处理函数最初函数原型为:void ( *signal (int signo, void (*func)(int) ) ) (int);
linux man page是这么描述这个函数原型的:
这个函数看起来灰常蛋疼,刚毕业的时候看linux源码剖析,C语言基础不好,当日情景,历历在目。
此函数需要两个参数,返回一个函数指针,该指针指向的函数无返回值。第一个参数signo是一个整数,第二个是一个函数指针,其指向函数需要一个int参数且返回void,也即这个函数指针跟signal函数返回的函数地址的函数是一样的。好吧,我觉得我说的自己都晕了。
因其太过难以理解,所以后来被改造:
假如typedef void sighandler(int)
则sighandler *signal(int, sighandler)就可以代替上述函数原型。
系统头文件中声明了三个宏:
#define SIG_ERR (void (*)()) -1
#define SIG_DFL (void (*)()) 0
#define SIG_IGN (void (*)()) 1
这几个宏可以用于signal函数的第二个函数和返回值。
下面来看一个signal的实例,用信号来实现一个软件定时器:
static timer = 0;
void timer_proc(sigid){
if(timer)
timer--;
return;
}
void set_time_ms(void)
{
struct itimerval v;
if(signal(SIGALRM,timer_proc) == SIG_ERR)
{
printf("signal handler config error\n");
}
v.it_interval.tv_sec = 0;
v.it_interval.tv_usec = 97000;
v.it_value.tv_sec = 0;
v.it_value.tv_usec = 97000;
(void)setitimer(ITIMER_REAL, &v, NULL);
while(1)
{
pause();
}
}
被信号中断的系统调用:
kill和raise函数:
#include <signal.h>
int kill(pid_t pid, int signo);
int raise(int signo);
//raise(signo) 等价于 kill(getpid(), signo)
这两个函数的主要功能是发送一个信号来终结进程。
对于kill的pid参数有4种不同的情况:
pid > 0,正常将信号发送给ID为pid的进程。
pid = 0,将该信号发送给与发送进程属于同一进程组的所有进程,前提是发送进程有词权限。
pid < 0,将信号发送给进程组ID为pid的绝对值,需要权限。
pid = -1,将信号发送给发送进程所有有权限发送的进程。
shell命令的kill就是通过这个系统调用实现的。
alarm和pause函数:
#include <unistd.h>
unsigned int alarm(unisigned int seconds);
alarm函数可以设置一个计时器,计时器超时时会产生SIGALRM信号,如果不忽略或不捕捉该信号,默认动作为此函数调用者退出。
每个进程只能有一个闹钟时钟,如果旧的未超时,新的会更新计时器,并将旧的剩余时间通过返回值返回。
所以可以通过alarm(0)来关闭定时器。
#include <unistd.h>
int pause(void);
pause()挂起一个进程直到捕捉一个信号。只有执行了一个信号处理并返回时,pause才返回。返回-1,并将errno设置为EINTR。