本文参考《LinuxC编程实战》
信号的来源
信号来源按产生体哦啊间的不同分为硬件和软件两种方式。
1.硬件方式:
1). 用户按下某些键所产生的信号。如<ctrl + C>组合键产生一个SIGINT信号。
2). 硬件异常产生的一些信号。
2.软件方式:
1). 用户在终端下用kill命令向进程发送任意信号;
2). 进程调用kill 或 sigqueue 函数发送信号;
3). 当检测到某种软件已经具备时发出信号;如alarm和settimer设置的定时超时产生的SIGALRM信号;
信号的种类
在shell下输入命令kill -l 可以显示Linux系统支持的全部信息。信号值的定义在signal.h中:(这里只列举一个)
例如:
SIGINT:用户按下<ctrl+C>组合键产生该信号,终端向正在运行的由终端启动的程序发送此信号。默认动作为终止进程并产生core文件。
该信号在signal.h中的定义为:
#include <signal.h>
#define SIGINT 2
从这里可以看到SIGINT的值为2,经过kill -l 查看有64个信号值,他们的值分别从1到64;而前30个信号以被linux系统定义,31-64为linux的实时信号,他们没有固定的含义(可以由用户自己定义)
注意:linux线程机制使用了前3个实时信号,所有的事实信号默认为终止进程。
1.可靠信号和不可靠信号
类型 | 范围 | 性质 |
---|---|---|
可靠信号 | 1号-31号 | 信号不会丢失,支持排队,递送多次 |
不可靠信号 | 33号-64号 | 信号会丢失,不支持排队只被递送一次 |
2.信号的优先级
如果一个进程有多个未决信号,则值(编号)越小越先被递送。linux系统和大多数POSIX标准的操作系统将优先递送不可靠信号。
信号的捕捉和处理
该操作主要有signal和sigaction函数来完成。
signal函数
该函数用来设置进程接收到信号时的动作,函数原型:
#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum,sighandler_t handler);
参数signum用来设置处理的信号,但接收到该信号便会跳转到handler所指的函数执行; 如果handler不是函数指针,则必须是SIG_IGN(忽略该信号)或SIG_DFL(按默认执行)。handler为函数指针,它所指的函数类型为sighandler_t,即函数有一个int参数且为void型。
例signal1.c:
/*****************************************************
* 用signal函数忽略<ctrl + C>产生的SIGINT函数,来防止被杀死(可以用<ctrl + \>杀死)
* ***************************************************/
#include <stdio.h>
#include <signal.h>
int main()
{
signal(SIGINT, SIG_IGN); //防止ctrl + C杀死程序
while(1) //制造死循环
{
printf("A\n");
}
return 0;
}
例signal2.c:
/*****************************************************
* 用signal函数接收指定信号,并设置函数响应
* ***************************************************/
#include <stdio.h>
#include <signal.h>
void hand(int num)
{
printf("\n<ctrl+ C>\n");
}
int main()
{
signal(SIGINT, hand); //防止ctrl + C杀死程序
while(1) //制造死循环
{
;
}
return 0;
}
例signal3.c
/*****************************************************
* 用signal函数,理解不可靠信息SIGINT
* ***************************************************/
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
void hand(int num)
{
sleep(3);
printf("\n<ctrl+ C>\n");
}
int main()
{
signal(SIGINT, hand); //防止ctrl + C杀死程序
while(1) //制造死循环
{
;
}
return 0;
}
sigaction函数
该函数可用来检查或设置进程在接收到信号后的动作,执行成功返回0,错误失败返回-1,错误代码存入errno,函数原型为:
#include <signal.h>
int sigaction(int signum, const struct sigaction *act,struct sigaction *oldact);
参数signum为信号编码;act若不为空则为signum信号设置新的信号处理函数;oldact若不为空则储存旧的信号处理函数。struct sigaction的定义如下:
struct sigaction{
void (*sa_handler)(int); //函数指针,可设置为SIG_DFL或SIG_IGN
void (*sa_sigaction)(int,siginfo_t *,void *); //函数指针
sigset_t sa_mask;
int sa_flags;
void (*sa_restorer)(void); //以作废不使用
}
参数sa_mask声明了一个信号屏蔽集,调用处理函数前将处理的信号加入信号屏蔽集中,新的信号屏蔽码会自动包括正在处理的信号,当从信号捕捉函数返回时,进程的信号屏蔽码会恢复为原来的值。因此,当处理一个给定的信号时,如果这种信号再次发生,那么它会被阻塞直到本次信号处理结束为止。若这种信号发生了多次,则对于不可靠信号,它只会被阻塞一次, 即本次信号处理结束以后只会再处理- -次(只有一个在排队,相当于丢失了信号);对于可靠信号(实时信号),则会被阻塞多次,即信号不会丢失,信号发生了多少次就会调用信号处理函数多少次(都在排队)。
sa flags成员用来说明信号处理的一一些其他相关操作,它可以取以下值或它们的组合。
当使用sa_aigaction来指定处理函数时,第二个参数定义如下:
pause函数
该函数使调用进程挂起直到捕捉到一个信号,函数原型为
#include <unistd.h>
int pause(void);
pause函数会令目前进程暂停(进入睡眠状态),知道被信号(signal)所中断。该函数只返回-1,并将errno设置为EINTR。
信号处理函数的返回
信号处理函数可以正常返回,也可以调用其他函数返回到程序的主函数中,而不是从该处理程序返回。信号处理程序可以返回或者调用abort. exit或longjmo(goto不支持跳出它所在的函数,因此不能用来从信号处理程序返回到主函数中)
setjmp/longjmp函数
使用longjmp函数可以跳转到setjmp设置的位置,函数原型为:
#include <setjmp.h>
int setjmp(jmp_buf env);
void longjmp(jmp_buf env, int val);
参数env 是一个特殊类型jmp_ buf的变量。这一数据类型是某种形式的数组,其中存放的是在调用longimp时能用来恢复栈状态的所有信息。一般来说 env是个全局变量,因为需从另一个函数中引用它。我们可以在希望返回的位置使用setimp,直接调用setimp时返回0;当从longjmp返回时,setimp 的返回值是longimp的第2个参数的值,可以利用这一点使多个longjmp返回到一个 setjimp处,下面例题演示了setjmp 和longjmp丽数的用法。
#include <stdio.h>
#include <setjmp.h>
#include <unistd.h>
#include <signal.h>
jmp_buf env; //保存跳转位置的栈信息
void handler_sigrtmin15(int signo)
{
printf("recv SIGRTMIN+15\n");
longjmp(env,1);
}
void handler_sigrtmax15(int signo)
{
printf("recv SIGRTMAX-15\n");
longjmp(env,2);
}
int main()
{
int pid;
printf("pid = %d\n",getpid());
switch(setjmp(env))
{
case 0:
{
break;
}
case 1:
{
printf("return from SIGRTMIN+15\n");
break;
}
case 2:
{
printf("return from SIGRTMAX-15\n");
break;
}
default:
{
break;
}
}
pid = getpid();
signal(SIGRTMIN+15,handler_sigrtmin15);
signal(SIGRTMIN-15,handler_sigrtmax15);
while(1)
;
return 0;
}
注意:应为信号处理函数在处理信号好时会屏蔽该信号,直到信号处理函数返回时结束,但使用longjmp时信号处理函数不是正常返回,所以longjmp使用一次后,该信号会被永久屏蔽,除非手动处理。 下面两个函数可以解决这个问题。
sigsetjmp/siglongjmp函数
函数原型为:
#include <stdio.h>
int sigsetjmp(sigjmp_buf env, int savesigs);
void siglongjmp(sigjmp_buf env, int val);
这两个函数与上面的区别在与sigsetjmp函数多了一个参数savesigs,如果它非0,则sigsetjmp在env中保存当前信号屏蔽字,在调用siglongjmp时会以env恢复原来的信号屏蔽字(savesigs只要非0及可)。
信号的发送
kill函数
该函数用来发送信号给指定进程,函数原型为:
#include <sys/types.h>
#include <signal.h>
int kill(pid_t pid, int sig);
函数的行为与pid有关,第二个参数sig表示信号编码。
- pid为正数,将信号发送给ID为pid的进程
- pid为0, 将信号发送给当前进程所属进程组里的所有进程
- pid为-1,将信号广播至系统除1号进程(init进程)和自身以外的所有进程
- pid为比-1还小的数,则发送信号给属于进程组-pid的所有进程
- pid为0,kill()仍执行正常的错误检查,不发送信号。
函数执行成功返回0,失败错误返回-1。
注意:只有root权限才能向任意进程发送信号,非root权限的进程只能向属于同一进程组或同一用户的进程发送信号。
sigqueue函数
该函数支持信号带有参数,从而可以配合sigaction使用。函数原型为:
#include <signal.h>
int sigquwuw(pid_t pid, int sig, const union sigval value);
sigqueue与kill不同的是他不能给一组进程发信号。参数value是一个共用体。
unionsignal{
int sival_int;
void *sival_ptr;
};
所以信号所携带的参数要么是整型,要么是void型指针。
alarm函数
该函数可以用来设定秒数,定时器超过将产生SIGALRM信号给调用进程。函数原型为:
#include <unistd.h>
unsigned int alarm(unsigned int seconds);
参数seconds表示设定的秒数,经过seconds后,内核给调用该函数的进程发送SIGALRM信号。如果seconds为0,则不要再发送SIGALRM信号。最新一次调用alarm将取消之前的设定。如果之前以经调用过alarm,则返回剩余的时间;如果之前没有设置过计时器则返回0。
注意:alarm只发送一次信号,要多次发送需要多次调用alarm函数
getitimer/setitimer函数
该函数与alarm函数一样,都是用来设置定时器,他们使用同一个定时器,所以会相互影响。该函数据有更多的功能,函数原型为:
#include <sys/time.h>
int getitimer(int which, struct itimerval *value);
int setitimer(int which, const struct itimerval *value, struct itimerval *ovalue);
参数value指定时器的时间,结构体的定义如下:
struct itimerval{
struct timeval it_interval;
struct timeval it_value;
}
struct timeval{
lonf tv_sec; //秒速
long tv_usec; //微秒
}
对于函数getitimer,如果存在由which指定的定时器,则将剩余时间保存在it _value中,该定时器的初始值保存在it _interval 中:如果不存在指定类型的定时器,则将value置为0返回。执行成功返回0,当有错误发生时则返回-1,错误代码存入ermo中,详细的错误代码说明请参考man手册。对于函数setitimer,参数ovalue如果不是空指针,则将在其中保存上次设定的定时器的值。定时器从value递减为0时,产生一个信号,并将it _value 的值设为it inteval,然后重新开始计时,如此周而复始。仅当it value 的值为0或者计时到达而it interval 的值为0时,停止计时。执行成功返回0,当有错误发生时则返回-1.
abort函数
该函数用来想进程发送SIGABRT信号,函数原型为:
#include <stdlib.h>
void abort(void);
如果进程设置信号处理函数以捕捉SIGABRT信号,且信号处理函数不返回(如使用longjmp),则abort()无法关闭进程。该函数没有返回值。
信号屏蔽
信号集
信号总数目达64个,超过了一个整型数能表示的位数(一个整型变量通常为32位),因此不能用整型量中的一位代表一种信号。POSIX 标准定义了数据类型sigset t来表示信号集,并且定义了一系列函数来操作信号集。它们的函数原型如下:
#include <signal.h>
int sigemptyset(sigset_t *set);
int sigfillset(sigset_t *set);
int sigaddset(sigset_t *set, int signum);
int sigdelset(sigset_t *set, int signum);
int sigismember(const sigset_t *set, int signum);
这些函数的含义如下:
- 函数 sigemptyset用来初始化一个信号集, 使其不包括任何信号。
- 函数sifillet用来初始化一个信号集,使其包括所有信号。
- 函数sigaddset用来向set指定的信号集中添加由signum指定的信号。
- 函数 sigdelset用来从set指定的信号集中删除由signum指定的信号。
- 函数 sigismember用来测试信号signum是否包括在set指定的信号集中。
函数sigemptyset. sille sigaddset 以及sigdelset 在执行成功时返回0,失败返回-1。函数sigismember返回I表示测试的信号在信号集中,返回0表示测试的信号不在信号集中,出错返回-1。
注意:所有应用程序在使用信号集前,要对该信号集调用一次sigemptyset或silletl以初始化信号集。这是因为C语言编译器将不赋初值的外部和静态度量都初始化为0.
信号屏蔽
信号屏蔽又称信号阻塞,该函数的原型为:
#include <signal.h>
int sigprocmask(int how, const sigset_t *set, sigset_t * oldset);
int sigpending(sigset_t * set);
int sigsuspend(const sigset_t *mask);
这些函数的含义如下:
sigprocmask函数
每个进程都有一个信号屏蔽码,它规定了当前阻塞而不能递送给该进程的信号集。调用函数sigprocmask可以检测或更改进程的信号屏蔽码。如果参数oldset是非空指针,则该进程之前的信号屏蔽码通过oldset返回:如果参数set是非空指针,则该函数将根据参数how来修改信号当前屏蔽码,how的取值如下。
该函数执行成功返回0,错误失败返回-1。
how中前两种参数较难理解,可以配合下面代码理解:
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#include <stdlib.h>
void my_err(const char * err_string, int line)
{
fprintf(stderr, "line:%d ", line);
perror(err_string);
exit(1);
}
void handler_sigint(int signo) //SIGINT信号处理函数
{
printf("\nrecv SIGINT\n");
}
int main()
{
sigset_t newmask, oldmask, zeromask;
if(signal(SIGINT, handler_sigint) == SIG_ERR)
{
my_err("signal",__LINE__);
}
sigemptyset(&newmask); //初始化一个空的信号集newmask
sigemptyset(&zeromask); //初始化一个空的信号集
sigaddset(&newmask, SIGINT); //向newmask信号集加入了SIGINT信号
if(sigprocmask(SIG_BLOCK,&newmask,&oldmask) < 0) //将newmask加入信号屏蔽集
//if(sigprocmask(SIG_UNBLOCK,&newmask,&oldmask) < 0) //将删除信号品比集中newmask中的内容
{
my_err("sigprocmask",__LINE__);
}
else
{
printf("SIGINT blocked\n");
}
sleep(10);
sigpending(&zeromask); //将为决信号放如zeromask
if(sigismember(&zeromask, SIGINT) == 1)
{
printf("SIGINT is in pending queue\n");
}
else
{
printf("SIGINT is not in pending queue\n");
}
if(sigprocmask(SIG_UNBLOCK,&newmask,&oldmask) < 0) //将删除信号品比集中newmask中的内容
{
my_err("sigprocmask",__LINE__);
}
else
{
printf("SIGINT unblocked\n");
}
while(1)
;
return 0;
}
sigpending函数
该函数用来获取调用进程因被阻塞而不能递送和当前为决的信号集,该信号集通过参数set返回。执行成功返回0,错误失败返回-1。
例:sig_mask.c
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <signal.h>
void my_err(const char *err_string, int line)
{
fprintf(stderr, "line:%d ",line);
perror(err_string);
exit(1);
}
void hander_sigint(int signo)
{
printf("\nrecv SIGINT\n");
}
int main()
{
sigset_t newmask, oldmask, pendmask; //定义信号集
if(signal(SIGINT,hander_sigint) == SIG_ERR)
{
my_err("signal",__LINE__);
}
sleep(10); //留出按<ctrl + C>的时间
sigemptyset(&newmask); //初始化一个信号集,不包括任何信号
sigaddset(&oldmask,SIGINT); //向oldmask信号集中添加SIGINT信号
if(sigprocmask(SIG_UNBLOCK, &newmask,&oldmask) < 0)
{
my_err("sigprocmask",__LINE__);
}
else
{
printf("SIGINT blocked\n"); //SIGINT阻塞
}
sleep(10); //留出按<ctrl + C>的时间
if(sigpending(&pendmask) < 0) //将未决信号集返回给pendmask信号集
{
my_err("sigpending",__LINE__);
}
switch(sigismember(&pendmask, SIGINT)) //检测信号是否在pendmask信号集中
{
case 0:
{
printf("SIGINT is not in pending queue\n");
break;
}
case 1:
{
printf("SIGINT is in pending queue\n");
break;
}
case -1:
{
my_err("sigismember",__LINE__);
break;
}
default:
break;
}
if(sigprocmask(SIG_SETMASK,&oldmask,NULL) < 0) //将信号屏蔽码设置为oldmask信号集
{
my_err("sigprocmask",__LINE__);
}
else
{
printf("SIGINT unblocked\n"); //SIGINT没有阻塞
}
while(1)
;
return 0;
}
sigsuspend函数
函数sigsuspend将进程的信号屏蔽码设置为mask,然后与pause函数-样等待信号的发生并执行完信号处理函数。信号处理函数执行完后再把进程的信号屏蔽码设置为原来的屏蔽字,然后sigsuspend函数才返回。sigsuspend 函数保证改变进程的屏蔽码和将进程挂起等待信号是原子操作。sigsuspend函数总是返回-1,并将errno置为EINTR。
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#include <stdlib.h>
void my_err(const char * err_string, int line)
{
fprintf(stderr, "line:%d ", line);
perror(err_string);
exit(1);
}
void handler_sigint(int signo) //SIGINT信号处理函数
{
printf("\nrecv SIGINT\n");
}
int main()
{
sigset_t newmask, oldmask, zeromask; //定义三个信号集
if(signal(SIGINT, handler_sigint) == SIG_ERR) //接收信号
{
my_err("signal",__LINE__);
}
sigemptyset(&newmask); //初始化一个空的信号集newmask
sigemptyset(&zeromask); //初始化一个空的信号集
sigaddset(&newmask, SIGINT); //向newmask信号集加入了SIGINT信号
if(sigprocmask(SIG_BLOCK,&newmask,&oldmask) < 0) //将newmask中的信号加入信号屏蔽集
{
my_err("sigprocmask",__LINE__);
}
else
{
printf("SIGINT blocked\n"); //加入成功,SIGINT被阻塞
}
if(sigsuspend(&zeromask) != -1) //将信号屏蔽集设置为zeromask(为空),即相当于取消所有的信号屏蔽,并等待信号
{
my_err("sigsuspend",__LINE__);
}
else
{
printf("recv a signo, return from sigsuspend\n"); //取消成功,当前信号屏蔽集为空
}
if(sigprocmask(SIG_SETMASK,&oldmask,NULL) < 0) //将oldmask设置为信号屏蔽集
{
my_err("sigprocmask",__LINE__);
}
pause(); //等待信号
if(sigprocmask(SIG_SETMASK,&oldmask,NULL) < 0) //将oldmask设置为信号屏蔽集
{
my_err("sigprocmask",__LINE__);
}
while(1)
;
return 0;
}