原文:http://www.cse.wustl.edu/~schmidt/signal-patterns.html
应用设计模式简化信号处理
道格拉斯.施密特
c++ report
1998年 4月
本文展示了哪些貌似棘手的编程问题可以用基本的设计模式来解决。本文旨在演示设计模式--比如单件(GOF),适配器[GOF],以及钩子[Pree]模式如何地简化了组件的设计,避免了信号和信号捕获的常见的陷阱和缺陷。
1、信号的技术背景
信 号是“软中断”,信号允许应用程序异步地捕获各种类型的事件。要在单线程的程序中支持多个“控制流”,他们是非常有用的。许多操作系统支持信号,包括各种 版本的 unix 和 win32。此外,ANSI C 标准库和 ANSI/ISO C++ 标准库,也定义了对信号的支持。
信号有很多种,包括:
SIGSEGV -- 由硬件引发的错误。 例如,对一个空指针进行取地址操作。
SIGALRM -- 指示了一个软件定时器的超时事件。
SIGINT -- 用户键入了中断符,例如 control-C.
应用程序可以给操作系统系统的信号处理 API (signal 或 sigaction)传入函数地址,以捕获上面这些以及其它的信号。同样地,通过给信号函数传入 SIG_IGN, 应用程序可以选择性地忽略大多数的信号。像创建线程这样的 API 一样,标准的信号捕捉函数是 c 式的函数,而不是c++对象或c++成员函数。
当操作系统产生一个信号,检查到某个应用程序期望捕获它。当前执行的函数会被“屏蔽”掉。要实现这样的功能,首先,操作系统首先将程序上下文压栈。 然后,操作系统自动唤醒应用程序的预注册的信号处理函数。当这个函数完成后,操作系统再恢复程序上下文,并将程序的控制权交给信号发生前的地点。
2 信号捕获示例
sig_atomic_t graceful_quit = 0;
sig_atomic_t abortive_quit = 0;
/* 处理 SIGINT 的函数 */
void SIGINT_handler (int signum)
{
assert (signum == SIGINT);
graceful_quit = 1;
}
/* 处理 SIGQUIT 的函数 */
void SIGQUIT_handler (int signum)
{
assert (signum == SIGQUIT);
abortive_quit = 1;
}
/* ... */
int main (void)
{
struct sigaction sa;
sigemptyset (&sa.sa_mask);
sa.sa_flags = 0;
/* 注册处理 SIGINT 的函数 */
sa.sa_handler = SIGINT_handler;
sigaction (SIGINT, &sa, 0);
/* 注册处理 SIGQUIT 的函数 */
sa.sa_handler = SIGQUIT_handler;
sigaction (SIGQUIT, &sa, 0);
/* 事件循环 */
while (graceful_quit == 0
&& abortive_quit == 0)
do_work ();
if (abortive_quit == 1) {
_exit (1);
}
else if graceful_quit {
clean_up ();
exit (0);
}
/* NOTREACHED */
}
2.1 尽管上面展示的信号处理方法是可行的,但是它使用了全局变量。这不是一个良好的面向对象设计。例如,注意到信号处理函数必须操作全局变量 graceful_quit 和 abortive_quit。在一个大型系统中,使用全局变量显然有悖单一职责原则和信息隐藏原则。在c++中使用全局变量也意味着难移植。因为全局静态 变量初始化顺序是不可靠的。
通常,在信号处理函数中使用全局变量会引起“不一致”,特别是
1)全局变量需要同信号处理函数交互。
2)结构良好的面向对象设计有于其它进程。
2.2. 解决办法
As usual, the way out of this programming morass is to apply several common design patterns, such as Singleton [GOF], Adapter [GOF], and Hook Method [Pree]. This section outlines the steps required to accomplish this task.
A. 定义一个 Hook Method 接口
-- We'll start out by defining an Event_Handler interface that contains, among other things, a virtual method called handle_signal:
首先我们需要先定义一个 虚方法,这个虚方法名叫 handle_signal。
{
public:
// Hook method for the signal hook method.
virtual int handle_signal (int signum) = 0;
// ... other hook methods for other types of
// events such as timers, I/O, and
// synchronization objects.
};
Programmers subclass from Event_Handler and override the handle_signal hook method to perform application-specific behavior in response to designated signals. Instances of these subclasses, i.e., concrete event handlers, are the targets of the hook method callback. These callbacks are invoked through a Signal_Handler singleton, which is described next.
B. 定义一个单件的信号处理组件
C.定义具体事件处理器。
下一步就是定义具体事件处理器。这些处理器由 Event_Handler 触发。例如,下面这个具体事件处理器可用来处理 SIGINT。

