2011-10-09 wcdj
BLP 4th P.388
知识点:
(1) 进程的结构、类型和调度
(2) 用不同的方法启动新进程
(3) 父进程、子进程和僵尸进程
(4) 什么是信号以及如何使用它们
signal, sigaction, 信号集
信号 —— 是UNIX和Linux系统响应某些条件而产生的一个事件。接收到该信号的进程会相应地采取一些行动。
生成(raise)一个信号 -> 捕获(catch)一个信号 -> 响应或(至少对于一些信号)忽略一个信号
信号是由于某些错误条件而生成的,如内存段冲突,浮点处理器错误或非法指令等。
signal函数
程序可以用signal库函数来处理信号。
函数原型:
#include <signal.h>
void (*signal(int sig, void (*func)(int)))(int);
准备捕获或忽略的信号由参数sig给出,接收到指定的信号后将要调用的函数由参数func给出。
signal函数本身也返回一个同类型的函数,即先前用来处理这个信号的函数。(注意)
可以用下面两个特殊值之一来代替信号处理函数:
SIG_IGN 忽略信号
SIG_DFL 恢复默认行为
例1:使用signal截获SIGINT信号。
// ctrl.c
#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);
}
int main()
{
(void)signal(SIGINT, ouch);
while(1){
printf("Hello World!\n");
sleep(1);
}
}
注意:
(1) 信号处理函数使用了一个单独的整数参数,它就是引起该函数被调用的信号代码。如果需要在同一个函数中处理多个信号,这个参数就很有用了。
(2) 在信号处理函数中,调用如printf这样的函数是不安全的。因此,一个有用的技巧是,在信号处理函数中设置一个标志,然后在主程序中检查该标志,如需要就打印一条消息。
(3) 如果想保留信号处理函数,让它继续响应用户的Ctrl+C组合键,我们就需要再次调用signal函数来重新建立它。这会使信号在一段时间内无法得到处理,这段时间从调用中断函数开始,到信号处理函数的重建为止。如果在这段时间内程序接收到第二个信号,它就会违背我们的意愿终止程序的运行,而不是打印出一条消息。
(4) 我们不推荐使用signal接口。之所以介绍它,是因为在许多老程序中有它们的应用。我们应该使用一个定义更清晰、执行更可靠的函数sigaction。
一个健壮的信号接口
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函数一样,成功返回0,失败返回-1。
如果给出的信号无效或者试图对一个不允许被捕获或忽略的信号进行捕获或忽略,错误变量errno将被设置为EINVAL。
在参数act指向的sigaction结构中,sa_handler是一个函数指针,它指向接收到信号sig时将被调用的信号处理函数。它相当于传递给signal的参数func。我们可以将sa_handler字段设置为特殊值SIG_IGN和SIG_DEL,它们分别表示信号将被忽略或对该信号的处理方式恢复为默认动作。
sa_mask成员指定了一个信号集,在调用sa_handler所指向的信号处理函数之前,该信号集将被加入到进程的信号屏蔽字中。这是一组将被阻塞且不会传递给该进程的信号。设置信号屏蔽字可以防止信号在它的处理函数还未运行结束时就被接收到的情况。使用sa_mask字段可以消除这一竞态条件。
注意:
由sigaction函数设置的信号处理函数在默认情况下是不被重置的,如果希望获得类似前面二次signal调用对信号处理进行重置的效果,就必须在sa_flags成员中包含值SA_RESETHAND。
例2:使用sigaction截获SIGINT信号。
// ctrlc2.c
#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;
act.sa_flags = 0;
sigaction(SIGINT, &act, 0);
while (1)
{
printf("Hello World!\n");
sleep(1);
}
}
要想终止这个程序,只能按下Ctrl+\组合键,它在默认情况下产生SIGQUIT信号。终止这个程序后,将会在当前目录下产生一个core文件(前提条件打开core文件:ulimit -c unlimited)。可以安全地删除这个core文件。
信号集
头文件signal.h定义了类型sigset_t和用来处理信号集的函数。sigaction和其他函数将用这些信号集来修改进程在接收到信号时的行为。
#include <signal.h>
int sigemptyset( sigset_t *set ); 将信号集初始化为空
int sigfillset( sigset *set ); 将信号集初始化为包含所有已定以的信号
int sigaddset( sigset_t *set, int signo ); 从信号集中增加给定的信号signo
int sigdelset( sigset_t *set, int signo ); 从信号集中删除给定的信号signo
返回值:成功时返回0,失败时返回-1并设置errno。
只有一个错误代码被定义,即当给定的信号无效时,errno将设置为EINVAL。