三、信号
信号时linux中进程间的一种重要的通信方式。信号是在软件层次上对中断的一种模拟,也叫做软中断。信号是linux进程间通信机制中唯一的异步通信机制。信号通信机制包括信号的发送,信号的响应,信号的安装,信号集与信号集操作函数,以及信号的阻塞和未决。Linux中总共包括了64中信号,前32种信号是不可靠的信号(非实时信号:不支持排队);后32种是可靠地信号(即有效的解决了信号丢失的问题,且是实时信号:支持信号的排队)。下面列举出ubuntu 中64中信号: 终端中输入命令: kill -l
下面依次地介绍信号通信机制中的几种操作:
1、 信号的响应
进程可以通过三种方式对信号进行响应:1)忽略信号,对信号不做任何处理;但SIGKILL 和SIGSTOP是不能被忽略的。2)捕捉信号,定义信号处理函数,当信号发生时执行信号处理函数。3)执行缺省操作,linux对每种信号都定义了缺省操作。
2、信号的发送
发送信号的函数主要包括:kill(); raise(); sigqueue(); alarm(); setitimer(); 以及abort();
1).kill()
#include<sys/types.h>
#include<signal.h>
int kill(pid_t pid, int signum);
其中pid是进程的ID;signum是信号值,当为0时(即空信号),实际不发送任何信号,但照常进行错误检查,因此,可用于检查目标进程是否存在,以及当前进程是否具有向目标发送信号的权限(root权限的进程可以向任何进程发送信号,非root权限的进程只能向属于同一个session或者同一个用户的进程发送信号)。
参数pid |
意义 |
=0 |
同一个进程组的进程 |
>0 |
ID为pid的进程 |
<0且不等于-1 |
ID为-pid的进程组 |
=-1 |
除发送进程自身外,所有进程ID大于1的进程 |
2).raise()
#include<signal.h>
int raise(int signum)
向进程本身发送信号,信号值为函数的参数signum。
3).alarm()
#inlcude<unistd.h>
unsigned int alarm(unsigned int seconds)
alarm()又称作闹钟函数,可以在进程中设置一个定时器,当定时时间到了,内核就会向进程发送一个SIGALRM信号。seconds为定时的秒数,如果该参数为0,则取消进程中所有的alarm()定时作用。如果alarm()函数之前有alarm()存在,则返回之前alarm()定时的剩余时间,否则返回0。且后来定义的alarm()函数会清除前面定义的alarm()函数的作用。
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<signal.h>
#include <sys/types.h>
void Sigalarm_func(int signum)
{
if(SIGALRM == signum)
printf("enter alarm!\n");
exit(EXIT_SUCCESS);
}
int main()
{
int n =0;
int ncount = 0;
signal(SIGALRM, Sigalarm_func);
n = alarm(10);
printf("now n is :%d\n", n);
sleep(6);
n = alarm(5);
printf("now n is :%d\n", n);
int i;
for(i = 0; i<10; i++)
{
printf("now ncount is: %d\n",ncount);
ncount++;
sleep(1);
}
return 0;
}
输出结果如下:
从上面可以看出后面定义的alarm(5)取消了alarm(10)的定时作用。
4). setitimer()
#include<sys/time.h>
int setitimer(int which, const struct itimerval *value, struct itimerval *ovalue )
setitimer也是用作定时作用,但相比于alarm()有很大的灵活度。函数的第一个参数有三种可选的值:
- ITIMER_REL :设定绝对的时间;经过设定的时间后,内核将发送SIGALRM给本进程
- ITIMER_VIRTUAL :设定程序执行时间;经过设定的时间后,内核将发送SIGALRM给本进程。
- ITIMER_PROF: 设定进程执行时间以及内核因该进程执行所消耗的时间和,经过设定的时间后,内核将发送SIGALRM给本进程。
struct itimerval {
struct timeval it_interval;
struct timeval it_value;
};
struct timeval {
long tv_sec; // second
long tv_usec; // usecond 微秒
};
it_interval指定间隔时间,it_value指定初始定时时间。如果只是指定it_value,就是实现一次定时;如果同时指定it_interval,则超时后,系统会重新初始化it_value为it_interval,实现重复定时;两者都清零,则会清除定时器。
第三个参数 ovalue是保存先前的值,通常会置为NULL;
5). absort()
#include <stdlib.h>
void absort(void)
向进程发送SIGABSORT信号,缺省情况下使进程异常退出。也可以为SIGABSORT信号自定义处理函数(handler)
6). sigqueue()
这个是最新的信号发送函数,但也是最复杂的一个,其函数原型如下:
#include<sys/types.h>
#include<signal.h>
int sigqueue(pid_t pid, int signum, const union sigval val)
其中第一个参数是指定的进程ID号(该函数只能给一个进程发送信号,而kill可以给一个进程组发送信号);第二个参数是信号值;第三个参数是sigval联合体的一个实例,
typedef union sigval {
int sival_int;
void *sival_ptr;
}sigval_t;
该联合体提供了信号传递的参数,即sigqueue相比于kill可以支持传递带参数的信号。同样如果signum=0,将会执行错误检查,但实际上不发送任何信号,0值信号可用于检查pid的有效性以及当前进程是否有权限向目标进程发送信号。
3. 信号的安装
信号的安装的意思是:实现对特定信号进行自定义函数的处理。
1). signal()
#include<signal>
void(* signal(int signum, void(*handler)(int signum)))(int)
或者可以理解为:
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler));
即signal函数有两个参数组成包括:一个int型的信号值和一个参数为int型返回为void的函数指针handler。在提前定义handler的时候,调用signal可以实现对信号特性处理的安装。2). signalaction()
这是一个比较复杂的函数,不过它提供了相当强大的功能。与sigqueue配合使用可以传递具有参数的信号。
#include<sys/types.h>
#include<signal.h>
int sigaction(int signum, const struct sigaction *act, const struct sigaciton *oldact)
其中第一个比较好理解,它就是信号值。第二和第三个参数都是sigaction的结构体,其中第三个参数是对之前的信息的存储,可以设置为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);
}
结构体中第一个数据成员是一个联合体,它也就是对信号的处理方式,可以选择:SIGDEF(缺省模式),SIGIGN(忽略信号),或者一个处理函数的函数指针。
但是这里的处理函数相比于signal中的处理函数多出了两个参数,第二和第三个参数。第三参数现在没有用处。第二参数是一个结构体,其中定义如下:
typedef struct {
int si_signo; //信号值
int si_errno; //错误值
int si_code; // 信号产生的原因
union sigval si_value;
} siginfo_t;
union sigval { //也就是在sigqueue中的第三个参数
int sival_int;
void *sival_ptr;
}
在信号的处理函数中包括了 sigval的联合体的目的是给处理函数提供由信号携带的参数。具体怎么操作可以在编写信号处理函数时进行设计。
继续上面有关结构体sigaction的介绍:
它的第二个参数是sa_mask,sa_mask指定在信号处理程序执行过程中,哪些信号应当被阻塞。缺省情况下当前信号本身被阻塞,防止信号的嵌套发送,除非指定SA_NODEFER或者SA_NOMASK标志位。
它的第三个参数:sa_flags中包含了许多标志位,包括刚刚提到的SA_NODEFER及SA_NOMASK标志位。另一个比较重要的标志位是SA_SIGINFO,当设定了该标志位时,表示信号附带的参数可以被传递到信号处理函数中,因此,应该为sigaction结构中的sa_sigaction指定处理函数,而不应该为sa_handler指定信号处理函数,否则,设置该标志变得毫无意义。即使为sa_sigaction指定了信号处理函数,如果不设置SA_SIGINFO,信号处理函数同样不能得到信号传递过来的数据,在信号处理函数中对这些信息的访问都将导致段错误(Segmentation
fault)。
信号是进程通信中的一种重要的通信机制,一定要熟悉该通信机制的发送信号,安装信号的函数。当然还有信号集,以及信号阻塞的部分的函数。