可靠signal函数的实现

作者: lesca 分类: Ubuntu 发布时间: 2011-03-16 15:16

在上一篇文章中,笔者介绍了Ubuntu中signal()函数的不可靠性以及产生不可靠性的缺陷。
我们先来回忆一下这四种缺陷:

  1. 每次接到信号后,该信号复位成默认动作
  2. 不改变信号的处理方式就无法确定当前的信号处理方式
  3. 无法避免地导致系统调用的中断
  4. 进程不能关闭某些不想捕获的信号

那么如何才能消除这种不可靠性呢?这正是本文要讨论的话题。

lesca原创,转载请注明转自http://lesca.me/
由于POSIX.1对可靠信号例程的标准化,使我们可以回避上述问题。在给出解决方案之前,我们先介绍一些可靠信号的基本术语及语义。

可靠信号术语及语义
未决(Pending)信号:在信号产生(generation)和递送(delivery)之间该信号所处的状态
阻塞的信号:内核将会记住该信号,等到进程准备好时再递送
信号集(signal set):由POSIX.1定义的用于保存信号状态的一种数据结构(sigset_t)。
信号屏蔽字(signal mask):规定了被阻塞的信号的集合一个进程只有一个信号屏蔽字。它保存在称为信号集的数据结构中,并通过相关函数进行设置和查看。

sigaction()函数

int sigaction(int signo, const struct sigaction* restrict act, 
struct sigaction * restrict oact);

说明:该函数将signo(除了SIGKILLSIGSTOP)注册到一个信号动作act上,并将其现有的信号动作保存到oact
在这里,信号动作是一种的数据结构,它规定了当这个信号发生时所调用的信号处理函数、信号屏蔽字、以及可以设置的其他参数:
[cpp]
struct sigaction
{
void (*sa_handler)(int); // address of signal handler
// or SIG_IGN, or SIG_DFL
sigset_t sa_mask; // additional signals to block
int sa_flags; // signal options

/* alternative handler */
void (*sa_handler)(int, siginfo_t*, void*);
};
[/cpp]
我们要关注的的是该结构中前面三项,正确地使用它们能够解决先前遇到的问题。
sa_handler指定了要对捕获的信号进行处理的信号处理函数的地址。它有一个很好的特性:一旦设置了信号处理函数,那么在调用sigaction显式地改变它之前,该设置一直有效(回忆一下我们之前不得不在每次进行信号处理时再调用signal()函数注册该信号)。
sa_mask提供了在信号处理过程中屏蔽掉无用信号的接口,它有三个特性非常值得关注:a)默认情况下,信号发生时,该信号被自动加入信号屏蔽字中;b)设置信号屏蔽字、运行信号处理函数这两个过程是原子性的;c)执行信号处理函数执行完毕后,它又会恢复原来的信号屏蔽字(回忆一下一个进程只有一个信号屏蔽字),并且它与b中的操作一起也是原子性的。
sa_flags能够改变一些默认情况,比如上述的a情况。一般情况下我们无需改变它,只须要将它赋值为0即可。有关该标志的详细信息,请参阅联机手册。

signal()函数的可靠实现
通过对sigaction()函数的学习,我们发现它能够消除signal()函数的缺陷:

  1. 每次接到信号后,该信号复位成默认动作
  2. sigaction()保证该信号动作一直有效不会自动复位。

  3. 不改变信号的处理方式就无法确定当前的信号处理方式
  4. 调用sigaction()时,就已经获得了该信号当前的处理状态,无须调用两次(见前文的论述);另外将第二个参数设置为NULL可以在不改变当前信号处理函数的状态下获得当前信号处理动作。

  5. 无法避免地导致系统调用的中断
  6. 通过设置sa_flags中的SA_INTERRUPTSA_RESTART使我们可以自行决定采取什么动作。

  7. 进程不能关闭某些不想捕获的信号
  8. 通过设置sa_mask使我们能够关闭一些信号(被捕获的信号自动被阻塞),同时sigaction()保证屏蔽信号与信号处理之间是原子性的。

下面我们来看一个用sigaction()实现的signal()函数,它具有我们所希望的可靠性。
[cpp]
typedef void Sigfunc(int);

Sigfunc *
signal(int signo, Sigfunc *func)
{
struct sigaction act, oact;
act.sa_handler = func;

sigemptyset(&act.sa_mask);
act.sa_flags = 0;
#ifdef SA_INTERRUPT
act.sa_flags |= SA_INTERRUPT;
#endif
if(sigaction(signo, &act, &oact) < 0)
return(SIG_ERR);
return(oact.sa_handler);
}
[/cpp]

总结
使用旧版本的signal()函数使我们遇到无法避免的竞争条件(Ubuntu系统,内核版本:2.6.32-29-generic),我们应当使用具有可靠信号语义的函数处理信号。为了使用方便,我们重写了signal()函数,并使其具有该性质。

版权声明

本文出自 Lesca 技术宅,转载时请注明出处及相应链接。

本文永久链接: https://www.lesca.cn/archives/reliable-signal-function.html

如果觉得我的文章对您有用,请随意赞赏。您的支持将鼓励我继续创作!