Linux高级编程系列之一:信号

信号是UNIX和Linux系统响应某些条件而产生的一个事件。接收到该信号的进程会相应地采取一些行动。用术语生成(raise)表示一个信号的产生,使用术语捕获(catch)表示接收到一个信号。信号是由于某些错误条件而生成的,如内存段冲突、浮点处理器错误或非法指令等。它们由shell和终端处理器生成来引起中断,它们还可以作为在进程间传递消息或修改行为的一种方式,明确地由一个进程发送给另一个进程。五轮何种情况,它们的编程接口都是相同的。信号可以被生成、捕获、响应或忽略。
信号的名称是在头文件signal.h中定义的。它们以SIG开头。
Signal Name                                   Description
-------------------------------------------------------------------------------------------------------------------------------
SIGABORT                                   *Process abort(进程异常终止)
SIGALRM                                     Alarm clock(超时警告)
SIGFPE                                         *Floating-point exception(浮点运算异常)
SIGHUP                                        Hangup(连接挂断)
SIGILL                                          *Illegal instruction(非法指令)
SIGINT                                         Terminal interrupt(终端中断)
SIGKILL                                        Kill (can’t be caught or ignored)(终止进程(此信号不能被捕获或忽略))
SIGPIPE                                        Write on a pipe with no reader(向无读进程的管道写数据)
SIGQUIT                                       Terminal quit(终端退出)
SIGSEGV                                       *Invalid memory segment access(无效内存段访问)
SIGTERM                                      Termination(终止)
SIGUSR1                                       User-defined signal 1(用户定义信号1)
SIGUSR2                                       User-defined signal 2(用户定义信号2)
---------------------------------------------------------------------------------------------------------------------------------
如果进程接收到这些信号中的一个,但事先没有安排捕获它,进程将会立刻终止。通常,系统将生成核心转储文件core,并将其放在当前目录下。该文件是进程在内存中的映像,它对程序的调试很有用处。
其他信号如下:
Signal                                            Name Description
----------------------------------------------------------------------------------------------------------------------------------------------
SIGCHLD                                     Child process has stopped or exited.(子进程已经停止或退出)
SIGCONT                                    Continue executing, if stopped.(继续执行暂停进程)
SIGSTOP                                     Stop executing. (Can’t be caught or ignored.)(停止执行(此信号不能被捕获或忽略))
SIGTSTP                                      Terminal stop signal.(终端挂起)
SIGTTIN                                      Background process trying to read.(后台进程尝试读操作)
SIGTTOU                                     Background process trying to write.(后台进程尝试写操作)
---------------------------------------------------------------------------------------------------------------------------------------------
SIGCHLD信号对于管理子进程很有用。默认情况下,它是被忽略的。其余的信号会使接收它们的进程停止运行,但SIGCONT是个例外,它的作用是让进程恢复并继续执行。shell脚本通过它来控制作业,但用户程序很少会用到它。
如果想发送一个信号给进程,而该进程并不是当前的前台进程,就需要使用kill命令。该命令需要一个可选的信号代码或信号名称和一个接收信号的目标进程的PID。例如,如果要向运行在另一个终端上的PID为512的进程发送“挂断”信号,可以使用如下命令:

$ kill -HUP 512
kill命令有一个有用的变体叫killall,它可以给运行着某一命令的所有进程发送信号。并不是所有的UNIX系统都支持它,但Linux系统一般都有该命令。如果不知道某个进程的PID,或者想给执行相同命令的许多不同的进程发送信号,这条命令就很有用了。一条常见的用法是,通知inetd程序重新读取它的配置选项,要完成这一工作,可以使用下面这条命令:
$ killall -HUP inetd
程序可以用signal库函数来处理信号,定义如下:
#include <signal.h>

void (*signal(int sig, void (*func)(int)))(int);
这个相当复杂的函数定义说明,signal是一个带有sig和func两个参数的函数。准备捕获或忽略的信号由参数sig给出,接收到指定的信号后将要调用的函数由参数func给出。信号处理函数必须有一个int类型的参数(即接收到的信号代码)并且返回类型为void。signal函数本身也返回一个同类型的函数,即先前用来处理这个信号的函数,或者也可以用下面的两个特殊值之一来代替信号处理函数。
-------------------------------------------------------------------------------
SIG_IGN                       Ignore the signal.(忽略信号)
SIG_DFL                      Restore default behavior.(恢复默认行为)
--------------------------------------------------------------------------------
举例:
/*  We'll start by writing the function which reacts to the signal
    which is passed in the parameter sig.
    This is the function we will arrange to be called when a signal occurs.
    We print a message, then reset the signal handling for SIGINT
    (by default generated by pressing CTRL-C) back to the default behavior.
    Let's call this function ouch.  */

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

