信号(1)
信号是软件中断。每一个信号都有一个名字,这些名字都以SIG开头(如SIGABRT 夭折信号)。
在头文件<signal.h>中。这些信号都被定义成正整数。不存在编号为0的信号,kill函数对信号编号为0有特殊的应用。
当某个信号出现时,能够要求内核依照下列三种方式之中的一个进行处理:
1. 忽略此信号
2. 捕捉信号
3. 运行系统默认动作
signal函数
UNIX信号机制最简单的接口是signal函数。
#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum,sighandler_t handler);
此函数中signum是在头文件<signal.h>定义的信号名,handler的值是常量SIG_IGN、常量SIG_DFL或当地接到此信号后要调用的函数地址。假设指定SIG_IGN,则向内核表示忽略此信号。假设指定SIG_DFL,则表示接到此信号后的动作是系统默认动作。当指定函数地址时,则在信号发生时。调用该函数,我们称这样的处理为捕捉该信号。称此函数为信号处理程序或信号捕捉函数。返回值也是一个函数指针。它是指向之前的信号处理程序的指针。
例如以下程序显示了一个简单的信号处理程序。它捕捉两个用户定义的信号并打印信号编号。
#include<stdio.h>
#include<signal.h>
static voidsig_usr(int); /* one handler for bothsignals */
int main(void)
{
if (signal(SIGUSR1, sig_usr) == SIG_ERR)
perror("can't catchSIGUSR1");
if (signal(SIGUSR2, sig_usr) == SIG_ERR)
perror("can't catchSIGUSR2");
for ( ; ; )
pause();
}
static void sig_usr(intsigno) /* argument is signal number*/
{
if (signo == SIGUSR1)
printf("received SIGUSR1
");
else if (signo == SIGUSR2)
printf("received SIGUSR2
");
else
printf("received signal%d
", signo);
}
运行及输出结果例如以下:
chen123@ubuntu:~/user/apue.2e$ ./a.out &
[1] 5712
chen123@ubuntu:~/user/apue.2e$kill -USR1 5712
received SIGUSR1
chen123@ubuntu:~/user/apue.2e$kill -USR2 5712
received SIGUSR2
chen123@ubuntu:~/user/apue.2e$ kill 5712
[1]+ Terminated ./a.out
我们在后台调用该程序,并且用kill(1)命令将信号传送给它。注意,在UNIX中,kill不代表杀死进程。kill(1)和kill(2)仅仅是将一个信号送给一个进程或进程组。信号是否终止进程取决于信号的类型,以及进程是否安排了捕捉该信号。
程序启动:当一个程序启动时,全部信号的状态都是系统默认或忽略。在上面的程序中的以下代码:
if(signal(SIGUSR1, sig_usr) == SIG_ERR)
perror("can't catch SIGUSR1");
if(signal(SIGUSR2, sig_usr) == SIG_ERR)
perror("can't catch SIGUSR2");
表示仅当信号当前未被忽略时,进程才会捕捉它们。从signal的这两个调用中能够看到这样的函数的限制:不改变信号的处理方式就不能确定信号的当前处理方式。sigaction函数能够确定一个信号的处理方式,而无需改变它。
进程创建:当一个进程调用fork,其子进程继承父进程的信号处理方式。
kill和raise函数
#include <sys/types.h>
#include <signal.h>
int kill(pid_t pid, int sig);
#include <signal.h>
int raise(int sig);
成功返回0,出错返回-1。调用raise(sig)等价于调用kill(getpid(), sig)
kill的pid參数有四种可能:
pid > 0: 将该信号发送给进程ID为pid的进程
pid == 0: 将该信号发送给与发送进程属于同一进程组的全部进程,并且发送进程具有向这些进程发送信号的权限。
pid<0: 将该信号发送给其进程组ID等于pid的绝对值,并且发送进程具有向其发送信号的权限。
pid == -1:将该信号发送给发送进程有权限向他们发送信号的系统上的全部进程。
alarm和pause函数
#include<unistd.h>
unsigned intalarm(unsigned int seconds);
使用alarm函数能够设置一个计时器,在将来某个指定的时间该计时器会超时。当计时器超时时。产生SIGALRM信号。假设不忽略或不捕捉此信号。则其默认动作是终止调用该alarm函数的进程。
当中。參数seconds的值是秒数。要了解的是,经过指定的秒数后,信号由内核产生。由于进程调度的延迟,所以进程得到控制从而能处理信号还须要一些时间。
每一个进程仅仅能有一个闹钟时钟。
#include<unistd.h>
int pause(void);
pause函数使调用进程挂起直到捕捉到一个信号。
仅仅有运行了一个信号处理程序并从其返回时,pause才返回。在这样的情况下,pause返回-1,并将errno设置为EINTR。
信号集
我们须要一个能表示多个信号的——信号集的数据类型。
在诸如siprocmask之类的函数中使用这样的数据类型。以便告诉内核不同意发生该信号集中的信号。
POSIX.1定义了数据类型sigset_t以包括一个信号集,并且定义了下列五个处理信号集的函数。
#include<signal.h>
intsigemptyset(sigset_t *set);
intsigfillset(sigset_t *set);
intsigaddset(sigset_t *set, int signum);
intsigdelset(sigset_t *set, int signum);
intsigismember(const sigset_t *set, int signum);
函数sigemptyset初始化由set指向的信号集,清除当中全部的信号。
函数sigfillset初始化由set指向的信号集,使其包括全部信号。全部应用程序在使用信号集前。要对该信号集调用sigempty或sigfillset一次。这是由于C编译器把未赋初值的外部和静态变量都初始化为0,而这是否与给定系统上信号集的实现相相应却并不清楚。
一旦已经初始化了一个信号集,以后就可在该信号集中增、删特定的信号。
函数sigaddset 将加入一个信号到现有集中。sigdelset 则从信号集中删除一个信号。对全部以信号集作为參数的函数,我们总是以信号集地址作为向其传送的參数。
sigprocmask函数
#include<signal.h>
intsigprocmask(int how, const sigset_t *set, sigset_t *oldset);
返回值:若成功则返回0,若出错则返回-1。
首先,若oset是非空指针,那么进程的当前信号屏蔽字通过oset返回。
其次,若set是一个非空指针,则參数how指示怎样改动当前信号屏蔽字。
以下说明了how可选用的值。
SIG_BLOCK 该进程的信号屏蔽字是其当前信号屏蔽字和set指向信号集的并集。set包括了我们希望堵塞的附加信号。
SIG_UNBLOC 该进程的信号屏蔽字是其当前信号屏蔽字和set所指信号集补集的交集。set包括了我们希望解除堵塞的信号。
SIG_SETMASK 该进程新的信号屏蔽字将被set指向的信号集的值替换。
假设set是空指针。则不改变进程的信号屏蔽字。how的值也无意义。
sigpending函数
#include<signal.h>
intsigpending(sigset_t *set);
sigpending函数返回信号集,当中的各个信号对于调用进程时堵塞的而不能递送。因而也一定是当前未决的。该信号集通过set參数返回。
以下的程序使用了非常多前面说过的信号功能。
#include <stdio.h>
#include <signal.h>
static void sig_quit(int);
int
main(void)
{
sigset_t newmask, oldmask,pendmask;
if (signal(SIGQUIT, sig_quit) == SIG_ERR)
perror("can't catch SIGQUIT");
/*
* Block SIGQUIT and save current signal mask.
*/
sigemptyset(&newmask);
sigaddset(&newmask, SIGQUIT);
if (sigprocmask(SIG_BLOCK, &newmask, &oldmask) < 0)
perror("SIG_BLOCK error");
sleep(5); /* SIGQUIT here will remain pending */、
exit(0);
}
static void
sig_quit(intsigno)
{
printf("caught SIGQUIT
");
if (signal(SIGQUIT, SIG_DFL) == SIG_ERR)
perror("can't reset SIGQUIT");
}
运行及输出结果:
./a.out
^ 产生信号一次(在5秒钟之内)
SIGQUIT pending 从sleep返回后
caught SIGQUIT 在信号处理程序中
SIGQUIT unblocked 从sigprocmask返回后
^Quit 再次产生信号
./a.out
^^^^^^^^^^ 产生信号10次
SIGQUIT pending
caught SIGQUIT 仅仅产生信号一次
SIGQUIT unblocked
^Quit 再次产生信号
进程堵塞SIGQUIT信号,保存了当前信号屏蔽字(以便以后恢复),然后休眠5秒钟。再次期间所产生的退出信号SIGQUIT都会被堵塞,而不递送至该进程,直到该信号不再被堵塞。在5秒钟休眠结束后,检查该信号是否是未决的。然后将SIGQUIT设置为不再堵塞。
在设置SIGQUIT为堵塞时。我们保存了旧屏蔽字。为了解除对该信号的堵塞,用旧屏蔽字又一次设置了进程信号屏蔽字(SIG_SETMASK)。
在休眠期间假设产生退出信号,那么此时该信号是未决的,可是不再受堵塞。所以在sigprocmask返回之前。它被递送到调用进程。从程序的输出能够看出:SIGQUIT处理程序中的printf语句先运行。然后再运行sigprocmask之后的printf语句。