{
public:
SIGINT_Handler (void)
: graceful_quit_ (0)
{
}
// Hook method.
virtual int handle_signal (int signum)
{
assert (signum == SIGINT);
this->graceful_quit_ = 1;
}
// Accessor.
sig_atomic_t graceful_quit (void)
{
return this->graceful_quit_;
}
private:
sig_atomic_t graceful_quit_;
};
这 个具体事件处理器维护其数据成员 graceful_quit_ 的状态。其 hook 方法 handle_signal 将 graceful_quit_ 的值置为1。正如下面所展示的,这个值指示应用程序优雅地关闭。下面是处理 SIGQUIT 的具体事件处理器。
{
public:
SIGQUIT_Handler (void)
: abortive_quit_ (0) {}
// Hook method.
virtual int handle_signal (int signum)
{
assert (signum == SIGQUIT);
this->abortive_quit_ = 1;
}
// Accessor.
sig_atomic_t abortive_quit (void)
{ return this->abortive_quit_; }
private:
sig_atomic_t abortive_quit_;
};
这个具体事件处理器的工作方式与 SIGINT_Handler 同理。
D. 用 信号处理单件 注册 事件处理子类
应用程序可以使用 Signal_Handler 组件 和 Event_Handler 基类定义派生的事件处理,并使用 Signal_Handler::instance 注册它们。代码如下:
int main (void)
{
SIGINT_Handler sigint_handler;
SIGQUIT_Handler sigquit_handler;
// 为 SIGINT 注册处理器
Signal_Handler::instance ()->register_handler
(SIGINT, &sigint_handler);
// 为 SIGQUIT 注册处理器
Signal_Handler::instance ()->register_handler
(SIGQUIT, &sigquit_handler);
// Run the main event loop.
while (sigint_handler.graceful_quit () == 0
&& sigquit_handler.abortive_quit () == 0)
do_work ();
if (sigquit_handler.abortive_quit () == 1)
_exit (1);
else /* if sigint_handler.graceful_quit () */ {
clean_up ();
return 1;
}
}
通过使用上面展示的 Signal_Handler 组件,我们可以用单件、适配器、hook 方法 等设计模式,唤醒 具体事件处理器 的 handle_signal hook 方法,去处理 SIGINT 和 SIGQUIT 信号。这种设计对原来的 C方式 的处理是一种改进。因为它不需要全局的或静态的变量。特别是每个具体事件处理器的状态可以在一个对象的内部进行维护。
基本设计有很多变式。例如,我们可以使用各种标志和信号掩码支持其它的信号语义。同样地,我们可以允许一个信号注册多个 Event_Handler。此外,我们也可以支持 POSIX 扩展的信号API。但是,这些解决方案涉及的基本要素能使我们认识到几个基本的设计模式-- Singleton, Adapter, and Hook Method--的力量。
ASX: An Object-Oriented Framework for Developing Distributed Applications
MFC也采用了类似的机制。
例如,一个按钮被单击的时候,将会产生以下一连串的函数调用:
CWinThread::PumpMessage -> CWnd::PretranslateMessage -> CWnd::WWalkPreTranslateMessate -> CD1Dlg::PreTranslateMessage -> CDialog::PreTranslateMessage -> CWnd::PreTranslateInput -> CWnd::IsDialogMessageA -> USER32内核 -> AfxWndProcBase -> AfxWndProc -> AfxCallWndProc -> CWnd::WindowProc -> CWnd::OnWndMsg -> CWnd::OnCommand -> CDialog::OnCmdMsg -> CCmdTarget::OnCmdMsg -> _AfxDispatchCmdMsg -> CD1Dlg::OnButton1()
以上, CD1Dlg::OnButton1() 是用户自定义的按钮事件。
消息映射结构:
struct AFX_MSGMAP
{
#ifdef _AFXDLL
const AFX_MSGMAP* (PASCAL* pfnGetBaseMap)();
#else
const AFX_MSGMAP* pBaseMap;
#endif
const AFX_MSGMAP_ENTRY* lpEntries;
};
在这个结构中,我们只关心lpEntries这个值,因为这个值指向“消息处理映射入口”数组的首地址
消息处理映射入口结构
AFX_MSGMAP_ENTRY
{
nMessage;
nCode ;
nID ;
nLastID ;
nSig ;
lpEntry ;
}
在这个AFX_MSGMAP_ENTRY里面,nID保存了控件的ID,lpEntry则是控件的入口地址。