signal函数

signal函数

要使用UNIX系统中信号这一功能最简单的方法是使用signal函数:

#include <signal.h>

void (*signal(int signo, void (*func)(int)))(int);

Returns: previous disposition of signal (see following) if OK, SIG_ERR on error

这个定义看上去有点复杂,可以将其简化为:

typedef void (*HANDER) (int);

HANDER signal(int signo, HANDER func);

原型的分析:

可以把原型看做:void (*signal(…))(int);

signal(…)是一个函数,它的返回值是一个指针,该指针的类型又是什么呢?

这时再把原型看做void (*)(int);该指针指向一个函数,这个函数的参数是一个int型变量,并返回void。——这就是指针的类型。

signo参数是要捕获的信号名字。

func是:

  1. 常量SIG_IGN,告诉系统忽略信号。(SIGKILLSIGSTOP不能忽略)
  2. 常量SIG_DFL,告诉系统使用默认方法处理。
  3. 当产生信号时处理信号的函数的地址。

如同上面原型分析所说,signal函数有两个参数,并返回一个函数指针(该函数返回类型是void)。signal函数的第一个参数signo是整型,第二个参数是带一个整数参数的函数指针(该函数返回类型是void)。

例子

我们在后台运行一个程序,然后使用kill命令向它发送一个信号。kill命令和kill函数只是发送信号到进程或进程组。信号是否能结束进程依赖于收到信号的进程是否安排了处理信号的函数。

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

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

int
main(void)
{
    if (signal(SIGUSR1, sig_usr) == SIG_ERR)
        err_sys("can't catch SIGUSR1");
    if (signal(SIGUSR2, sig_usr) == SIG_ERR)
        err_sys("can't catch SIGUSR2");
    for ( ; ; )
        pause();
}

static void
sig_usr(int signo)      /* argument is signal number */
{
    if (signo == SIGUSR1)
        printf("received SIGUSR1\n");
    else if (signo == SIGUSR2)
        printf("received SIGUSR2\n");
    else
        err_dump("received signal %d\n", signo);
}

执行方法:

   $ ./a.out &                   start process in background
   [1]      7216                 job-control shell prints job number and process ID
   $ kill -USR1 7216             send it SIGUSR1
   received SIGUSR1
   $ kill -USR2 7216             send it SIGUSR2
   received SIGUSR2
   $ kill 7216                   now send it SIGTERM
   [1]+  Terminated    ./a.out

程序的启动:

当程序被执行时,所有的信号状态要么是默认,要么是忽略。一般,所有信号都被设置成它们的默认动作,除非进程调用exec函数忽略信号。确切地讲,exec函数将捕捉到的信号都更改为它们的默认动作,并且不改变其它剩下信号的状态。(通常,一个信号被进程捕获后,该进程调用exec启动的新程序不能捕获这个信号,因为信号捕捉程序的地址在新调用的程序中没有意义。)

本例演示了shell如何对待后台进程的中断和退出信号。shell本身并不支持任务控制,当我们执行:

cc main.c &

时,shell自动设置后台进程的中断和退出信号为忽略。这就是为什么我们键盘输入中断字符不会中断后台进程。如果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);

如果信号没有被进程忽略,那么才会执行这些代码。

这两个调用signal函数的方法都有限制:不能判断当前信号的布局(disposition)是否已经改变。

进程创进:

当进程调用fork时,子进程继承了父进程的信号布局(disposition)。因为子进程从父进程的内存镜像开始,所以信号捕捉程序的地址对子进程是有意义的。

发表评论