日期:2014-05-16  浏览次数:20914 次

Linux/UNIX之信号(1)

信号(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\n");
    else if (signo == SIGUSR2)
        printf("received SIGUSR2\n");
    else
        printf("received signal%d\n", 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