void ouch(int sig)
{
    printf("OUCH! - I got signal %d\n", sig);
    (void) signal(SIGINT, SIG_DFL);
}

/*  The main function has to intercept the SIGINT signal generated when we type Ctrl-C .
    For the rest of the time, it just sits in an infinite loop,
    printing a message once a second.  */

int main()
{
    (void) signal(SIGINT, ouch);

    while(1) {
        printf("Hello World!\n");
        sleep(1);
    }
}
函数ouch对通过参数sig传递进来的信号作出响应。信号出现时,程序调用该函数,它先打印一条消息,然后将信号SIGINT(默认情况下,按下Ctrl+C将产生这个信号)的处理方式恢复为默认行为。
注意:不推荐大家使用signal接口。之所以会在这里介绍它,是因为你可能会在许多老程序中看到它的应用。稍后介绍一个定义更清晰、执行更可靠的函数sigaction,在所有的新程序中都应该使用这个函数。
siganl函数返回的是先前对指定信号进行处理的信号处理函数的函数指针,如果未定义信号处理函数,则返回SIG_ERR并设置errno为一个正整值。如果给出的是一个无效的信号,或者尝试处理的信号是不可捕获或不可忽略的信号(如SIGKILL),errno将被设置为EINVAL。

1.发送信号
进程可以通过调用kill函数向包括它本身在内的其他进程发送一个信号。如果程序没有发送该信号的权限,对kill函数的调用就讲失败,失败的常见原因是目标进程由另一个用户所拥有。这个函数和同名的shell命令完成相同的功能,定义如下:
#include <sys/types.h>
#include <signal.h>

int kill(pid_t pid, int sig);
kill函数把参数sig给定的信号发送给由参数pid给出的进程号所指定的进程,成功时它返回0。要想发送一个信号,发送进程必须拥有相应的权限。这通常意味着两个进程必须拥有相同的用户ID(即你只能发送信号给属于自己的进程,但超级用户可以发送信号给任何进程)。
kill调用会在失败时返回-1并设置errno变量。失败的原因可能是:给定的信号无效(errno设置为EINVAL);发送进程权限不够(errno设置为EPERM);目标进程不存在(errno设置为ESRCH)。
信号向我们提供了一个有用的闹钟功能。进程可以通过调用alarm函数在经过预定时间后发送一个SIGALRM信号。
#include <unistd.h>

unsigned int alarm(unsigned int seconds);
举例:
/*  In alarm.c, the first function, ding, simulates an alarm clock.  */

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

static int alarm_fired = 0;

void ding(int sig)
{
    alarm_fired = 1;
}

/*  In main, we tell the child process to wait for five seconds
    before sending a SIGALRM signal to its parent.  */

int main()
{
    int pid;

    printf("alarm application starting\n");

    if((pid = fork()) == 0) {
        sleep(5);
        kill(getppid(), SIGALRM);
        exit(0);
    }

/*  The parent process arranges to catch SIGALRM with a call to signal
    and then waits for the inevitable.  */

    printf("waiting for alarm to go off\n");
    (void) signal(SIGALRM, ding);

    pause();
    if (alarm_fired)
        printf("Ding!\n");

    printf("done\n");
    exit(0);
}
pause函数的定义如下:
#include <unistd.h>

int pause(void);
当它被一个信号中断时,将返回-1(如果下一个接收到的信号没有导致程序终止的话)并把errno设置为EINTR。当需要等待信号时,一个更常见的方法是使用稍后将要介绍的sigsuspend函数。

一个健壮的信号接口
我们已经对用signal和其相关函数来生成和捕获信号做了比较深入的介绍,因为它们在传统的UNIX编程中很常见。但X/Open和UNIX规范推荐了一个更新和更健壮的信号编程接口:sigaction。
#include <signal.h>

