分类: LINUX
2014-02-23 23:44:21
原文地址:(3)信号 作者:g_programming
以下内容来自:
http://blogold.chinaunix.net/u1/59291/article.php?frmid=83308
一、不可靠信号安装和发送函数。
1.
名称:: |
signal |
功能: |
信号安装(设置信号关联动作) |
头文件: |
#include |
函数原形: |
typedef void (*sighandler_t)(int); sighandler_t signal(int signum,sighandler_t handler); |
参数: |
signum 信号名 handler 操作方式 |
返回值: |
成功则为以前的信号处理配置,若出错则为SIG_ERR |
signum参数是信号名,handler的值是:1。常数SIG_IGN:表示忽略此信号。2。SIG_DFL:表示接到此信号后的动作是系统默认动作。3。函数地址:表示我们捕捉此信号,并且调用用户设置的信号处理程序。当调用signal设置信号处理程序时,第二个参数是指向该函数的指针。
/*10_1.c*/ #include #include #include
main(){ signal(SIGINT,SIG_IGN); printf("hello!n"); sleep(10); printf("hellon"); } |
上面的代码忽略了SININT信号.
此程序执行会在屏幕上先打印一个“hello!”,然后睡眠10分钟。在此期间用户按ctrl+c没有任何反应,因为signal函数已将SIGINT信号(按ctrl+c会产生)设为忽略。
然后看下面的程序:
/*10_2.c*/ #include #include #include
void catch(int sig);
main(){ signal(SIGINT,catch); printf("hello!n"); sleep(10); printf("hello!n"); }
void catch(int sig){ printf("catch signaln"); }
|
当用户按下ctrl+c时,进程被中断,catch()被执行.中断处理函数处理完毕后,转回断点执行下面的指令.
当编写自己的中断处理函数时,注意下面两点:
1.信号不能打断系统调用.
2.信号不能打断信号处理函数.
2.
名称:: |
pause |
功能: |
等待信号 |
头文件: |
#include |
函数原形: |
int pause(void); |
参数: |
无 |
返回值: |
-1,errno设置为EINTR |
pause函数使调用进程挂起直至捕捉到一个信号,pause才返回。在这种情况下,pause返回-1,errno设置为EINTR..
下面是一个例子:
/*10_3.c*/ #include
static void sig_usr(int signo);
int main() { if(signal(SIGUSR1,sig_usr)==SIG_ERR) perror(SIGUSR1); if(signal(SIGUSR2,sig_usr)==SIG_ERR) perror(SIGUSR2); while(1) pause(); }
static void sig_usr(int signo) { if(signo==SIGUSR1) printf(“received SIGUSR1\n”); else if(signo==SIGUSR2) printf(“received SIGUSR2\n”); else printf(“received signal %d\n”,signo); } |
pause();函数使调用进程挂起,直至捕捉到一个信号。
下面我们来运行一下:
#./10_1 & 在后台运行进程
[3] 18864
#.kill -USR1 18864 向该进程发送SIGUSR1
received SIGUSR1
# kill –USR2 18864 向该进程发送SIGUSR1
received SIGUSR2
# kill 18864 向该进程发送SIGTERM
[3]+ Terminated ./signal
可以看到当用户kill -USR1 18864的时候产生了SIGUSR1信号,signal 定义了处理此信号要调用sig_usr函数,所以就在屏幕上打印出received SIGUSR1。
shell自动将后台进程对中断和退出信号的处理方式设置为忽略。于是当按中断键时就不会影响到后台进程。如果没有执行这样的处理,那么当按中断键时,它不但会终止前台进程,还会终止所以的后台进程。
我们还应注意的是,我们不能在信号处理程序中调用某些函数,这些函数被称为不可重入函数,例如malloc,getpwnam..那是因为当发生中断的时候系统有可能正在执行这些函数,在中断中调用这些函数可能会覆盖原来的信息,因而产生错误。
3.
名称:: |
kill/raise |
功能: |
信号发送函数 |
头文件: |
#include |
函数原形: |
int kill(pid_t pid,int signo); int raise(int signo); |
参数: |
pid 进程id signo 信号 |
返回值: |
若成功返回0,若出错返回-1 |
kill函数将信号发送给进程或进程组。raise函数则允许进程向自己发送信号。
raise(signo)等价于kill(getpid(),signo);
kill的pid函数有4种不同的情况:
pid>0 将信号发送给进程ID为pid的进程。
pid==0将信号发送给与发送进程同一组的所有进程。
pid<0 将该信号发送给其进程组id等于pid绝对值。
pid==-1将信号发送给进程有权向它发送信号的系统上的所有进程。
进程将信号发送给其他进程需要许可权。超级用户可将信号发送另一个进程。对于非超级用户,其基本规则是发送者的实际或有效用户ID必须等于接收者的实际或有效用户ID。
/*10_4.c*/ #include #include
void sig_usr(int);
main() { if(signal(SIGINT,sig_usr)==SIG_ERR) perror(“error”); while(1); }
void sig_usr(int signo) { if(signo==SIGINT) printf(“received SIGINT\n”); kill(getpid(),SIGKILL); } |
程序运行后,当用户按ctrl+c后,程序调用信号处理函数输出received SIGINT,然后调用kill函数中止进程。此程序的kill(getpid(),SIGKILL);也可以写成raise(SIGKILL);.
4.
名称:: |
alarm |
功能: |
set an alarm clock for delivery of a signal |
头文件: |
#include |
函数原形: |
unsigned int alarm(unsigned int seconds); |
参数: |
seconds 时间 |
返回值: |
0或以前设置时间的剩余数 |
使用alarm函数可以设置一个时间值(闹钟时间),在将来的某个时刻时间值会被超过。当所设时间值被超过后,产生SIGALRM信号。如果不忽略或不捕捉此信号,则其默认动作是终止该进程。
/*10_5.c*/ #include #include main() { unsigned int i; alarm(1); for(i=0;1;i++) printf("I=%d",i); } |
下面这个函数会有什么结果呢?
SIGALRM的缺省操作是结束进程,所以程序在1秒之后结束,你可以看看你的最后I值为多少,来比较一下大家的系统性能差异(我的是40300)。
5.
名称:: |
abort |
功能: |
信号发送函数 |
头文件: |
#include |
函数原形: |
void abort(void); |
参数: |
|
返回值: |
无 |
此函数将SIGABRT信号发送给调用进程。进程不应该忽略此信号。
二、可靠信号安装和发送函数。
可靠信号的处理函数和不可靠信号的处理函数基本原理是一样的,只不过是可靠信号的处理函数支持排队,信号不会丢失。
6.
名称:: |
sigaction |
功能: |
可靠信号的安装函数 |
头文件: |
#include |
函数原形: |
int sigaction(int signo,const struct sigaction *act,struct sigaction *oact); |
参数: |
|
返回值: |
若成功返回0,若出错返回-1。 |
sigaction结构的原形为:
struct sigaction {
void (*sa_handler)(int signo);
void (*sa_sigaction)(int siginfo_t *info,void *act);
sigset_t sa_mask;
int sa_flags;
void (*sa_restore)(void);
}
这个函数和结构看起来是不是有点恐怖呢。不要被这个吓着了,其实这个函数的使用相当简单的。我们先解释一下各个参数的含义。 signo很简单就是我们要处理的信号了,可以是任何的合法的信号。有两个信号不能够使用(SIGKILL和SIGSTOP)。 act包含我们要对这个信号进行如何处理的信息。oact更简单了就是以前对这个函数的处理信息了,主要用来保存信息的,一般用NULL就OK了。
信号结构有点复杂。不要紧我们慢慢的学习。
sa_handler是一个函数型指针,这个指针指向一个函数,这个函数有一个参数。这个函数就是我们要进行的信号操作的函数。 sa_sigaction,sa_restore和sa_handler差不多的,只是参数不同罢了。这两个元素我们很少使用,就不管了。
sa_flags用来设置信号操作的各个情况。一般设置为0好了。sa_mask用来设置信号屏蔽字,将在后面介绍。
在使用的时候我们用sa_handler指向我们的一个信号操作函数,就可以了。sa_handler有两个特殊的值:SIG_DEL和SIG_IGN。SIG_DEL是使用缺省的信号操作函数,而SIG_IGN是使用忽略该信号的操作函数。
这个函数复杂,我们使用一个实例来说明。下面这个函数可以捕捉用户的CTRL+C信号。并输出一个提示语句。
/*10_6.c*/ #include #include
#define PROMPT "catch the signal of ‘ctrl+c’\nplease enter ‘ctrl+z’ to exit\n"
char *prompt=PROMPT;
void ctrl_c_op(int signo) /*信号处理程序*/ { write(STDERR_FILENO,prompt,strlen(prompt)); }
int main() { struct sigaction act; act.sa_handler=ctrl_c_op; act.sa_flags=0; if(sigaction(SIGINT,&act,NULL)<0) { preeor(“error”); exit(1); } while(1); }
|
运行程序后,当用户按ctrl+c(会产生SIGINT信号)后屏幕上会打印。
catch the signal of ‘ctrl+c’
please enter ‘ctrl+z’ to exit
也就是说当用户按ctrl+c时我们调用我们自己写的函数来处理中断。
7.
名称:: |
sigqueue |
功能: |
可靠信号的发送函数 |
头文件: |
#include |
函数原形: |
int sigqueue(pid_t pid,int sig,const union sigval value); |
参数: |
|
返回值: |
若成功返回0,若出错返回-1。 |
typedef union sigval
{
int sival_int;
void *sival_ptr;
}sigval_t;
sigqueue()是比较新的发送信号系统调用,主要是针对实时信号提出的(当然也支持前32种),支持信号带有参数,与函数sigaction()配合使用。sigqueue的第一个参数是指定接收信号的进程ID,第二个参数确定即将发送的信号,第三个参数是一个联合数据结构union sigval,指定了信号传递的参数,即通常所说的4字节值。
sigqueue()比kill()传递了更多的附加信息,但sigqueue()只能向一个进程发送信号,而不能发送信号给一个进程组。如果sig为0,将会执行错误检查,但实际上不发送任何信号,0值信号可用于检查pid的有效性以及当前进程是否有权限向目标进程发送信号。
在调用sigqueue时,sigval_t指定的信息会拷贝到3参数信号处理函数的siginfo_t结构中,这样信号处理函数就可以处理这些信息了。由于sigqueue系统调用支持发送带参数信号,所以比kill()系统调用的功能要灵活和强大得多。
注:sigqueue()发送非实时信号时,第三个参数包含的信息仍然能够传递给信号处理函数; sigqueue()发送非实时信号时,仍然不支持排队,即在信号处理函数执行过程中到来的所有相同信号,都被合并为一个信号。
三、信号屏蔽字:
有时候我们希望进程正确的执行,而不想进程受到信号的影响,比如我们希望上面那个程序在1秒钟之后不结束。这个时候我们就要进行信号的操作了。
信号操作最常用的方法是信号屏蔽。信号屏蔽要用到下面的几个函数。
sigemptyset,sigfillset,sigaddset,sigdelset,sigismember,sigprocmask。下面对他们分别进行讲解。
8.
名称:: |
sigemptyset/sigfillset/sigaddset/sigdelset/sigismember |
功能: |
处理信号集 |
头文件: |
#include |
函数原形: |
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); |
参数: |
set 信号集 signum 信号 |
返回值: |
若成功返回0,若出错返回-1。 若真返回1,若假返回0,若出错返回-1。 sigismember |
我们需要有一个能表示多个信号—信号集的数据类型。我们将在诸如sigprocmask之类的函数中使用这种数据类型,以便告诉内核不允许发生该信号集中的信号。上面的5个函数可以对信号集进行处理。
函数sigemptyset 初始化由set指向的信号集,清除其中所有信号。函数sigfillset初始化由set指向的信号集,使其包含所有信号。所以信号在使用信号集前,要对信号集调用sigemptyset或sigfillset一次。
函数sigaddset 将一个信号添加到现有集中,sigdelset则从信号集中删除一个信号。对所有以信号集作为参数的函数,我们总是以信号集地址作为其传送的参数。
sigismember查询信号是否在信号集合之中。
下面的例子:
/*10_7.c*/ #include #include
main() { sigset_t *set; set=(sigset_t*)malloc(sizeof(set));
sigemptyset(set);/*初始化信号集*/ sigaddset(set,SIGUSR1);/*添加信号SIGUSR1到信号集中*/ sigaddset(set,SIGINT);/*添加信号SIGUSR2到信号集中*/
if((sigismember(set,SIGUSR1))==1)/*测试信号SIGUSR1是否在信号集中*/ printf(“SIGUSR1\n”); if((sigismember(set,SIGUSR2))==1) printf(“SIGUSR2\n”); if((sigismember(set,SIGINT))==1) printf(“SIGINT\n”); }
|
下面是执行结果:
# ./10_7 SIGUSR1 SIGINT |
程序先初始化信号集,清除其中所有信号,然后把SIGUSR1和SIGINT添加到信号集中,然后测试SIGUSR1,SIGUSR2,SIGINT信号是否在信号集中。因为SIGUSR2不在信号集中,所以程序并不打印SIGUSR2。
9.
名称:: |
sigprocmask |
功能: |
检测或更改信号屏蔽字 |
头文件: |
#include |
函数原形: |
int sigprocmask(int how,const sigsett_t *set,sigset_t *oldset); |
参数: |
how 操作方式 set 信号集 oldest |
返回值: |
若成功返回0,若出错返回-1。 |
每个进程都有一个用来描述哪些信号递送到进程时将被阻塞的信号集,该信号集中的所有信号在递送到进程后都将被阻塞。
sigprocmask是最为关键的一个函数。在使用之前要先设置好信号集合set。这个函数的作用是将指定的信号集合set加入到进程的信号阻塞集合之中去,如果提供了oldset那么当前的进程信号阻塞集合将会保存在oldset里面。参数how决定函数的操作方式。
SIG_BLOCK:增加一个信号集合到当前进程的阻塞集合之中。
SIG_UNBLOCK:从当前的阻塞集合之中删除一个信号集合。
SIG_SETMASK:将当前的信号集合设置为信号阻塞集合。
我们把10_7.c稍微修改一下,看看sigprocmask函数的功能。
/*10_8.c*/ #include #include
main() { sigset_t *set; set=(sigset_t*)malloc(sizeof(set));
sigemptyset(set);/*定义信号集set*/ sigaddset(set,SIGINT);/*把信号SIGINT添加到信号集中*/ sigprocmask(SIG_SETMASK,set,NULL);/*把set设置为信号阻塞集合*/ wile(1);/*死循环*/ } |
程序先定义信号集set,然后把信号SIGINT添加到set信号集中,最后把set设置为信号阻塞集合。当我们运行程序时,进程进入死循环。我们按“ctrl+c”系统并没有中断程序,因为我们已经把SIGINT信号屏蔽掉了。我们可以按”ctrl+z”来结束程序。
sigeprocmask函数通常和sigemptyset/sigfillset/sigaddset/sigdelset/sigismember函数配合使用,主要有两种用途:
1.我们不希望某些不太重要的信号来影响我们的进程,我们就可以把这些信号添加到信号屏蔽集中。使它们不打扰进程的执行。
2.如果系统现在很忙,没有时间及时相应信号,进程可以先把信号阻塞掉,等系统有空闲时间在去相应,这也保证了信号的可靠性。下面的函数可以做到这一点。
10.
名称:: |
sigpending |
功能: |
返回信号集 |
头文件: |
#include |
函数原形: |
int sigpending(sigset_t *set); |
参数: |
|
返回值: |
若成功返回0,若出错返回-1。 |
我们要注意的是,阻塞信号并不是丢弃信号,它们被保存在一个进程的信号阻塞队列里,sigpending可以获得当前已递送到进程,却被阻塞的所有信号,在set指向的信号集中返回这些信号。
下面是一个例子:
/*10_9.c*/ #include #include #include
int main(void) { sigset_t newmask,oldmask,pendmask;
sigemptyset(&newmask); /*初始化信号集*/ sigaddset(&newmask,SIGINT); /*添加信号SIGINT到信号集*/ if(sigprocmask(SIG_BLOCK,&newmask,&oldmask)<0) /*信号集newmask设置为阻塞信号集*/ perror(“error”); sleep(5); if(sigpending(&pendmask)<0) /*把当前已递送到进程,却被阻塞的信号保存到进程的信号阻塞队列里*/ perror(“error”); if(sigismember(&pendmask,SIGINT)) /*检测进程阻塞队列里是否有SIGINT信号*/ printf(“\nSIGINT pending\n”);
exit(0); }
|
程序开始运行,如果我们在5秒钟内按”ctrl+c”,SIGINT信号会被传递到进程,但是由于进程设置了信号阻塞集,信号SIGINT在这个集合中,所有这个信号被阻塞。并由sigpend保存到进程的信号阻塞队列pendmask里.然后我们用sigismember检测SIGINT是否在信号阻塞队列pendmask里。
11.
名称:: |
sigsuspend |
功能: |
|
头文件: |
#include |
函数原形: |
int sigsuspend(const sigset_t *sigmask); |
参数: |
sigmask 要替换的进程信号屏蔽字。 |
返回值: |
-1,errno设置为EINTR. |
sigsuspend用于在接收到某个信号之前, 临时用sigmask替换进程的信号掩码, 并暂停进程执行,直到收到信号为止。sigsuspend 返回后将恢复调用之前的信号掩码。信号处理函数完成后,进程将继续执行。该系统调用始终返回-1,并将errno设置为EINTR。
但要注意的是,sigsuspend的整个操作都是原子的,也就不允许被打断的。sigsuspend的整个原子操作过程为:
(1) 设置新的mask阻塞当前进程;
(2) 收到信号,恢复原先mask;
(3) 调用该进程设置的信号处理函数;
(4) 待信号处理函数返回后,sigsuspend返回。
这种技术的功能有:
1.可以保护不希望由信号中断的代码临界区。
2.等待一个信号处理程序设置一个全局变量。
3.实现父子进程之间的同步。
这个函数的执行步骤有点难理解,但我们仔细分析一下就会明白,sigsuspend函数是先用我们定义的sigmask来暂时替代信号屏蔽集,然后阻塞当前进程,收到信号时调用信号处理函数函数对信号进行处理,处理后返回。
下面是函数的一个例子:
/*10_10.c*/ #include #include #include
static void sig_int(int);
int main(void) { sigset_t newmask,oldmask,zeromask; if(signal(SIGINT,sig_int)==SIG_ERR) /*设置信号处理,调用信号处理函数*/ perror(“signal error”); sigemtyset(&zeromask); /*初始化zeromask信号集*/ sigemtyset(&newmask); /*初始化newmask信号集*/ if(sigprocmask(SIG_BLICK,&newmask,&oldmask)<0) /*把信号集newmask设置为信号屏蔽集*/ perror(“SIG_BLOCK error”); printf(“In critical region: SIGINT\n”); if(sigsuspend(&zeromask)!=-1) /*用zeromask代替信号屏蔽集,然后阻塞信号,如果有信号来通过signal调用处理函数,最后返回*/ perror(“sigsuspend error”); printf(“After return from sigsuspend: SIGINT\n”); sleep(5);
exit(0); }
static void sig_int(int signo) /*信号处理函数 { printf(“In sig_int: SIGINT\n”); }
|
程序先打印In critical region: SIGINT,然后执行sigsuspend阻塞信号,当用户按“ctrl+c”时进程通过signal调用信号处理函数sig_int打印In sig_int: SIGINT,返回后打印After return from sigsuspend: SIGINT。然后程序休眠5秒钟,在这5秒钟如果用户按“ctrl+c”进程是不会有反应的,因为调用完sigsuspend进程就恢复了原先的屏蔽集。而原先的屏蔽集屏蔽了SIGINT信号。
12.
名称:: |
sigsetjmp/siglongjmp |
功能: |
save stack context for non-local goto |
头文件: |
#include |
函数原形: |
int sigsetjmp(sigjmp_buf env,int savemask); void siglongjmp(sigjmp_buf env,int val); |
参数: |
|
返回值: |
若直接调用则为0,若从sigsetjmp调用返回则为非0。 |
这两个函数和setjmp,longjmp之间的唯一区别是sigsetjmp增加了一个参数。如果savemask非0,则sigsetjmp在env中保存进程的当前信号屏蔽字。调用siglongjmp时。如果带非0 savemask的sigsetjmp调用已经保存了env,则siglongjmp从其中恢复保存的信号屏蔽字。