zoukankan      html  css  js  c++  java
  • 翻译:应用设计模式简化信号处理

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

  • 相关阅读:
    通讯总线 | 串口
    shell | 命令实用汇集
    编码 | 宏定义格式化日志信息
    shell | 脚本传参
    注释 | 代码注释原则
    GCC编译优化选项介绍
    shell | 已知进程查找命令
    网络 | ifconfig 配置 IP 掩码 网关
    Makefile | 使用eclipse软件自动生成Makefile文件
    VIO系统的IMU与相机时间偏差标定
  • 原文地址:https://www.cnblogs.com/diylab/p/1631950.html
Copyright © 2011-2022 走看看