天天看點

libevent源碼深度剖析八

libevent源碼深度剖析八

——內建信号處理

張亮

      現在我們已經了解了libevent的基本架構:事件管理架構和事件主循環。上節提到了libevent中I/O事件和Signal以及Timer事件的內建,這一節将分析如何将Signal內建到事件主循環的架構中。

1 內建政策——使用socket pair

      前一節已經做了足夠多的介紹了,基本方法就是采用“消息機制”。在libevent中這是通過socket pair完成的,下面就來詳細分析一下。

      Socket pair就是一個socket對,包含兩個socket,一個讀socket,一個寫socket。工作方式如下圖所示:

libevent源碼深度剖析八

    建立一個socket pair并不是複雜的操作,可以參見下面的流程圖,清晰起見,其中忽略了一些錯誤處理和檢查。                           

libevent源碼深度剖析八

Libevent提供了輔助函數evutil_socketpair()來建立一個socket pair,可以結合上面的建立流程來分析該函數。

2 內建到事件主循環——通知event_base

      Socket pair建立好了,可是libevent的事件主循環還是不知道Signal是否發生了啊,看來我們還差了最後一步,那就是:為socket pair的讀socket在libevent的event_base執行個體上注冊一個persist的讀事件。

      這樣當向寫socket寫入資料時,讀socket就會得到通知,觸發讀事件,進而event_base就能相應的得到通知了。

前面提到過,Libevent會在事件主循環中檢查标記,來确定是否有觸發的signal,如果标記被設定就處理這些signal,這段代碼在各個具體的I/O機制中,以Epoll為例,在epoll_dispatch()函數中,代碼片段如下:

[cpp]    ​​ view plain​​​    ​​ copy​​    
  
1. res = epoll_wait(epollop->epfd, events, epollop->nevents, timeout);
2. if
3. if
4. "epoll_wait");
5. return
6. }
7. // 處理signal事件
8. return
9. } else if
10. // 處理signal事件
11. }      

完整的處理架構如下所示:

libevent源碼深度剖析八

注1:libevent中,初始化階段并不注冊讀socket的讀事件,而是在注冊信号階段才會測試并注冊;

注2:libevent中,檢查I/O事件是在各系統I/O機制的dispatch()函數中完成的,該dispatch()函數在event_base_loop()函數中被調用;

3 evsignal_info結構體

      Libevent中Signal事件的管理是通過結構體evsignal_info完成的,結構體位于evsignal.h檔案中,定義如下:

[cpp]    ​​ view plain​​​    ​​ copy​​    
  
1. struct
2. struct
3. int
4. int
5. volatile sig_atomic_t
6. struct
7. sig_atomic_t
8. #ifdef HAVE_SIGACTION
9. struct
10. #else
11. ev_sighandler_t **sh_old;
12. #endif
13. int
14. };      

下面詳細介紹一下個字段的含義和作用:

1)ev_signal, 為socket pair的讀socket向event_base注冊讀事件時使用的event結構體;

2)ev_signal_pair,socket pair對,作用見第一節的介紹;

3)ev_signal_added,記錄ev_signal事件是否已經注冊了;

4)evsignal_caught,是否有信号發生的标記;是volatile類型,因為它會在另外的線程中被修改;

5)evsigvents[NSIG],數組,evsigevents[signo]表示注冊到信号signo的事件連結清單;

6)evsigcaught[NSIG],具體記錄每個信号觸發的次數,evsigcaught[signo]是記錄信号signo被觸發的次數;

7)sh_old記錄了原來的signal處理函數指針,當信号signo注冊的event被清空時,需要重新設定其處理函數;

    evsignal_info的初始化包括,建立socket pair,設定ev_signal事件(但并沒有注冊,而是等到有信号注冊時才檢查并注冊),并将所有标記置零,初始化信号的注冊事件連結清單指針等。

4 注冊、登出signal事件

      注冊signal事件是通過evsignal_add(struct event *ev)函數完成的,libevent對所有的信号注冊同一個處理函數evsignal_handler(),該函數将在下一段介紹,注冊過程如下:

1 取得ev要注冊到的信号signo;

2 如果信号signo未被注冊,那麼就為signo注冊信号處理函數evsignal_handler();

3 如果事件ev_signal還沒喲注冊,就注冊ev_signal事件;

4 将事件ev添加到signo的event連結清單中;

從signo上登出一個已注冊的signal事件就更簡單了,直接從其已注冊事件的連結清單中移除即可。如果事件連結清單已空,那麼就恢複舊的處理函數;

下面的講解都以signal()函數為例,sigaction()函數的處理和signal()相似。

處理函數evsignal_handler()函數做的事情很簡單,就是記錄信号的發生次數,并通知event_base有信号觸發,需要處理:

[cpp]    ​​ view plain​​​    ​​ copy​​    
  
1. static void evsignal_handler(int
2. {
3. int save_errno = errno; // 不覆寫原來的錯誤代碼
4. if
5. "%s: received signal %d, but have no base configured", __func__, sig);
6. return;
7. }
8. // 記錄信号sig的觸發次數,并設定event觸發标記
9. evsignal_base->sig.evsigcaught[sig]++;
10. evsignal_base->sig.evsignal_caught = 1;
11. #ifndef HAVE_SIGACTION
12. // 重新注冊信号
13. #endif
14. // 向寫socket寫一個位元組資料,觸發event_base的I/O事件,進而通知其有信号觸發,需要處理
15. "a", 1, 0);
16. // 錯誤代碼
17. }      

5 小節

繼續閱讀