翻译:应用设计模式简化信号处理

原文: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。

class Event_Handler
{
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。

具体事件处理器
class SIGINT_Handler : public Event_Handler
{
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 的具体事件处理器。

class SIGQUIT_Handler : public Event_Handler
{
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则是控件的入口地址。


 

http://bbs.pediy.com/showthread.php?t=86962

原文地址:https://www.cnblogs.com/diylab/p/1631950.html