引言
信号是软件中断。很多比较重要的应用程序都需要处理信号,信号提供了一种处理异步事件的方法,例如,终端用户键入中断键,会通过信号机制停止一个程序,或及早终止管道中的下一个程序。
信号概念
首先,每个信号都有一个名字。这些名字都以3个字符SIG开头。例如,SIGABRT是夭折信号,当进程调用abort函数时产生这种信号。SIGALRM是闹钟信号,由alarm函数设置的定时器超时后将产生此信号。
在头文件
不存在编号为0的信号。kill函数对信号编号0有特殊的应用。POSIX.1将此种信号编号值称为空信号。
很多条件可以产生信号:
- 当用户按某些终端键时,引发终端产生的信号。在终端上按Delete键(或者很多系统中的Ctrl+C键)通常产生中断信号(SIGINT)。这是停止一个已失去控制程序的方法。
- 硬件异常产生信号:除数为0、无效的内存引用等。这些条件通常由硬件检测到,并通知内核。然后内核为该条件发生时正在运行的进程产生适当的信号。例如,对执行一个无效内存引用的进程产生SIGSEGV信号。
- 进程调用kill函数可将任意信号发送给另一个进程或进程组。自然,对此有所限制,接受信号进程和发送信号进程的所有者必须相同,或发送信号进程的所有者必须是超级用户。
- 用户可用kill命令将信号发送给其他进程。此命令只是kill函数的接口。常用此命令终止一个失控的后台进程。
- 当检测到某种软件条件已经发生,并应将其通知有关进程时也产生信号。这里指的不是硬件产生条件(如除以0),而是软件条件。例如SIGURG(在网络连接上传来带外的数据)、SIGPIPE(在管道的读进程已终止后,一个进程写此管道)以及SIGALRM(进程所设置的定时器已经超时)。
信号是异步事件的经典实例。产生信号的事件对进程而言是随机出现的。进程不能简单的测试一个变量(如errno)来判断是否发生了一个信号,而是必须告诉内核“在此信号发生时,请执行下列操作”。
在某个信号出现时,可以告诉内核按下列3中方式之一进行处理,我们称之为信号的处理或与信号相关的动作。
- 忽略此信号。大多数信号都可使用这种方式进行处理,但有两种信号却绝不能忽略,它们是SIGKILL和SIGSTOP。这两种信号不能被忽略的原因是:它们向内核和超级用户提供了使进程终止或停止的可靠方法。另外,如果忽略某些由硬件异常产生的信号(如非法内存引用或除以0),则进程的运行行为是未定义的。
- 捕获信号。为了做到这一点,要通知内核在某种信号发生时,调用一个用户函数。在用户函数中,可执行用户希望对这种事件进行的处理。比如,如果捕获到SIGCHLD信号,则表示一个子进程已经终止,所以此信号的捕获函数可以调用waitpid以取得该子进程的进程ID以及它的终止状态。注意:不能捕获SIGKILL和SIGSTOP信号。
- 执行系统默认动作,对大多数系统默认动作是终止该进程。
下面较详细地逐一说明这些信号:
参考UNIX环境高级编程。
函数signal
UNIX系统信号机制最简单的接口是signal函数。
|
|
通过typedef可以转换成这样:
|
|
也就是说,signal有两个参数,一个是int,一个是Sigfunc ,返回值也是Sigfunc ,该指针指向一个参数为int,无返回值的函数。
signo参数是上图中的信号名。func的值是常量SIG_IGN、常量SIGDFL或当接到此信号后要调用的函数的地址。
- SIG_IGN,则向内核表示忽略此信号(记住有两个信号SIGKILL和SIGSTOP不能忽略)。
- SIG_DFL,则表示接到此信号后的动作是系统默认动作。
- 当指定函数地址时,则在信号发生时,调用该信号,我们称这种处理为捕获该信号,称此函数为信号处理程序(signal handler)或信号捕获函数(signal-catching function)。
当调用signal设置处理程序时,第二个参数是指向该函数(也就是信号处理程序)的指针。signal的返回值则是指向在此之前的信号处理程序的指针。
signal 的第1个参数signum表示要捕捉的信号,第2个参数是个函数指针,表示要对该信号进行捕捉的函数,该参数也可以是SIG_DEF(表示交由系统缺省处理,相当于白注册了)或SIG_IGN(表示忽略掉该信号而不做任何处理)。signal如果调用成功,返回以前该信号的处理函数的地址,否则返回 SIG_ERR。
|
这些常量可用于表示“指向函数的指针,该函数要求一个整形参数,并且无返回值”。signal的第二个参数及其返回值就可用它们表示。这些常量所使用的3个值不一定是-1、0和1,但它们必须是3个值而绝不能是任一函数的地址。
注:
为什么不是这样定义的呢??
|
|
在网上搜索之后找到答案,C语言中是可以这样定义的,C语言中前向声明是可以省略参数的,意味着可以有任意多个参数:
|
|
只是将-1强制转换为一个指针,通过编译。就像
|
可以将SIG_ERR跟其他的信号理解的一样,是一个整数。
示例
下面给出了一个简单的信号处理程序,它捕获两个用户定义的信号并打印信号编号,pause函数,它使调用进程在接到一信号前挂起。
|
|
我们使程序在后台运行,并且用kill命令将信号发送给它。
注意,在UNIX系统中,杀死(kill)这个术语是不恰当的。kill命令和kill函数只是将一个信号发送给一个进程或进程组。该信号是否终止进程取决于该信号的类型,以及进程是否安排了捕获该信号。
|
|
程序启动
当执行一个程序时,所有信号的状态都是系统默认或者忽略。通常所有信号都被设置为它们的默认动作,除非调用exec的进程忽略该信号。确切地讲,exec函数将原先设置为要捕获的信号都更改为默认状态,其他信号的状态不变(一个进程原先要捕获的信号,当其执行一个新程序后,就不再捕获了,因为信号捕获函数的地址很可能在所执行的新程序文件中已无意义)。
很多捕获这两个信号的交互程序具有下列形式的代码:
|
|
这样处理后,仅当SIGINT和SIGQUIT当前未被忽略时,进程才会捕获它们。
从signal的这两个调用中也可以看到这种函数的限制:不改变信号的处理方式就不能确定信号的当前处理方式。
进程创建
当一个进程调用fork时,其子进程继承父进程的信号处理方式,因为子进程在开始时复制了父进程的内存映像,所以信号捕获函数的地址在子进程中时有意义的。
示例
首先注册一个信号处理函数,发送信号后,注册信号处理函数更新新的注册信号函数,代替之前的信号处理函数。
|
|
|
|
|
|