信号是软件中断,信号提供了一种处理异步事件的方法。
1、信号概念
每个信号都有一个名字,这些名字都以三个字符SIG开头。
SIGABRT是夭折信号,当进程调用abort函数时产生这种信号
SIGALRM是闹钟信号,当由alarm函数设置的计时器超时后产生此信号。
在头<signal.h>中,这些信号都被定义为正整数(信号编号).
很多条件可以产生信号 :
a)当用户按某些终端键时,引发终端产生信号
ctrl+c产生中断信号(SIGINT)
b)硬件异常产生信号:除数为0、无效内存引用
对执行一个无效内存引用的进程产生SIGSEGV
c)进程调用kill(2)函数可将信号发送给另一个进程或进程组
d)kill(1)命令将信号发送给其他进程
e)当检测到某种软件条件已经发生,并应将其通知有关进程时也产生信号。
信号是异步事件,产生信号的事件对进程而言是随机出现的。进程不能简单的测试一个变量来判断是否出现一个信号,而是必须告诉内核
“在此信号出现时,请执行下列操作。”
可以要求内核在某个信号出现时按照下列三种方式之一进行处理,我们称之为信号的处理
(1)忽略此信号
二种信号决不能被忽略:SIGKILL SIGSTOP
(2)捕捉信号
(3)执行系统默认动作
每一种信号的默认动作。注,针对大多数信号的系统默认动作是终止进程。
在“默认动作”列中,“终止+core”表示在进程当前工作目录的core文件中复制该进程的存储映象
2、signal
#include <signal.h>
void (*signal(int signo,void (*func)(int))) (int);
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
static void sig_usr(int signo);
int main(int argc, char **argv) {
if(signal(SIGUSR1,sig_usr) == SIG_ERR)
printf("signal error");
if(signal(SIGUSR2,sig_usr) == SIG_ERR)
printf("signal error");
for(;;) {
pause();
}
}
static void sig_usr(int signo) {
if(signo == SIGUSR1) {
printf("recived sigusr1");
}
if(signo == SIGUSR2) {
printf("recived sigusr2");
}
printf("recived %d",signo);
}
|
kill -SIGUSR1 11251
kill -SIGUSR2 11251
程序启动:
1)当执行一个程序时,所有信号的状态都是系统默认或忽略,通常所有信号都被设置为它们的默认动作,除非调用exec的进程忽略该信号。
一个具体的例子是一个交互式shell如何处理针对后台进程的中断和退出信号
cc main.c &
shell自动将后台进程对中断和退出信号的处理方式设置为忽略。于是按中断键时不会影响到后台进程。
很多捕捉这二个信号的交互式程序具有下列形式的代码 :
void sig_int(int),sig_quit(int);
if(signal(SIGINT,SIG_IGN) != SIG_IGN)
signal(SIGINT,sig_int);
if(signal(SIGQUIT,SIG_IGN) != SIG_IGN)
signal(SIGQUIT,sig_quit);
2)进程创建
当一个进程调用fork时,其子进程继承父进程的信号处理方式。因为子进程在开始时复制了父进程的存储映像,所以信号捕捉函数的地址在子进程中是有意义
3、可重入函数
进程捕捉到信号并对其进行处理时,进程正在执行的指令序列就被信号处理程序临时中断,它首先执行该信号处理程序中的指令。
如果从信号处理程序返回,则继续执行在捕捉到信号时进程正执行的正常指令序列。
若信号处理程序中调用一个不可重入函数,则其结果是不可预见的。
4、可靠信号术语
1)当引发信号的事件发生时,为进程产生一个信号。可件可以是硬件异常、软件条件(alarm计时器超时)、终端产生的信号或调用kill。
2)当对信号采取了这种动作时,我们说向进程递送了一个信号。
3)在信号产生(generation)和递送(delivery)之间的时间间隔内,称信号是未决的(pending)
4)进程可以选用信号递送阻塞
5)进程调用sigpending判断哪些信号是设置为阻塞并处于未决状态
6)每个进程都有一个信号屏蔽字(signal mask),它规定了当前要阻塞递送到该进程的信号集。
7)sigset_t,保存一个信号集,信号屏蔽字就存放在这些信号集中
5、kill raise
kill函数将信号发送给进程或进程组。raise函数则允许进程向自身发送信号
#include <signal.h>
int kill(pid_t pid,int signo);
int raise(int signo);
|
raise(signo) 等价于kill(getpid(),signo);
pid>0 将该信号发送给进程ID为pid的进程
pid==0 将该信号发送给与发送进程属于同一进程组的所有进程
pid<0 将该信号发送给其进程组ID等于pid的绝对值,而且发送进程具有向其发送信号的权限。
pid==-1将该信号发送给发送进程有权限向它们发送信号的系统上的所有进程。
6、alarm和pause
使用alarm函数可以设置一个计时器,在将来某个指定的时间该计时器会超时。当计时器超时时,产生SIGALRM信号。
如果不忽略或不捕捉此信号,则其默认动作是终止调用该alarm函数的进程。
#include <unistd.h>
unsigned int alarm(unsigned int seconds);
参数seconds的值是秒数,经过了指定的seconds秒后产生信号SIGALRM。
|
pause函数使调用进程挂起直至捕捉到一个信号。
#incude <unistd.h>
int pause(void);
|
7、信号集
我们需要有一个能表示多个信号---信号集(signal set)的数据类型。
sigset_t表示一个信号集
#include <signal.h>
int sigemptyset(sigset_t *set); //初始化由set指向的信号集,清除其中所有信号
int sigfillset(sigset_t *set); //初始化由set指向的信号集,使其包括所有信号
int sigaddset(sigset_t *set,int signo);
int sigdelset(sigset_t *set,int signo);
|
8、sigprocmask函数
sigprocmask可以检测或更改其信号屏蔽字,或者在一个步骤中同时执行这二个操作。
#include <signal.h>
int sigprocmask(int how,const sigset_t *restrict set,siset_t #restrict oset);
|
9、sigpending
sigpending函数返回信号集,其中的各个信号对于调用进程是阻塞的而不能递送,因而也一定是当前未决的。
#include <signal.h>
int sigpending(sigset_t *set);
|
10、sigaction
sigaction函数的功能是检查或修改与指定信号相关的处理动作。
#include <signal.h>
int sigaction(int signum, const struct sigaction *act,
struct sigaction *oldact);
|
11、sigsetjmp siglongjmp
用于非局部转移的setjmp和longjmp,在信号处理程序中经常调用longjmp函数以返回到程序的主循环中,而不是从该处理程序返回。
调用longjmp有一个问题,当捕捉到一个信号时,进入信号捕捉函数,此时当前信号被自动地加到进程的信号屏蔽字中。
这阻止了后来产生的这种信号中断该信号处理程序。
#include <setjmp.h>
int sigsetjmp(sigjmp_buf env, int savesigs);
void siglongjmp(sigjmp_buf env, int val);
|
如果savemask非0,则sigsetjmp在env中保存进程的当前信号屏蔽字。
调用setlongjmp时,如果带非0 savemask的sigsetjmp调用已经保存了env,则siglongjmp从其中恢复保存的信号屏蔽字。
12、sigsuspend
13、abort
14、system
15、sleep