libevent原始碼學習-----統一事件源及訊號繫結函式
libevent在對檔案描述符,套接字進行監控時直接放到event,這些event通過io多路複用函式進行監控,然而對應訊號來說io複用函式卻無能為力,為了解決問題,libevent採用統一事件源的方式,即將訊號也表現成event的形式,用到了socketpair套接字對
socketpair套接字對
套接字對也是通訊方式的一種,在程序間通訊時相比於管道和命名管道而言更簡單,也更安全
linux下使用socketpair函式建立一對套接字,函式原型
/*
*@param d: 協議族,通常為AF_UNIX
*@param type: 套接字型別,如SOCK_STREAM
*@param protocol: 協議,0即可
*@param sv: 存放建立的兩個套接字
*/
int socketpair(int d, int type, int protocol, int sv[2]);
建立的套接字對的每一端都可以進行讀寫,也就是sv[0]可以既作為讀端也可以作為寫端,sv[1]也是。
libevent中sv[1]為讀端,sv[0]為寫端。向sv[0]中寫入資料時sv[1]會變為可讀
對於訊號的處理,其實使用者可以自己使用sigaction註冊訊號處理函式然後自己提供回撥函式,但是既然想要交給libevent處理,就說明使用者只提供回撥函式,其他什麼都不管。
也可以在內部只調用sigaciton/signal註冊訊號處理函式,回撥函式是使用者提供的那個,這樣也可以滿足需求,但是既然統一事件源,就需要把訊號也當成event處理。
libevent的做法是為
- 讀端註冊到base中,提供一個內部的回撥函式,該步驟僅僅呼叫一次
- 訊號值作為fd建立event,繫結內部回撥函式,註冊到base中
- 為訊號繫結一個內部的訊號處理函式,在訊號處理函式中將發生的訊號值寫入套接字對的寫端
- 此時讀端變為可讀,呼叫這個讀端套接字繫結的回撥函式
- 讀端回撥函式中讀取訊號值,將訊號值對應的event新增到啟用佇列中
- 啟用佇列處理
我覺得完全可以直接在內部使用sigaction/signal繫結訊號值和使用者的訊號處理函式
但是既然libevent想要把所有東西都當成event來處理,就需要將訊號轉成event,把所有event都放到啟用佇列中一起處理
複習一下linux下的訊號處理機制
UNIX系統訊號機制最簡單的介面是signal函式,函式原型為
#include <signal.h>
void (*signal(int signo, void (*func)(int)))(int);
- signo表示使用者關心的訊號型別,如終端訊號,退出訊號,SIGINT,SIGQUIT等
- func可以是一個函式指標,也就是訊號處理函式,當signno訊號發生時,會呼叫這個函式。該函式有一個int型別引數,通常是訊號值。也可以是常量SIG_IGN表示核心忽略該訊號(SIGKILL和SIGSTOP不能被忽略),常量SIG_DFL表示執行系統預設的處理動作,通常是終止當前程序
- 返回值是指向在此之前的訊號處理函式的指標
signal的使用還是比較簡單的
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <errno.h>
#include <sys/types.h>
void handler(int signo);
int main()
{
signal(SIGINT,handler);
for(;;)
pause();
return 0;
}
void handler(int signo)
{
printf("receive sig=%d\n", sig);
}
程式首先註冊訊號處理函式,之後當中斷髮生後切換到核心態,在核心的中斷處理程式執行完後返回使用者態之前檢測到有訊號SIGINT發生,核心遍不恢復到main函式而是直接轉到handler函式中,在執行完handler後由核心轉到main函式中繼續執行。main和handler是兩個獨立的存在
signal並不屬於POSIX標準,在UNIX中使用sigaction函式作為signal的替代品,signaction函式的功能是檢查或修改(或檢查並修改)與制定訊號相關聯的處理動作,函式原型為
#include <signal.h>
int sigaction(int signo, const struct sigaction *act,
struct sigaction *oact);
- signno是要檢測或修改其處理動作的訊號編號,如SIGINT,SIGQUIT等
- act為要修改的動作,本質上是訊號處理函式
- oact為以前的訊號處理動作,不關心時可以設定為NULL
- 呼叫成功返回0,失敗返回-1(linux的系統呼叫返回值大多是這樣)
結構體struct sigaction如下
struct sigaction
{
void (*sa_handler)(int);
sigset_t sa_mask;
int sa_flags;
void (*sa_sigaction)(int, siginfo_t *, void *);
}
- sa_handler和signal的第二個引數相同,表示訊號處理函式,SIG_IGN表示忽略,SIG_DFL表示預設動作,引數為訊號值
- sa_mask是訊號遮蔽字集,如果設定,那麼在進行訊號處理函式時,如果有在sa_mask中的訊號到達,則不會通知程序,而是在訊號處理函式執行完後才通知。預設情況下,如果程式正在執行訊號處理函式,那麼對於當前這個訊號處理函式處理的訊號不會再次送到程序。
- 核心維護著一個訊號佇列,如果當前有正在處理的訊號處理函式,會把其他到達程序的訊號放到這個佇列中。若同一種訊號多次發生,通常只會新增一次
- sa_sigaction和sa_flags配合使用,與sa_handler相對應,通常使用sa_handler就不需要使用sa_sigaction,主要用於實時訊號可以帶資訊,使用sa_handler時sa_flags設為0即可
通常使用sa_handler和sa_mask就可以滿足需求了
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <signal.h>
#include <sys/types.h>
void handler(int signo);
int main()
{
struct sigaction act;
act.sa_handler = handler;
/* 清空訊號遮蔽集 */
sigemptyset(&act.sa_mask);
/* 在訊號處理函式執行期間遮蔽SIGQUIT,執行完後送達當前程序 */
sigaddset(&act.sa_mask, SIGQUIT);
act.sa_flags = 0;
if(sigaction(SIGINT, &act, NULL) < 0)
{
perror("sigaction error");
exit(1);
}
for(;;)
pause();
return 0;
}
void handler(int signo)
{
printf("receive sig=%d\n", signo);
sleep(5);
}
先按ctrl+c傳送中斷訊號,馬上按ctrl+z傳送終止訊號,發現程式5秒後才終止,說明sa_mask中的訊號如果發生了會在當前訊號處理函式完成後才送達當前程序