什么是信号
信号是由单个词组成的消息。例如,当在终端按Ctrl-C时,内核会向此终端运行的进程发送中段信号,信号的来源有三个地方:
(1) 用户:用户可以输入Ctrl-C或者其他信号来更改进程运行情况。
(2) 内核:当程序执行出错时,内核给进程发送一个信号,如溢出等等。同时内核也利用信号通知进程特定事件的发生。
(3) 进程:一个进程可以通过系统调用kill给另一个进程发送信号。一个进程可以和另一个进程通过信号通信。
由进程的某个操作产生的信号被称为同步信号,由进程外的时间引起的信号被称为异步信号,如用户击键这样的操作。信号列表储存在/usr/include/signal.h文件中。
拿信号SIGINT(中段信号)举例,进程接收到此信号时不一定要消亡,通过调用系统函数signal,可以使用其他的方法处理信号
(1) 接收默认处理:默认处理通常为消亡。进程不一定要使用signal接收默认处理,但是进程能够通过以下调用来恢复默认处理:
signal(SIGINT, SIG_DFL);
(2) 忽略信号:程序可以通过以下调用来告诉内核,它需要忽略SIGINT信号
signal(SIGINT, SIG_IGN);
(3) 调用一个函数:调用此系统函数后,程序能告诉内核,当信号到来时并不是忽略,也不执行默认的操作,而是执行函数(类似回调函数),程序调用如下:
signal(signum, functionname);
系统函数signal的调用规则如下:
#include <signal.h>
/***********************
param signum 需响应的信号
param action 如何响应
return -1 遇到错误
prevaction 成功返回
***********************/
result = signal(int signum, void(*action)(int));
其中,action可以是函数名或以下的两个特殊值之一:
- SIG_IGN 忽略信号
- SIG_DFL 将信号恢复为默认处理
信号与时钟编程
目前最常用的延迟函数为sleep,sleep(n)函数将当前程序挂起n秒或者在此期间被一个不能被忽略的信号的到达所唤醒。
sleep函数的调用有三个步骤组成:
- 为信号SIGALARM设置一个处理函数
- 调用alarm(num_seconds)
- 调用pause
值得注意的是,并非仅有信号SIGALARM可以唤醒进程,任何信号都可以,alarm函数的处理过程如下:
#include <unistd.h>
/*******************************
param seconds 等待的时间(秒)
return -1 出错
old 计时器剩余时间
*******************************/
unsigned old = alarm(unsigned seconds)
如何使用间隔计时器
程序中使用间隔计时器比较麻烦,首先要选择初始间隔和重复间隔。在间隔计时器用的结构体中初始时间是it_value, 重复间隔是it_inteval。如果不想要重复这一特征,将it_inteval设置为0。要把两个时钟都关掉,设置it_value为0.
#include <stdio.h>
#include <sys/time.h>
#include <signal.h>
void countdown(int signum){
static int num = 10;
printf("%d..", num--);
fflush(stdout);
if(num < 0){
printf("DONE! \n");
exit(0);
}
}
int set_ticker(int n_msecs){
struct itimerval new_timeset;
long n_sec,n_usecs;
n_sec = n_msecs/1000;
n_usecs = (n_msecs%1000)*1000L;
new_timeset.it_interval.tv_sec = n_sec;
new_timeset.it_intercal.tv_usec = n_usec;
new_timeset.it_value.tv_sec = n_sec;
new_timeset.it_value.tv_usec = n_usec;
return setitmer(ITIMER_REAL, &new_timeset, NULL);
}
int main(){
signal(SIGALARM, countdown);
if(set_ticker(500) == -1)
perror("set_ticker");
else
while(1)
return 0;
}
代码理解起来很简单,设置定时器,到达固定时间后发出SIGALARM信号,函数countdown随之响应。间隔计时器的设置是通过struct itimerval来完成的。这个结构类型包括初始间隔和重复间隔,两者都储存在struct timecal中:
struct itimerval{
struct timeval it_value; //初始间隔
struct timeval it_interval; //持续间隔
}
struct timeval{
time_t tv_sec; //秒
suseconds tv_usec; //微秒
}
设置定时器和取得定时器的系统函数调用:
#include <sys/time.h>
/**********************************
参数: which 获取或设置的计时器
val 向当前设置值的指针
newval 指向要被设置值的指针
oldval 指向被替换的设置值的指针
返回值: -1 出错
0 成功
**********************************/
result = getitimer(int which, struct itimerval *val);
result = setitimer(int which, const struct itimerval *newval, struct itimerval *oldval);
getitimer将某个特定计时器的当前设置读到val指向的结构中。setitimer将计时器设置为newval指向的结构的值。如果oldval不指向null,之前计时器的设定将被复制到oldval指向的结构中。
信号的处理形式
目前使用的信号处理方式有两种,主要是调用的系统函数不同,分为signal与sigaction。
使用signal处理信号
首先看下面的代码:
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#define INPUTLEN 100
int main(int ac, char *av[]){
void inthandler(int);
void quithandler(int);
char input[INPUTLEN];
int nchars;
signal(SIGINT, inthandler);
signal(SIGQUIT, quithandler);
do{
printf("\nType a message\n");
nchars = read(0, input, (INPUTLEN-1));
if(nchars == -1)
perror("read returned an error");
else{
input[nchars] = '\0';
printf("You typed: %s", input);
}
}
while(strncmp(input, "quit", 4) != 0);
}
void quithandler(int s){
printf("Received signal %d.. waiting\n", s);
sleep(3);
printf("Leaving quithandler \n");
}
void inthandler(int s){
printf("Received signal %d.. waiting\n", s);
sleep(2);
signal(SIGQUIT, quithandler);
}
在程序运行时,若在输入Ctrl-C之后立马输入Ctrl-|,会发现输出如下:
Type a message
^CReceived signal 2.. waiting //信号2代表中段信号,Ctrl—C
^\Received signal 3.. waiting //信号3代表退出信号,Ctrl-|
Leaving quithandler
Leaving inthandler
经过上述的实验可以看出,在处理信号INI时,信号QUIT到达,此时,信号INI的处理函数并没有继续执行,而是首先执行的quithandler,等待quithandler执行完成时,再继续执行inthandler.
为了取消这种信号接收的模式,可以修改inthandler函数,使其在处理INT信号时忽略QUIT信号,待INT信号处理完成时恢复的QUIT信号的处理:
void inthandler(int s){
printf("Received signal %d.. waiting\n", s);
signal(SIGQUIT, SIG_IGN); //处理信号INT时忽略信号QUIT
sleep(2);
printf("Leaving inthandler \n");
signal(SIGQUIT, quithandler); //信号INT处理完成时恢复对信号QUIT的处理形式
}
使用sigaction处理信号
sigaction系统函数相对于signal的功能更加全面,函数原型如下:
#include <signal.h>
/******************************* ********
param: signum 要处理的信号
prevaction 指针,指向描述被替换操作的结构
action 指针,指向描述操作的结构
return -1 失败
0 成功
***************************************/
res = sigaction(int signum, const struct sigaction *action, struct sigaction *prevaction);
参数prevaction可以为null。结构体sigation定义了如何处理一个信号,结构体定义如下
struct sigaction{
void(*sa_handler)();
void(*sa_sigaction)();
sigset_t sa_mask;
int sa_flags;
}
新旧方法的差异在于,使用signal函数处理信号时,无法知晓信号的发出源头,而新方法则可以知悉被调用的原因以及上下 文产生的信息,使用时两者之间的差异如下:
struct sigaction action; action.sa_handler = handler_old;
struct sigaction acton; action.sa_handler = handler_old;
同时只需设置sa_flags的SA_SIGINFO位即可使用新的信号处理方式,sa_flags的的部分控制位如下:
- SA_RESTHNAND 当处理函数被调用时重置,此位被设置将导致信号只会被处理一次
- SA_NODEFER 处理信号时关闭信号自动阻塞,允许递归调用信号处理函数
- SA_RESRART
- SA_SIGINFO 如果此位没设置,那么使用sa_handler指向的处理函数的值。如果设置此位,那么传递给处理函数的将不只是信号编号,还将包括指向描述信号产生的原因和条件的结构体。
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#define INPUTLEN 100
void inthandler(int s){
printf("Called with signal %d\n", s);
sleep(s);
printf("handle done signal %d\n", s);
}
int main(int argc, char *argv[])
{
struct sigaction newhandler;
sigset_t blocked;
char x[INPUTLEN];
newhandler.sa_handler = inthandler;
newhandler.sa_flags = SA_RESTART;
//处理阻塞的信号
sigemptyset(&blocked);
sigaddset(&blocked, SIGQUIT);
newhandler.sa_mask = blocked;
if(sigaction(SIGINT, &newhandler, NULL) == -1)
perror("sigaction");
else
while(1 stdin);
printf("input: %s", x );
}
}
注意,上述处理阻塞信号的方式,设置sa_mask来设置阻塞的信号类型。当程序执行时连续按下Crtl+C与Ctrl+|程序将执行完INT信号的处理函数之后退出。下面将详细描述如何设置信号为阻塞信号:
使用sigprocmask函数可以设置或取消被阻塞的信号集:
#include <signal.h>
/******************************************
param how 如何修改阻塞信号集
sigs 指向使用的信号集的指针
prev 指向之前的阻塞信号集的指针
return -1 失败
0 成功
*******************************************/
int res = sigprocmask(int how, const sigset_t *sigs, sigset_t *prev);
how的值可以为SIG_BLOCK, SIG_UNBLOCK, SIG_SET。表示将sigs指定的信号添加,删除或者替换。
信号集可以用sigsetops函数来进行设置:
sigempty(sigset_t *setp) //清除列表中的信号
sigfillset(sigset_t *setp) //添加所有的信号到setp中
sigaddset(sigset *setp, int signum) //添加信号signum到setp信号列表
sigdelset(sigset *setp, int signum) //删除信号