信号(signal)是一种软件中断,它提供了一种处理异步事件的方法,也是进程间惟一的异步通信方式。在Linux系统中,根据POSIX标准扩展以后的信号机制,不仅可以用来通知某种程序发生了什么事件,还可以给进程传递数据。
信号的来源可以有很多种试,按照产生条件的不同可以分为硬件和软件两种。
1、 硬件方式
当用户在终端上按下某键时,将产生信号。如按下
硬件异常产生信号:除数据、无效的存储访问等。这些事件通常由硬件(如:CPU)检测到,并将其通知给Linux操作系统内核,然后内核生成相应的信号,并把信号发送给该事件发生时正在进行的程序。
2、 软件方式
用户在终端下调用kill命令向进程发送任务信号。进程调用kill或sigqueue函数发送信号。当检测到某种软件条件已经具备时发出信号,如由alarm或settimer设置的定时器超时时将生成SIGALRM信号。
在Shell下输入kill –l 可显示Linux 系统支持的全部依赖,信号列表如下:
信号的值定义在signal.h中,在Linux中没有16和32这两个信号。上面信号的含义如下:
(1) SIGHUP:当用户退出Shell时,由该Shell启的发所有进程都退接收到这个信号,默认动作为终止进程。
(2) SIGINT:用户按下
(3) SIGQUIT:当用户按下
(4) SIGILL :CPU检测到某进程执行了非法指令。默认动作为终止进程并产生core文件。
(5) SIGTRAP:该信号由断点指令或其他trap指令产生。默认动作为终止进程并产生core文件。
(6) SIGABRT:调用abort函数时产生该信号。默认动作为终止进程并产生core文件。
(7) SIGBUS:非法访问内存地址,包括内存地址对齐(alignment)出错,默认动作为终止进程并产生core文件。
(8) SIGFPE:在发生致命的算术错误时产生。不仅包括浮点运行错误,还包括溢出及除数为0等所有的算术错误。默认动作为终止进程并产生core文件。
(9) SIGKILL:无条件终止进程。本信号不能被忽略、处理和阻塞。默认动作为终止进程。它向系统管理员提供了一种可以杀死任何进程的方法。
(10) SIGUSR1:用户定义的信号,即程序可以在程序中定义并使用该信号。默认动作为终止进程。
(11) SIGSEGV:指示进程进行了无效的内存访问。默认动作为终止进程并使用该信号。默认动作为终止进程。
(12) SIGUSR2:这是另外一个用户定义信号,程序员可以在程序中定义并使用该信号。默认动作为终止进程。
(13) SIGPIPE:Broken pipe:向一个没有读端的管道写数据。默认动作为终止进程。
(14) SIGALRM:定时器超时,超时的时间由系统调用alarm设置。默认动作为终止进程。
(15) SIGTERM:程序结束(terminate)信号,与SIGKILL不同的是,该信号可以被阻塞和处理。通常用来要求程序正常退出。执行Shell命令kill时,缺少产生这个信号。默认动作为终止进程。
(16) SIGCHLD:子程序结束时,父进程会收到这个信号。默认动作为忽略该信号。
(17) SIGCONT:让一个暂停的进程继续执行。
(18) SIGSTOP:停止(stopped)进程的执行。注意它和SIGTERM以及SIGINT的区别:该进程还未结束,只是暂停执行。本信号不能被忽略、处理和阻塞。默认作为暂停进程。
(19) SIGTSTP:停止进程的动作,但该信号可以被处理和忽略。按下
(20) SIGTTIN:当后台进程要从用户终端读数据时,该终端中的所有进程会收到SIGTTIN信号。默认动作为暂停进程。
(21) SIGTTOU:该信号类似于SIGTIN,在后台进程要向终端输出数据时产生。默认动作为暂停进程。
(22) SIGURG:套接字(socket)上有紧急数据时,向当前正在运行的进程发出此信号,报告有紧急数据到达。默认动作为忽略该信号。
(23) SIGXCPU:进程执行时间超过了分配给该进程的CPU时间,系统产生该信号并发送给该进程。默认动作为终止进程。
(24) SIGXFSZ:超过文件最大长度的限制。默认动作为yl终止进程并产生core文件。
(25) SIGVTALRM:虚拟时钟超时时产生该信号。类似于SIGALRM,但是它只计算该进程占有用的CPU时间。默认动作为终止进程。
(26) SIGPROF:类似于SIGVTALRM,它不仅包括该进程占用的CPU时间还抱括执行系统调用的时间。默认动作为终止进程。
(27) SIGWINCH:窗口大小改变时发出。默认动作为忽略该信号。
(28) SIGIO:此信号向进程指示发出一个异步IO事件。默认动作为忽略。
(29) SIGPWR:关机。默认动作为终止进程。
(30) SIGRTMIN~SIGRTMAX:Linux的实时信号,它没有固定的含义(或者说可以由用户自由使用)。注意,Linux线程机制使用了前3个实时信号。所有的实时信号的默认动作都是终止进程。
1、可靠信号与不可靠信号
在Linux系统中,信号的可靠性是指信号是否会丢失,或者说该信号是否支持排除。SIGHUP( 1 ) ~SIGSYS( 31 )之间的信号都是继承自UNIX系统是不可靠信号。Linux系统根据POSIX标准定义了SIGRTMIN(33) ~SIGRTMAX(64)之间的信号,它们都是可靠信号,也称为实时信号。
当导致产生信号的事件发生时,内核就产生一个信号。信号产生后,内核通常会在进程表中设置某种形的标志。当内核设置了这个标志,我们就说内核向一个进程递送了一个信号。信号产生(generate)和递送(delivery)之间的时间间隔,称主信号未决(pending)。
进程可以调用sigpending将信号设为阻塞,如果为进程产生一个阻塞信号,而对信号的动作是捕捉该信号(即不忽略信号),则内核将为该进程的此信号保持为未决状态,直到该进程对此信号解除阻塞或者对此信号的响应更改为忽略。如果在进程解除对某个信号的阻塞之前,这种信号发生了多次,那么如果信号被递送多次(即信号在未决信号队列里面排队),则称之为可靠信号;只被递送一次的信号称为不可靠信号。
2、信号的优先级
信号实质上是软中断,中断有优先级,信号也有优先级。如果一个进程有多个未决信号,则对于同一个未决的实时信号,内核将按照发送的顺序来递送信号。如果存在多个未决信号,则值(或者说编号)越小的越先被递送。如果即存在不可靠信号,又存在可靠信号(实时信号),虽然POSIX对这一情况没有明确规定,但Linux系统和大多数遵循POSIX标准的操作系统一样,将优先递送不可靠信号。
当信号发生时,用户可以要求进程以下列3种方式之一对信号做出响应。
1、 捕捉信号:对于要捕捉的信号,可以为其指定信号处理函数,信号发生时该函数自动被调用,在该函数内部实现对该信号的处理。
2、 忽略信号:大多数信号都可使用这种方式进行处理,但是SIGKILL和SIGSTOP这两个信号不能被忽略,同时这两个信号也不能被捕获和阻塞。此外,如果忽略某某些由硬件异常产生的信号(如非法存储访问或除以0),则进程的行为是不可预测的。
3、 按照系统默认方式处理。大部分信号的默认操作是终止进程,且所有的实时信号的默认动作都是终止进程。
程序不可捕获、阻塞或忽略的信号有:SIGKILL,SIGSTOP
不能恢复至默认动作的信号有:SIGILL,SIGTRAP
默认会导致进程流产的信号有:SIGABRT、SIGBUS、SIGFPE、SIGILL、SIGIOT、SIGQUIT、SIGSEGV、SIGTRAP、SIGXCPU、SIGXFSZ
默认会导致进程退出的信号有:SIGALRM、SIGHUP、SIGINT、SIGKILL、SIGPIPE、SIGPOLL、SIGPROF、SIGSYS、SIGTERM、SIGUSR1、SIGUSR2、SIGVTALRM
默认会导致进程停止的信号有:SIGSTOP、SIGTSTP、SIGTTIN、SIGTTOU
默认进程忽略的信号有:SIGCHLD、SIGPWR、SIGURG、SIGWINCH
(1)、signal()
#include
void (*signal(int signum, void (*handler))(int)))(int);
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler));
第一个参数指定信号的值,第二个参数指定针对前面信号值的处理,可以忽略该信号(参数设为SIG_IGN);可以采用系统默认方式处理信号(参数设为SIG_DFL);也可以自己实现处理方式(参数指定一个函数地址)。
如果signal()调用成功,返回最后一次为安装信号signum而调用signal()时的handler值;失败则返回SIG_ERR。
直接上代码
#include
#include
#include
void handle(int s)
{
staticint count_2=0;
staticint count_34=0;
if(s==2)
{
count_2++;
printf("signalid %d count %d\n",s,count_2);
}
elseif(s==34)
{
count_34++;
printf("signalid %d count %d\n",s,count_34);
}
else
{
printf("signalid %d count\n",s);
}
}
int main()
{
printf("pid%d\n",getpid());
signal(2,handle);
signal(11,handle);
signal(34,handle);
while(1)
{
//printf("processruning %d\n",getpid());
sleep(1);
}
}
本例注册了三个信号,由系统默认转变为用户处理
此程序为了说明一下几点
->信号安装,信号发送
->可靠信号和不可靠信号的差异
运行下,从另外一个终端发送11信号,发现进入我们自己注册的信号处理函数
验证可靠性就不能用终端敲了,直接用一个程序验证
#include
#include
int main()
{
int index=0;
for(index=0;index<20;index++)
{
kill(21975,2);
}
for(index=0;index<20;index++)
{
kill(21975,34);
}
}
此程序分别向pid 21975,就是我们上面运行的程序,分别发送20次信号2(不可靠)和信号34(可靠),观察下打印结果
结果如下
不可靠信号只出现了一次,可靠信号出现了20次
(2)sigaction()
#include
int sigaction(int signum,const struct sigaction *act,struct sigaction*oldact));
sigaction函数用于改变进程接收到特定信号后的行为。该函数的第一个参数为信号的值,可以为除SIGKILL及SIGSTOP外的任何一个特定有效的信号(为这两个信号定义自己的处理函数,将导致信号安装错误)。第二个参数是指向结构sigaction的一个实例的指针,在结构sigaction的实例中,指定了对特定信号的处理,可以为空,进程会以缺省方式对信号处理;第三个参数oldact指向的对象用来保存原来对相应信号的处理,可指定oldact为NULL。如果把第二、第三个参数都设为NULL,那么该函数可用于检查信号的有效性。
Sigaction的结构体为
struct sigaction
{
void(*sa_handle)(int);
void(*sa_sigaction)(int,siginfo_t*,void*);
sigset_t*mask;//屏蔽信号
intflags;//SA_SIGINFO
void**//保留成员.
}
源码
#include
#include
#include
void hanle1(int s,siginfo_t *info,void*d)
{
printf("handles %d\n",info->si_int);
sleep(5);
printf("handlee\n");
}
int main()
{
structsigaction act={0};
printf("pid%d\n",getpid());
//act.sa_handler=handle;
act.sa_sigaction= hanle1;
sigemptyset(&act.sa_mask);
act.sa_flags=SA_SIGINFO;
sigaction(SIGUSR1,&act,0);
while(1);
}
此程序可以通过sigqueue(后面说明)发送带值得信号
(1) int raise(int sig);
这个比较容易理解,就是对当前进程发送指定信号
(2) int pause(void); 将进程挂起等待信号
直接上源码
#include
#include
void deal(int s)
{
printf("signal%d\n",s);
}
int main()
{
signal(11,deal);
printf("pid%d\n",getpid());
printf("beforepasue\n");
pause();
printf("afterpasue\n");
}
执行结果
总结:如果用了pause,注册了相关的信号处理,那么就会唤醒,继续执行,从打印after pause就能看出,如果你发送了没有注册的信号,那么就会按照系统默认的动作去执行,可能会直接退出程序,这是我们不愿意看到的
(3) int kill(pid_t pid,int sig); 通过进程编号发送信号
上面测试signal的例子已经有用到kill,可供参考
另外,补充kill的pid参数
>0:发送信号到指定进程
=0:发送信号到该进程所在进程组的所有进程
-1:发送给所有进程,除init外
<0:发送给指定的进程组(组ID=绝对值)
(4) unsigned int alarm(unsigned int seconds);指定时间(秒)发送(由内核发送给进程)SIGALRM信号。seconds 为0 时取消所有已设置的alarm请求;
源码
#include
#include
void deal(int s)
{
printf("getup\n");
alarm(5);
}
int main()
{
signal(SIGALRM,deal);
alarm(5);
while(1);
}
此程序是注册下SIGALRM的信号,然后5s做一次信号中断
执行结果为5s打印一次,和我们预期的一样
(5)int sigqueue(pid_t pid,int sig,const unionsigval val);类似于kill函数,多了附带共用体union sigval形数,将共用体中的成员 int sival_int 或 void *sival_ptr 的值传递给 信号处理函数中的定义类型 siginfo_t 中的 int si_int 或 void *si_ptr;
程序源码
#include
#include
#include
int main()
{
union sigvalval;
val.sival_int=9999;
sigqueue(18963,SIGUSR1,val);
}
此程序的意思是像pid 18963的进程发送一个SIGUSR1,并且带有9999 value的信号
(6)int setitimer(int which,const structitimerval *value,struct itimerval *oldvalue); 此函数也是由kernel发送SIGLARM的信号,但是是每隔多久发送,一直发送;
int setitimer(int which,//计时方式//ITIMER_REAL / ITIMER_VIRTUAL /ITIMER_PROF
const struct itimerval *val,//定时器的时间参数
struct itimer *oldval);//返回原来设置的定时器//如果=NULL,则不返回
其中使用的结构体为
struct itimerval
{
struct timevalit_interval;//间隔时间
struct timeval it_value;//延时时间
}
struct timeval
{
long tv_sec;
long tv_usec;
}
源码:
#include
#include
#include
void deal(int s)
{
printf("getup\n");
}
int main()
{
struct itimervalv={0};
signal(SIGALRM,deal);
v.it_value.tv_sec=10;
v.it_interval.tv_sec=1;
setitimer(ITIMER_REAL,&v,0);
while(1);
}
(7) void abort(void)
Abort函数不管进程有没有捕捉处理函数,都会退出当前进程,但是让进程步骤信号的用途是:让进程做必须的清理动作,如果信号处理函数不退出进程,那么当处理函数返回时,改进程也会退出
源码证明这点
结果
此部分为信号屏蔽做铺垫
#include
int sigemptyset(sigset_t *set); 从set信号集中清空所有信号;
int sigfillset(sigset_t *set); 设置所有的信号到set信号集中;
int sigaddset(sigset_t *set, int signum); 在set信号集中加入sig信号;
int sigdelset(sigset_t *set, int signum); 在set信号集中删除sig信号;
int sigismember(const sigset_t *set, int signum);判断一个信号是否在信号集内
int sigprocmask(int how,const sigset_t *set,sigset_t*set); 根据how值,设置阻塞信号集,或释放阻塞的信号集
直接上程序
#include
#include
int main()
{
int sum=0;
int i;
sigset_t sigs;
sigemptyset(&sigs);
sigaddset(&sigs,SIGINT);
sigprocmask(SIG_BLOCK,&sigs,0);
for(i=1;i<=10;i++)
{
sum+=i;
sleep(1);
}
printf("sum%d\n",sum);
sigprocmask(SIG_UNBLOCK,&sigs,0);
printf("over\n");
while(1);
}
执行结果
int sigpending(sigset_t *set); 获取在阻塞中的所有信号;
在上面的例子中直接修改
#include
#include
int main()
{
int sum=0;
int i;
sigset_t sigs;
sigset_t sigp;
sigemptyset(&sigs);
sigemptyset(&sigp);
sigaddset(&sigs,SIGINT);
sigprocmask(SIG_BLOCK,&sigs,0);
for(i=1;i<=10;i++)
{
sum+=i;
sigpending(&sigp);
if(sigismember(&sigp,SIGINT))
printf("HaveSIGINT pending\n");
sleep(1);
}
printf("sum%d\n",sum);
sigprocmask(SIG_UNBLOCK,&sigs,0);
printf("over\n");
while(1);
}
执行结果为
int sigsuspend(const sigset_t *set);
此函数有点绕,比较难以理解,有以下三个作用
1)屏蔽新的信号,原来的信号失效.
2)sigsuspend是阻塞函数.对参数信号屏蔽.
3)对参数没有指定的信号不屏蔽,如果指定的信号有user自己定义的动作,此函数才会返回
4)当sigsuspend返回的时候,恢复旧的屏蔽信号.
程序源码
#include
#include
#include
#include
void handle(int s)
{
printf("USR2signal\n");
}
main()
{
sigset_tsigs;
sigemptyset(&sigs);
sigaddset(&sigs,2);
sigaddset(&sigs,10);
signal(SIGUSR2,handle);
sigsuspend(&sigs);
printf("over!\n");
}
此函数屏蔽了信号2,信号10,如果发送SIGUSR2发现程序返回
此部分发送2信号无效,然后发送SIGUSR2才返回,但是此时恢复了信号2的处理,所以没有打印over
在来个比较高深的例子
#include
#include
void h(int s)
{
printf("抽空处理int信号\n");
}
main()
{
intsum=0;
inti;
//1.
signal(SIGINT,h);
sigset_tsigs,sigp,sigq;
//2.
sigemptyset(&sigs);
sigemptyset(&sigp);
sigemptyset(&sigq);
sigaddset(&sigs,SIGINT);
//3.
sigprocmask(SIG_BLOCK,&sigs,0);
for(i=1;i<=10;i++)
{
sum+=i;
sigpending(&sigp);
if(sigismember(&sigp,SIGINT))
{
printf("SIGINT在排队!\n");
sigsuspend(&sigq);
//使原来屏蔽信号无效,开放原来信号
//使新的信号屏蔽,
//当某个信号处理函数处理完毕
//sigsuspend恢复原来屏蔽信号,返回
}
sleep(1);
}
printf("sum=%d\n",sum);
sigprocmask(SIG_UNBLOCK,&sigs,0);
printf("Over!\n");
}
执行结果,在处理的时候此时我们按下ctrl+c就会打印此部分内容
程序分析:
先注册屏蔽SIGINT2的信号,如果检测到你按下ctrl+c,就会使用sigsuspend是原有信号失效,然后去调用信号处理函数,然后此时sigsuspend返回,回复原有的人信号屏蔽,这部分好绕
部分理论知识内容参考:http://blog.chinaunix.net/uid-25002135-id-3300821.html