11.4 信号量
信号是UNIX和linux系统响应某些条件而产生的一个事件.接收到该信号的进程会相应地采取一些行动.用术语生成(raise)表示一个信号的产生,使用术语捕获(catch)表示接收一个信号.信号是由于某些错误条件而生成的,例如内存段冲突,浮点处理器错误或非法指令等.它们由shell和终端处理器生成来引起中断,它们还可以作为在进程间传递消息或修改行为的一种方式,明确地由一个进程发送给另一个进程.无论何种情况,它们的编程接口都是相同的.信号可以生成,捕获,响应或忽略.信号的名称是在头文件signal.h中定义的,它们以SIG开头,如下所示:
信号名称 说明
SIGABORT 进程异常终止
SIGALRM 超时警告
SIGFPE 浮点运算异常
SIGHUP 连接挂断
SIGILL 非法指令
SIGINT 终端中断
SIGKILL 终止进程(此信号不能被捕获或忽略)
SIGPIPE 向无读进程的管道写数据
SIGQUIT 终端退出
SIGSEGV 无效内存段访问
SIGTERM 终止
SIGUSR1 用户定义信号1
SIGUSR2 用户定义信号2
如果进程接收这些信号中的一个,但事先并没有安排捕获它,进程将会立刻终止.通常,系统将生成核心转储文件core,并将其放在当前目录下.该文件是进程在内存中的映像,它对程序的调试很有用处.
其他信号如下:
信号名称 说明
SIGCHLD 子进程已经停止或退出
SIGCONT 继续执行暂停进程
SIGSTOP 继续执行(此信号不能被捕获或忽略)
SIGTSTP 终端挂起
SIGTTIN 后台进程尝试读操作
SIGTTOU 后台进程尝试写操作
SIGCHLD信号对于管理子进程很有用.默认情况下,它是被忽略的.其余的信号会使接收它们的例程停止运行,但SIGCONT是个例外,它的作用是让进程恢复并继续执行.shell脚本通过它来控制作业,但用户程序很少会用到它.
如果想发送一个信号给进程,而该进程并不是当前的前台进程,就需要使用kill命令.该命令需要一个可选的信号代码或者信号名称和一个接收信号的目标进程PID(这个PID一般需要用ps命令查询).例如,如果要向运行在另一个终端上的PID为512的进程发送"挂断"信号,可以使用如下命令:
$ kill -HUP 512
kill命令有一个有用的变体叫killall,它可以给运行着某一命令的所有进程发送信号.如果不知道某个进程的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或SIG_DFL.
编写程序ctrlc.c,它将响应用户敲入的Ctrl+C组合键,在屏幕上打印出一条适当的消息而不是终止程序的运行.当用户第二次按下Ctrl+C时,程序将结束运行.
#include <signal.h>
#include <stdio.h>
#include <unistd.h>
/* 函数ouch对通过参数sig传递进来的信号作出响应,信号出现时,程序调用该函数,它先打印一条消息,然后将信号SIGINT的处理方式恢复为默认行为*/
void ouch(int sig){
printf("OUCH! - I got signal %d\n", sig);
(void) signal(SIGINT, SIG_DFL);
}
/* main函数的作用是截获按下Ctrl+C组合键时产生的SIGINT信号,没有信号出现时,它会在一个无限循环中每隔一秒打印一条消息*/
int main(){
(void)signal(SIGINT, ouch);
while(1){
printf("hello world!\n");
sleep(1);
}
}
signal函数返回的是先前对指定信号进行处理的信号处理函数的函数指针,如果未定义信号处理函数,则返回SIG_ERR并设置errno为一个正数值.如果给出的是一个无效的信号,或者尝试处理的信号是不可捕获或不可忽略的信号(如SIGKILL),errno将被设置为EINVAL.