libevent源碼深度剖析八
——內建信号處理
張亮
現在我們已經了解了libevent的基本架構:事件管理架構和事件主循環。上節提到了libevent中I/O事件和Signal以及Timer事件的內建,這一節将分析如何将Signal內建到事件主循環的架構中。
1 內建政策——使用socket pair
前一節已經做了足夠多的介紹了,基本方法就是采用“消息機制”。在libevent中這是通過socket pair完成的,下面就來詳細分析一下。
Socket pair就是一個socket對,包含兩個socket,一個讀socket,一個寫socket。工作方式如下圖所示:
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiI0gTMx81dsQWZ4lmZf1GLlpXazVmcvwFciV2dsQXYtJ3bm9CX9s2RkBnVHFmb1clWvB3MaVnRtp1XlBXe0xCMy81dvRWYoNHLwEzX5xCMx8FesU2cfdGLwMzX0xiRGZkRGZ0Xy9GbvNGLpZTY1EmMZVDUSFTU4VFRR9Fd4VGdsYTMfVmepNHLrJXYtJXZ0F2dvwVZnFWbp1zczV2YvJHctM3cv1Ce-cGcq5iNxgTN3ITYyQjM1kDNyETYyYzX5AzNzYDM5AzLcZDMyIDMy8CXn9Gbi9CXzV2Zh1WavwVbvNmLvR3YxUjLyM3Lc9CX6MHc0RHaiojIsJye.jpg)
建立一個socket pair并不是複雜的操作,可以參見下面的流程圖,清晰起見,其中忽略了一些錯誤處理和檢查。
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. }
完整的處理架構如下所示:
注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 小節