UNIX系统信号机制最简单的接口是signal函数。

#include <signal.h>
void (*signal(int signo, void (*func)(int)))(int);
//返回值:若成功,返回以前的信号处理配置;若出错,返回SIG_ERR

signal函数由ISO C定义。因为ISO C不涉及多进程、进程组以及终端I/O等,所以它对信号的定义非常含糊,以致于对UNIX系统而言几乎毫无用处。

从UNIX System V派生的实现支持signal函数,但该函数提供旧的不可靠信号语义(10.4节将说明这些旧的语义)。提供此函数主要是为了向后兼容要求此旧语义的应用程序,新应用程序不应使用这些不可靠信号。

4.4BSD 也提供 signal 函数,但它是按照 sigaction 函数定义的(10.14 节将说明sigaction 函数),所以在 4.4BSD 之下使用它提供新的可靠信号语义。目前大多数系统遵循这种策略,但Solaris 10沿用System V signal函数的语义。

因为signal的语义与实现有关,所以最好使用sigaction函数代替signal函数。在10.14节讨论sigaction函数时,提供了使用该函数的signal的一个实现。本书中的所有实例均使用图10-18中给出的signal函数,这样不管使用何种平台都可以有一致的语义。

signo参数是图10-1中的信号名。func的值是常量SIG_IGN、常量SIG_DFL或当接到此信号后要调用的函数的地址。如果指定SIG_IGN,则向内核表示忽略此信号(记住有两个信号SIGKILLSIGSTOP不能忽略)。如果指定SIG_DFL,则表示接到此信号后的动作是系统默认动作(见图10-1中的最后一列)。当指定函数地址时,则在信号发生时,调用该函数,我们称这种处理为捕捉该信号,称此函数为信号处理程序(signalhandler)或信号捕捉函数(signal-catching function)。

signal 函数原型说明此函数要求两个参数,返回一个函数指针,而该指针所指向的函数无返回值(void)。第一

个参数signo是一个整型数,第二个参数是函数指针,它所指向的函数需要一个整型参数,无返回值。

signal 的返回值是一个函数地址,该函数有一个整型参数(即最后的(int))。

用自然语言来描述也就是要向信号处理程序传送一个整型参数,而它却无返回值。当调用signal设置信号处理程序时,第二个参数是指向该函数(也就是信号处理程序)的指针。signal的返回值则是指向在此之前的信号处理程序的指针。

很多系统用附加的依赖于实现的参数来调用信号处理程序。10.14节将对此做进一步说明。本节开头所示的signal函数原型太复杂了,如果使用下面的typedef[Plauger 1992],则可使其简单一些。

typedef void Sigfunc(int);

我们已将此typedef包括在apue.h文件中(见附录B),并随本章中的函数一起使用。如果查看系统的头文件<signal.h>,则很可能会找到下列形式的声明:

#define SIG_ERR (void (*)())-1
#define SIG_DFL (void (*)())0
#define SIG_IGN (void (*)())1

?: 这些常量可用于表示“指向函数的指针,该函数要求一个整型参数,而且无返回值”。signal的第二个参数及其返回值就可用它们表示。这些常量所使用的3 个值不一定是−1、0 和1,但它们必须是3个值而决不能是任一函数的地址。大多数UNIX系统使用上面所示的值。

实例

图10-2给出了一个简单的信号处理程序,它捕捉两个用户定义的信号并打印信号编号。10.10节将说明pause函数,它使调用进程在接到一信号前挂起。

#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>


static void sig_usr(int);/* one handler for both signals*/

int main(void) {
    if(signal(SIGUSR1,sig_usr) == SIG_ERR){
        printf("can't catch SIGUSR1");
        exit(1);
    }
    if(signal(SIGUSR2,sig_usr) == SIG_ERR){
        printf("can't catch SIGUSR2");
        exit(1);
    }
    for(;;){
        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);
        exit(1);
    }
}

图10-2

我们使该程序在后台运行,并且用kill(1)命令将信号发送给它。注意,在UNIX系统中,杀死(kill)这个术语是不恰当的。kill(1)命令和 kill(2)函数只是将一个信号发送给一个进程或进程组。该信号是否终止进程则取决于该信号的类型,以及进程是否安排了捕捉该信号。

$ ./a.out &        在后台启动进程
[1]   7216        作业控制shell打印作业编号和进程ID
$ kill -USR1 7216     向该进程发送SIGUSR1
received SIGUSR1
$ kill -USR2 7216     向该进程发送SIGUSR2
received SIGUSR2
$ kill 7216        向该进程发送SIGTERM
[1]+ Terminated ./a.out

因为执行图10-2程序的进程不捕捉SIGTERM信号,而对该信号的系统默认动作是终止,所以当向该进程发送SIGTERM信号后,该进程就终止。

1.程序启动

当执行一个程序时,所有信号的状态都是系统默认或忽略。通常所有信号都被设置为它们的默认动作,除非调用exec的进程忽略该信号。

?: 确切地讲,exec函数将原先设置为要捕捉的信号都更改为默认动作,其他信号的状态则不变(一个进程原先要捕捉的信号,当其执行一个新程序后,就不能再捕捉了,因为信号捕捉函数的地址很可能在所执行的新程序文件中已无意义)。

一个具体例子是一个交互 shell 如何处理针对后台进程的中断和退出信号。对于一个非作业控制shell,当在后台执行一个进程时,例如:

cc main.c &

shell自动将后台进程对中断和退出信号的处理方式设置为忽略。于是,当按下中断字符时就不会影响到后台进程。如果没有做这样的处理,那么当按下中断字符时,它不但终止前台进程,也终止所有后台进程。

很多捕捉这两个信号的交互程序具有下列形式的代码:

void sig_int(int), sig_quit(int);
if (signal(SIGINT, SIG_IGN) != SIG_IGN)
    signal(SIGINT, sig_int);
if (signal(SIGQUIT, SIG_IGN) != SIG_IGN)
    signal(SIGQUIT, sig_quit);

?: 这样处理后,仅当SIGINTSIGQUIT当前未被忽略时,进程才会捕捉它们。

从signal的这两个调用中也可以看到这种函数的限制:不改变信号的处理方式就不能确定信号的当前处理方式。

我们将在本章的稍后部分说明使用sigaction函数可以确定一个信号的处理方式,而无需改变它。

2. 进程创建

当一个进程调用fork时,其子进程继承父进程的信号处理方式。因为子进程在开始时复制了父进程内存映像,所以信号捕捉函数的地址在子进程中是有意义的。

results matching ""

    No results matching ""