最近更新: 2011-06-07

Vala - system signal agent

Unix系統的行程間通訊機制中,有一套稱為 signal 的信號機制。因為它被列在 POSIX 規範之中,所以本文將以 Posix signal 稱之。Psoxi signal 是一種簡單的事件通知機制。它將某些事件予以編號,例如 SIGHUP, SIGTERM 等。程序可以向系統註冊這些信號的處理函式。當特定事件發生時,系統就會打斷程序目前的流程,將執行點轉移到程序指定的處理函式。

然而從設計模式的眼光來看,Posix signal 對程序而言是 Singalton 模式,它只有一個實體。所以同一時間,每一個 Posix 信號只能註冊一個處理函式。當你的程序內有多個單元關心同一個 Posix 信號時,程序員必須要另行安排登記與分派機制,以免不同的單元彼此爭搶 Posix 信號的處理權。 GNOME 環境的程序員,可以利用 GObject 型別機制提供的 GObject signal 機制,實現 Posix signal 的分派機制。

注意,GNOME 核心的 GObject 型別機制,也提供了一套基於個體的 signal 機制。雖然都叫 signal,但 GObject signal 與 Posix signal 兩者的內容完全不同。

實作說明

由於 Posix signal 係由作業系統提供,故我在此稱為 System Signal。我先以 Singalton 設計模式撰寫一個專責接收 Posix signal 的 Handler 類別,其內有一個基於 GObject 的實體。當 Handler 接收到 Posix signal 之後,會再透過 GObject signal 機制轉發給其他人。因此外部可同樣透過 GObject signal 去傾聽 Posix signal 。由 GObject signal 機制去負責 Posix signal 的再分派工作。

using Posix;

namespace SystemSignal {
    private class Handler {
        private static Handler _handler = null;

        private Handler() {
            Posix.stdout.printf("new singleton instance\n");
            sigaction_t sig_act = {};
            sig_act.sa_handler = signum => {
                Posix.stdout.printf("handle signal (%d)\n", signum);
                _handler.delivered(signum); // emit delivered signal.
            };
            sigfillset(sig_act.sa_mask);
            sig_act.sa_flags = 0;
                
            int[] signal_set = { // See also manpage signal(7)
                SIGHUP,     SIGINT,     SIGQUIT,    SIGILL,     
                SIGABRT,    SIGFPE,     SIGSEGV,    SIGPIPE,
                SIGALRM,    SIGTERM
            };
            foreach (var i in signal_set)
                sigaction(i, sig_act, null);
        }

        public static Handler singleton {
            get {
                if (_handler == null)
                    _handler = new Handler();
                return _handler;
            }
        }

        /** 
        To prompt system signal has been delivered.

        This is a signal based on GObject type system.
         */        
        public signal void delivered(int signum);
    }

    public class SignalAgent {
        public SignalAgent() {
            var handler = Handler.singleton;
            handler.delivered.connect(forward);
        }
        
        /**
        To forward delivered signal of system signal to customer's routine.
         */
        private void forward(int signum) {
            Posix.stdout.printf("forward %d\n", signum);
            delivered(signum);
        }
        
        /** 
        To prompt system signal has been delivered.

        This is a signal based on GObject type system.
         */        
        public signal void delivered(int signum);
    }
}

SystemSignal.Handler 提供類別方法 singalton() 讓外部取得其單一實體。當此實體接受到 Posix signal 時,則會發送名為 delivered 的 GObject signal,其夾帶的參數 signum 則為此次接受到的 Posix signal 代號。

SignalAgent 則實作了一個會其傾聽 SystemSignal.Handler 所發送的信號的類別。凡是對 Posix signal 有興趣的單元,都可以自己配置一個 SignalAgent 實體,並指派各自的處理函數。彼此獨立,不會發生爭搶 Posix signal 處理權的問題。

使用範例

signal_agent_demo.vala 示範了 SignalAgent 的使用方式。

// valac --pkg posix signal_agent_demo.vala system_signal_agent.vala
using SystemSignal;

public void main() {
    var sp = new SignalAgent();
    var sp2 = new SignalAgent();

    sp.delivered.connect(signum => {
        Posix.stdout.printf("1 callback %d\n", signum);
    });

    sp2.delivered.connect(signum => { 
        Posix.stdout.printf("2 callback %d\n", signum);
    });

    Posix.stdin.getc();
}

編譯之後執行它,會停下來等待使用者按下任意鍵後結束程式。在它等待的過程中,使用者可以透過 kill 指令對它發送系統信號。或者直接按下 Ctrl+C ,送出 SIGINT 信號。

範例程式使用 lambda (此處也可稱為匿名函數) 指派 Posix signal 發生時的處理函數。兩個 Agent 彼此獨立,老死不相往來。

相關文章
樂多舊網址: http://blog.roodo.com/rocksaying/archives/15774453.html