int sigaction(int sig, const struct sigaction *act, struct sigaction *oact);
sigaction结构定义在文件signal.h中,它的作用是定义在接收到参数sig指定的信号后应该采取的行动。该结构至少应该包括以下几个成员:
void (*) (int) sa_handler    /*  function, SIG_DFL or SIG_IGN
sigset_t sa_mask             /*  signals to block in sa_handler
int sa_flags                 /*  signal action modifiers
sigaction函数设置与信号sig关联的动作。如果oact不是空指针,sigaction将把原先对该信号的动作写到它指向的位置。如果act是空指针,则sigaction函数就不需要再做其他设置了,否则将在该参数中设置对指定信号的动作。
与signal函数一样,sigaction函数会在成功时返回0,失败时返回-1.如果给出的信号无效或者试图对一个不允许被不会或忽略的信号进行捕获或忽略,错误变量errno将被设置为EINVAL。
在参数act指向的sigaction结构中,sa_handler是一个函数指针,它指向接收到信号sig时将被调用的信号处理函数。它相当于前面见到的传递给函数signal的参数func。我们可以将sa_handler字段设置为特殊值SIG_IGN和SIG_DFL,它们分别表示信号将被忽略或把对该信号的处理方式恢复为默认动作。
sa_mask成员指定了一个信号集,在调用sa_handler所指向的信号处理函数之前,该信号集将被加入到进程的信号屏蔽字中。这是一组将被阻塞且不会传递给该进程的信号。设置信号屏蔽字可以防止前面看到的信号在它的处理函数还未运行结束时就被接收到的情况。使用sa_mask字段可以消除这一竞态条件。
但是,由sigaction函数设置的信号处理函数在默认情况下是不被重置的,如果希望获得类似前面用第二次signal调用对信号处理进行重置的效果,就必须在sa_flag成员中包含值SA_RESETHAND。在深入了解sigaction函数之前,我们先用sigaction替换signal来重写前面的程序。
举例:
#include <signal.h>
#include <stdio.h>
#include <unistd.h>

void ouch(int sig)
{
    printf("OUCH! - I got signal %d\n", sig);
}

int main()
{
    struct sigaction act;

    act.sa_handler = ouch;
    sigemptyset(&act.sa_mask);
    act.sa_flags = 0;

    sigaction(SIGINT, &act, 0);

  while(1) {
    printf("Hello World!\n");
    sleep(1);
  }
}
要想终止该程序,只能按下Ctrl+\组合键,默认情况下产生SIGQUIT信号。

2.信号集
头文件signal.h定义了类型sigset_t和用来处理信号集的函数。sigaction和其他函数将用这些信号集来修改进程在接收到信号时的行为。
#include <signal.h>

int sigaddset(sigset_t *set, int signo);
int sigemptyset(sigset_t *set);
int sigfillset(sigset_t *set);
int sigdelset(sigset_t *set, int signo);
这些函数执行的操作如它们的名字所示。sigemptyset将信号集初始化为空。sigfillset将信号集初始化为包含所有已定义的信号。sigaddset和sigdelset从信号集中增加或删除给定的信号(signo)。它们在成功时返回0,失败时返回-1并设置errno。只有一个错误代码被定义,即当给定的信号无效时,errno将设置为EINVAL。
函数sigismember判断一个给定的信号是否是一个信号集的成员。如果是就返回1;如果不是,它就返回0;如果给定的信号无效,它就返回-1并设置errno为EINVAL。
#include <signal.h>

int sigismember(sigset_t *set, int signo);
进程的信号屏蔽字的设置或检查工作由函数sigprocmask来完成。信号屏蔽字是指当前被阻塞的一组信号,它们不能被当前进程接收到。
#include <signal.h>

int sigprocmask(int how, const sigset_t *set, sigset_t *oset);
sigprocmask函数可以根据参数how指定的方法修改进程的信号屏蔽字。新的信号屏蔽字由参数set指定,而原先的信号屏蔽字将保存到信号集oset中。
参数how的可能取值如下:
----------------------------------------------------------------------------------------------------------------------------------------------------------------
SIG_BLOCK                      The signals in set are added to the signal mask. (把参数set中的信号添加到信号屏蔽字中)
SIG_SETMASK                 The signal mask is set from set. (把信号屏蔽字设置为参数set中的信号)
SIG_UNBLOCK                 The signals in set are removed from the signal mask.(从信号屏蔽字中删除参数set中的信号)
-----------------------------------------------------------------------------------------------------------------------------------------------------------------
如果参数set为空指针,how的值就没有意义了。此时这个调用的唯一目的就是把当前信号屏蔽字的值保存到oset中。
如果sigprocmask成功完成,它将返回0;如果参数how取值无效,它将返回-1并设置errno为EINVAL。
如果一个信号被进程阻塞,它就不会传递给进程,但会停留在待处理状态。程序可以通过调用函数sigpending来查看它阻塞的信号中有哪些正停留在待处理状态。
#include <signal.h>

int sigpending(sigset_t *set);
这个函数的作用是,将被阻塞的信号中停留在待处理状态的一组信号写到参数set指向的信号集中。成功时它将返回0,否则返回-1并设置errno以表明错误的原因。如果程序需要处理信号,同时又需要控制信号处理函数的调用时间,这个函数就很有用了。
进程可以通过调用sigsuspendhas农户挂起自己的执行,直到信号集中的一个信号到达为止。这是我们前面见到的pause函数更通用的一种表现形式。
#include <signal.h>

int sigsuspend(const sigset_t *sigmask);
sigsuspend函数将进程的屏蔽字替换为由函数sigmask给出的信号集,然后挂起程序的执行。程序将在信号处理函数执行完毕后继续执行。如果接收到的信号终止了程序,sigsuspend就不会返回;如果接收到的信号没有终止程序,sigsuspend就返回-1并将errno设置为EINTR。

(1)sigaction标志

用在sigaction函数里的sigaction结构中的sa_flag字段可以包含下表中的取值,它们用于改变信号的行为。
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
SA_NOCLDSTOP                    Don’t generate SIGCHLD when child processes stop.(子进程停止时不产生SIGCHLD信号)
SA_RESETHAND                    Reset signal action to SIG_DFL on receipt.(将对此信号的处理方式在信号处理函数入口处重置为SIG_DFL)
SA_RESTART                          Restart interruptible functions rather than error with EINTR.(重启可中断哦函数而不是给出EINTR错误)
SA_NODEFER                         Don’t add the signal to the signal mask when caught.(捕获到信号时不将它添加到信号屏蔽字中)
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
当一个信号被捕获时,SA_RESETHAND标志可以用来自动清除它的信号处理函数。
程序中使用的许多系统调用都是可中断的。也就是说,当接收到一个信号时,它们将返回一个错误并将errno设置为EINTR,表明函数是因为一个信号而返回的。使用了信号的应用程序需要特别注意这一行为。如果sigaction调用中的sa_flag字段设置了SA_RESTART标志,那么在信号处理函数执行完之后,函数将被重启而不是被信号中断。
一般的做法是,信号处理函数正在执行时,新接收到的信号将在该处理函数的执行期间被添加到进程的信号屏蔽字中。这防止了同一信号的不断出现引起信号处理函数的再次运行。如果信号处理函数是一个不可重入的函数,在它结束对第一个信号的处理之前又让另一个信号再次调用它就有可能引起问题。但如果设置了SA_NODEFER标志,当程序接收到这个信号时就不会改变信号屏蔽字。
信号处理函数可以在其执行期间被中断并再次被调用。当返回到第一次调用时,它能否继续正确操作是很关键的。这不仅仅是递归的问题,而是可重入(可以安全地进入和再次执行)的问题。Linux内核中,在同一时间负责处理多个设备的中断服务例程就需要是可重入的,因为优先级更高的中断可能会在同一段代码的执行期间“插入”进来。
下表列出的是可以在信号处理函数中安全调用的函数。X/Open规范保证它们都是可重入的或者本身不会再生成信号的。
所有未列在下表中的函数,在涉及信号处理时,都被认为是不安全的。
-----------------------------------------------------------------------------------------------------------------------------------
access                alarm                          cfgetispeed                           cfgetospeed
cfsetispeed        cfsetospeed             chdir                                      chmod
chown                 close                           creat                                       dup2
dup                      execle                         execve                                    _exit
fcntl                      fork                             fstat                                        getegid
geteuid               getgid                          getgroups                              getpgrp
getpid                 getppid                        getuid                                      kill
link                      lseek                            mkdir                                      mkfifo
open                   pathconf                      pause                                     pipe
read                    rename                       rmdir                                       setgid
setpgid               setsid                          setuid                                     sigaction
sigaddset          sigdelset                     sigemptyset                         sigfillset
sigismember    signal                          sigpending                            sigprocmask
sigsuspend       sleep                           stat                                          sysconf
tcdrain                 tcflow                         tcflush                                    tcgetattr
tcgetpgrp            tcsendbreak            tcsetattr                                 tcsetpgrp
time                     times                           umask                                    uname
unlink                  utime                           wait                                         waitpid
write

-------------------------------------------------------------------------------------------------------------------------------------

(2)常用信号参考
下表中信号的默认动作都是异常终止进程,进程将以_exit调用方式退出(它类似exit,但在返回到内核之前不作任何清理工作)。但进程的结束状态会传递到wait和waitpid函数中去,从而表明进程是因某个特定的信号而异常终止的。

Signal Name                                                          Description
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
SIGALRM                                       Generated by the timer set by the alarm function.(由alarmhas农户设置的定时器产生)
SIGHUP                                          Sent to the controlling process by a disconnecting terminal, or by the controlling process on termination to each foreground process.
                                                         (由一个处于非连接状态的终端发送给控制进程,或者由控制进程在自身结束时发送给每个前台进程)
SIGINT                                            Typically raised from the terminal by typing Ctrl+C or the configured interrupt character.(一般由从终端敲入的Ctrl+C或预先设置好的中断字
                                                         符产生)
SIGKILL                                          Typically used from the shell to forcibly terminate an errant process, because this signal can’t be caught or ignored.(因为这个信号不能  
                                                         被捕获或忽略,所以一般在shell中用它来强制终止异常进程)
SIGPIPE                                         Generated by an attempt to write to a pipe with no associated reader.(如果在向管道写数据时没有与之对应的读进程,就会产生该信号)
SIGTERM                                       Sent as a request for a process to finish. Used by UNIX when shutting down to request that system services stop. This is the default
                                                         signal sent from the kill command.(作为一个请求被发送,要求进程结束运行。UNIX在关机时用这个信号要求系统服务停止运行。它是kill
                                                         命令默认发送的信号)
SIGUSR1, SIGUSR2                    May be used by processes to communicate with each other, possibly to cause them to report status information.(进程之间可以用这个信
                                                       号进行通信,例如让进程报告状态信息等)
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
默认情况下,下表中的信号也会引起进程的异常终止。但可能还会有一些与具体实现相关的其他动作,比如创建core文件等。

Signal Name                                                                               Description
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
SIGFPE                                           Generated by a floating-point arithmetic exception.(由浮点运算产生异常)
SIGILL                                             An illegal instruction has been executed by the processor. Usually caused by a corrupt program or invalid shared memory module.(处
                                                         理器执行了一条非法的指令。这通常是由一个崩溃的程序或无效的共享内存模块引起的)
SIGQUIT                                          Typically raised from the terminal by typing Ctrl+\ or the configured quit character.(一般由从终端敲入的Ctrl+\或预先设置好的退出字符产
                                                           生)
SIGSEGV                                        A segmentation violation, usually caused by reading or writing at an illegal location in memory either by exceeding array bounds or 
                                                         dereferenc-ing an invalid pointer. Overwriting a local array variable and corrupting the stack can cause a SIGSEGV to be raised when a 
                                                          function returns to an illegal address.(段冲突。一般是因为对内存中的无效地址进行读写而引起的,例如超越数组边界或解引用无效指
                                                          针。当函数返回到一个非法地址时,覆盖局部数组变量和引起栈崩溃都会引发SIGSEGV信号)
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

默认情况下,进程接收到下表中的信号时将会被挂起。

Signal Name                                                                                      Description
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
SIGSTOP                                          Stop executing (can’t be caught or ignored).(停止执行(不能被捕获或忽略))
SIGTSTP                                           Terminal stop signal, often raised by typing Ctrl+Z.(终端挂起信号。通常因按下Ctrl+Z而产生)
SIGTTIN, SIGTTOU                         Used by the shell to indicate that background jobs have stopped because they need to read from the terminal or produce output.(输入
                                                            或产生输出而暂停运行)
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
SIGCONT信号的作用是重启被暂停的进程,如果进程没有暂停,则忽略该信号。SIGCHLD信号在默认情况下被忽略。

Signal Name                                                                                      Description
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
SIGCONT                                          Continue executing, if stopped.(如果进程被暂停,就继续执行)
SIGCHLD                                          Raised when a child process stops or exits.(子进程暂停或退出时产生)
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值