一、什么是信号
信号是Linux系统中唯一的异步通信机制,也可以看作是异步通知,通知接收信号的进程有某种事件发生,这类似于DOS下的int或Windows下的事件。信号是在软件层面上对中断机制的一种模拟,进程收到一个信号类似于处理器收到一个中断请求。由于信号是异步的,进程不必等待信号的到达,事实上,它也不知道信号何时会到达。
信号一般是由系统中一些特定事件引起的,主要包括如下。
1.硬件故障
2.程序运行中的错误,例如除数为0,或者访问进程以外的内存区域。
3.进程的子程序终止
4.用户从终端向进程发送终止等信号
5.进程调用kill,raise,以及alarm等函数向其他进程发送信号。
这里需要注意一下:并不是程序中的所有错误都会产生信号。例如,调用库函数时发生的错误,一般都是通过返回值和错误码来报告错误的发生。只有那些在程序的任何位置都可能发生的错误,才会产生信号,报告错误的发生。
进程收到信号后,对于特定的信号,例如SIGKILL和SIGSTOP信号,处理方式是确定的,对于大部分信号,进程可以选择不同的响应方式:
1.捕获信号,这类似于中断处理程序,对于需要处理的信号,进程可以指定相应的函数来进行处理。
2.忽略信号,对信号不进行处理,就像未收到一样,有两个信号不能忽略,即SIGKILL和SIGSTOP信号。
3.让Linux内核执行与信号对于的默认动作,对于大部分信号而言,默认的处理方式是终止相应的程序。
进程可以通过signal或sigaction函数来选择具体的响应方式。
二、信号的类型
Linux系统中,可以使用kill -l命令来列出系统中所有的信号。
其中编号为1-31的信号是从早期的unix系统中继承过来的,由于在传送的过程中可能会丢失,所以也称为不可靠信号或非实时信号。这些信号已经有了确定的用途和含义,并且每种信号都有各自的默认处理方式。例如:用户在键盘上按下Ctrl+C时,会产生SIGINT信号,系统对该信号的默认处理方式就是终止相应的进程。编号为32-64的信号是后来扩充的,它解决了传送过程中可能丢失的问题,称为可靠消息或实时消息,这些信号是POSIX标准的一部分,可用于应用进程。
部分信号类型说明:
三、信号处理函数
1、signal函数
signal函数的一般形式如下:
#include
void(*signal(int sig,void(*handler)(int)))(int);
(1)SIG_IGN:忽略参数sig所指定的信号。
(2)SIG_DFL:采用系统默认方式处理sig所指定信号。
下面编写一个程序,使用signal函数设置SIGINT和SIGQUIT信号的处理函数
//使用signal函数设置sigint和sigquit信号的处理函数
#include
#include
#include
#include
void sig_handler(int sig)//信号处理函数
{
switch (sig)
{
case 2: //处理信号SIGINT信号 SIGINT==2 Ctrl+C
printf("received signal:sigint\n");
break;
case 3: //处理信号SIGQUIT信号 SIGQUIT==3 Ctrl+\
printf("received signal:sigquit\n");
break;
default:
exit(1);
}
}
int main()
{
printf("PID:%d\n",getpid()); //输出进程的标识符
signal(SIGINT, sig_handler); //设置SIGINT信号的处理函数
signal(SIGQUIT, sig_handler); //设置SIGQUIT信号的处理函数
for (; ; ) {
}
return 0;
}
2.sigaction函数
sigaction函数和signal函数类似,只是它支持信号带有参数,进而可以传递消息给处理函数。该函数的一般形式如下:
#include
int sigaction(int sig, const struct sigaction *act, struct sigaction *oact);
参数sig为要设置处理函数的信号;参数act用来设定信号的处理函数,如果为NULL,则系统会以默认方式对信号进行处理;参数oact用来保存信号以前的处理信息,一般情况下设置为NULL即可。下面介绍sigaction结构,其中包含了对指定信号的处理函数,信号所传递的信息,信号处理过程中应当被阻塞其他信号等信息。它的定义如下:
struct sigaction {
union {
__sighandler_t sa_handler;
void (*sa_sigaction)(int, struct siginfo *, void *);
} _u;
sigset_t sa_mask;
unsigned long sa_flags;
void (*sa_restorer)(void);
};
sa_mask用来指定在信号处理过程中,哪些信号应当被阻塞;sig_flags中包含许多标志位,其中比较重要的一个为SA_SIGINFO,它表示信号附带的参数可以被传递到信号处理函数之中;sa_restorer现在已不再使用。
注意:如果要传递消息,必须使用sa_sigaction来设定信号的处理函数,同时设置SA_SIGINFO标志位。
例:使用sigaction函数设置SIGINT信号的处理函数。
//使用sigaction函数设置SIGINT信号的处理函数
#include
#include
#include
#include
void sig_handler(int sig,siginfo_t *info,void *t) //信号处理函数
{
printf("Receive signal:%d\n",sig);
return;
}
int main()
{
int status;
struct sigaction act; //定义sigaction结构
act.sa_sigaction=sig_handler; //使用sa_sigaction来设定处理函数
sigemptyset(&act.sa_mask); //清空信号集中的所有信号。
act.sa_flags=SA_SIGINFO; //设置SA_SIGINFO标志位
status=sigaction(SIGINT, &act, NULL); //设置SIGINT信号的处理函数
if (status<0)
{
printf("error\n");
}
for (; ; ) {
}
return 0;
}
四、信号发送函数
Linux系统中最常使用的信号发送函数主要有:kill,raise,alarm以及setitimer.
1.kill 函数
kill函数用于向进程或进程组发送一个信号。
#include
#include
int kill(pid_t pid,int sig)
参数:
pid:可能选择有以下四种
1. pid大于零时,pid是信号欲送往的进程的标识。
2. pid等于零时,信号将送往所有与调用kill()的那个进程属同一个使用组的进程。
3. pid等于-1时,信号将送往所有调用进程有权给其发送信号的进程,除了进程1(init)。
4. pid小于-1时,信号将送往以-pid为组标识的进程。
sig:准备发送的信号代码,假如其值为零则没有任何信号送出,但是系统会执行错误检查,通常会利用sig值为零来检验某个进程是否仍在执行。
返回值说明: 成功执行时,返回0。失败返回-1,errno被设为以下的某个值
EINVAL:指定的信号码无效(参数 sig 不合法)
EPERM;权限不够无法传送信号给指定进程
ESRCH:参数 pid 所指定的进程或进程组不存在
2.raise函数raise函数用于向进程本身发送信号。
#include
int raise(int sig);
参数sig为要发送的信号值,该函数执行成功后,返回值为0,否则返回-1.
3.abort函数
abort函数用于向进程发送SIGABORT信号,默认情况下进程会异常退出,当然,用户也可以定义自己的信号处理函数。
#include
void abort(void);
该函数没有参数和返回值。
4.sigqueue函数
sigqueue函数用于向进程发送信号,同时还支持附加信息的传递。
#include
int sigqueue(pid_t pid, int sig, const union sigval value);
参数:
返回值:成功返回0,失败返回-1。
联合数据结构union sigval:
typedef union sigval
{
int sival_int;
void *sival_ptr;
}sigval_t;
调用sigqueue函数时,sigval_t所指定的信息将会被复制到前面介绍的sa_sigaction的siginfo_t结构中,这样信号处理函数就可以处理这些信息了。
sigqueue()比kill()传递了更多的附加信息,但sigqueue()只能向一个进程发送信号,而不能发送信号给一个进程组。
例:在不同进程间实现信号发送和接收,同时在传递过程中附加其它信息。
//在不同的进程间实现信号发送和接收,同时在传递过程中附加其他信息(发送进程)
#include
#include
#include
#include
#include
#include
#include
#include
#include
int main()
{
int status;
pid_t pid;
union sigval sg;
printf("发送进程PID:%d\n",getpid());
printf("输入接收进程的PID:");
scanf("%d",&pid);
sg.sival_int=getpid(); //获取当前进程的标识符,作为附加信息发送出去
status=sigqueue(pid,SIGUSR1,sg); //发送信号
if (status<0)
{
printf("send error\n");
}
else
{
printf("Done\n");
}
return 0;
}
//在不同的进程间实现信号发送和接收,同时在传递过程中附加其他信息(接收进程)
#include
#include
#include
#include
void sig_handler(int sig,siginfo_t *info,void *t) //信号处理函数
{
printf("\nreceive signal:%d\n",sig); //输出收到的信号值
printf("receive message:%d\n",info->si_pid); //输出接收到的附加信息,这里是发送进程的PID
}
int main()
{
int status;
pid_t pid;
struct sigaction act; //定义sigaction结构
pid=getpid(); //获取当前进程的标志符
printf("接收进程的PID:%d",pid);
act.sa_sigaction=sig_handler; //使用sa_sigaction来设置信号处理函数
sigemptyset(&act.sa_mask); //清空信号集中的所有信号
act.sa_flags=SA_SIGINFO; //设置SA_SIGINFO标志位
status=sigaction(SIGUSR1, &act, NULL);//设置SIGUSR1信号的处理函数
if (status<0)
{
printf("sigaction error\n");
}
printf("\nreceiver:\n");
for (; ; ) {
}
return 0;
}
5.alarm函数
alarm函数用于在系统中设置一个定时器,计时到达后向进程发送SIGALRM信号。
#include
unsigned int alarm(unsigned int seconds);
如果在上一次调用alarm函数所设定的时间到达之前,再次调用alarm函数,则函数的返回值为上一次设定的剩余时间,同时,新设定的时间将取代原来的时间。
//使用alarm函数在系统中设置一个定时器
#include
#include
#include
int main()
{
int i;
alarm(1); //设置定时器
i=0;
while (1)
{
printf("i= %d\n",i);
sleep(0.5);
i++;
}
return 0;
}
setitimer函数用来设置定时器,getitimer函数用来读取定时器的状态。
#include
#include
int getitimer(int which,struct itimerval *value);
int setitimer(int which,const struct itimerval *value,struct itimerval *ovalue);
其中which参数表示类型,可选的值有:
EFAULT:参数value或者value是一个无效的指针。
EINVAL: 参数which错误。
value和ovalue均为itimerval结构体:
struct itimerval {
struct timeval it_interval; /* 下次超时时间 */
struct timeval it_value; /* 当前超时时间 */
};
struct timeval {
time_t tv_sec; /* 秒 */
suseconds_t tv_usec; /* 微秒 */
};
settimer工作机制是,先对it_value倒计时,当it_value为零时触发信号,然后重置为it_interval,继续对it_value倒计时,一直这样循环下去。
基于此机制,setitimer既可以用来延时执行,也可定时执行。若it_interval的值为0,则定时器超时后不再重新自动启动
假如it_value为0是不会触发信号的(定时器将停止工作并且也不会重新自动启动),所以要能触发信号,it_value得大于0;如果it_interval为零,只会延时,不会定时(也就是说只会触发一次信号)。
ovalue参数,通常用不上,设置为NULL,它是用来存储上一次setitimer调用时设置的value值。
以下是一个简单的使用例子:
#include
#include
#include
#include
void signalHandler(int signo)
{
switch (signo){
case SIGALRM:
printf("Caught the SIGALRM signal!\n");
break;
}
}
int main(int argc, char *argv[])
{
signal(SIGALRM, signalHandler);
struct itimerval new_value, old_value;
new_value.it_value.tv_sec = 5; //5秒钟后将启动定时器
new_value.it_value.tv_usec = 1;
new_value.it_interval.tv_sec = 1; //定时器启动后,每隔1秒将执行相应的函数
new_value.it_interval.tv_usec = 0;
setitimer(ITIMER_REAL, &new_value, &old_value);
for(;;);
return 0;
}
我们已经知道,我们可以通过信号来终止进程,也可以通过信号来在进程间进行通信,程序也可以通过指定信号的关联处理函数来改变信号的默认处理方式,也可以屏蔽某些信号,使其不能传递给进程。那么我们应该如何设定我们需要处理的信号,我们不需要处理哪些信号等问题呢?信号集函数就是帮助我们解决这些问题的。
下面是信号函数集:
SIG_BLOCK:将信号集set添加到当前进程的阻塞集合中。
SIG_UNBLOCK:从当前进程的阻塞集合中删除信号集set中的信号,即删除信号集set中的信号,即解除该信号的阻塞。
SIG_SETMASK:将信号集set设置为信号阻塞集合。
#include
#include
#include
#include
void sig_handler(int sig)//信号处理函数
{
printf("received signal:SIGINT\n");
return;
}
int main()
{
sigset_t set; //定义信号集
sigemptyset(&set); //初始化信号集,清空所有信号
sigaddset(&set, SIGINT); //将SIGINT信号添加到信号集中
signal(SIGINT, sig_handler); //设置SIGINT信号的处理函数
while (1)
{
sigprocmask(SIG_BLOCK, &set, NULL); //阻塞信号
printf("SIGINT is blocked\n");
sleep(5);
sigprocmask(SIG_UNBLOCK, &set, NULL); //解除阻塞
printf("SIGINT is unblocked\n");
sleep(5);
}
return 0;
}
#include
#include
#include
#include
#include
//倒计时值设置,重复到时,超时值设为1秒
struct itimerval val_alarm={.it_interval.tv_sec=1,.it_interval.tv_usec=0,.it_value.tv_sec=1,.it_value.tv_usec=0};
//sigalrm信号处理函数
void sig_handler(int sig)
{
printf("Timer has expired,elapsed %d seconds.\n",val_alarm.it_value.tv_sec);
}
int main()
{
struct sigaction sa;
memset(&sa, 0, sizeof(sa));
sa.sa_handler=&sig_handler;
if(sigaction(SIGALRM, &sa, NULL)==-1)
perror("Failed to call sigaction");
//使用初始val_alarm的值启动定时器
if(setitimer(ITIMER_REAL, &val_alarm, NULL)==-1)
perror("failed to call setitimer");
while (1)
{}
return 0;
}
//使进程在某段时间内阻塞信号集中的信号
#include
#include
#include
#include
void sig_handler(int sig)//信号处理函数
{
printf("received signal:SIGINT\n");
return;
}
int main()
{
sigset_t set; //定义信号集
sigemptyset(&set); //初始化信号集,清空所有信号
sigaddset(&set, SIGINT); //将SIGINT信号添加到信号集中
signal(SIGINT, sig_handler); //设置SIGINT信号的处理函数
while (1)
{
getchar();
sigprocmask(SIG_BLOCK, &set, NULL); //阻塞信号
printf("SIGINT is blocked\n");
sleep(5);
sigprocmask(SIG_UNBLOCK, &set, NULL); //解除阻塞
printf("SIGINT is unblocked\n");
sleep(5);
}
return 0;
}