部份內容轉自http://blog.chinaunix.net/uid-20273473-id-3128151.htmlhtml
重要結構體!!!數組
struct eloop_sock { int sock; void *eloop_data; void *user_data; eloop_sock_handler handler; //該handler是一個方法,後續socket有變化,就會調用相應的socket所在的結構體中的handler方法來處理 WPA_TRACE_REF(eloop); WPA_TRACE_REF(user); WPA_TRACE_INFO }; struct eloop_timeout { struct dl_list list; //雙向鏈表 struct os_time time; void *eloop_data; void *user_data; eloop_timeout_handler handler; WPA_TRACE_REF(eloop); WPA_TRACE_REF(user); WPA_TRACE_INFO }; struct eloop_signal { int sig; void *user_data; eloop_signal_handler handler; int signaled; }; struct eloop_sock_table { int count; struct eloop_sock *table; //eloop_sock類型的變量 int changed; }; struct eloop_data { int max_sock; int count; /* sum of all table counts */ #ifdef CONFIG_ELOOP_POLL int max_pollfd_map; /* number of pollfds_map currently allocated */ int max_poll_fds; /* number of pollfds currently allocated */ struct pollfd *pollfds; struct pollfd **pollfds_map; #endif /* CONFIG_ELOOP_POLL */ struct eloop_sock_table readers; struct eloop_sock_table writers; struct eloop_sock_table exceptions; struct dl_list timeout; int signal_count; struct eloop_signal *signals; int signaled; int pending_terminate; int terminate; int reader_table_changed; }; //建立了一個靜態全局變量,類型爲eloop_data static struct eloop_data eloop;
ps:這裏貼出結構體關係圖,應該是wpa_supplicant_6代碼相關的dom
進入正題:socket
1.關鍵點:一個成員變量:static struct eloop_data eloop;函數
這個變量會處理三大類型的Event事件:Socket事件,Timeout事件,Signal事件。oop
socket事件還分爲三個類型:ui
* @EVENT_TYPE_READ: Socket has data available for reading * @EVENT_TYPE_WRITE: Socket has room for new data to be written * @EVENT_TYPE_EXCEPTION: An exception has been reported
根據eloop_data結構體分析事件的處理:spa
Socket事件:有readers,writers,exceptions三個eloop_sock_table結構體,每一個裏面都有一個eloop_sock類型的指針table,這裏能夠將該指針變量理解成動態數組。能夠向各個table裏面添加、刪除eloop_sock。事件分發就是遍歷eloop_sock_table,依次運行裏面的每一個handler。.net
Timeout事件:每一個struct eloop_timeout都被放在一個雙向鏈表中, 鏈表頭就是eloop_data中的「timeout」項。這些struct eloop_timeout按超時前後排序。unix
Signal事件:每一個struct eloop_signal都經過eloop_signal類型的指針連接起來。
2.基於select方法實現多事件監聽
eloop_run方法中的「死循環」:
while (!eloop.terminate && (!dl_list_empty(&eloop.timeout) || eloop.readers.count > 0 ||eloop.writers.count > 0 || eloop.exceptions.count > 0)) {
若是eloop.terminate變爲非零值,就會退出循環。這是爲了提供一種從外部結束循環的方法。
若是eloop.terminate爲零,只要timeout鏈表或者任一個Socket不爲空,都會繼續循環。
select系統調用的原型是:
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
在循環體裏面:
(1)找到timeout鏈表的第一項(由於是按超時前後排序的,因此第一項確定是最早超時的),計算超時時間距如今還有多久,並據此設置select的timeout參數。
(2)設置rfds,wfds和efds三個fd_set:方法是遍歷各個eloop_sock_table,把每一個sock描述符加入相應的fd_set裏面。
(3)調用select。可能阻塞在此處。
(4)eloop_process_pending_signals(); 處理Signal事件。後面會分析。
(5)判斷是否有超時發生,若是是則調用其handler,並從timeout鏈表移除。而後繼續下次循環。
(6)若是不是超時事件,則應該是rfds, wfds或者efds事件,fd_set裏面會被改變,存放發生事件的描述符。所以分別遍歷三個sock_table,若是其描述符在fd_set裏面則調用其handler方法。
(7)繼續下次循環。
值得一提的是,這裏對Signal的處理有點特別。在eloop_register_signal() 函數註冊的signal的handler並非當Signal發生時就會自動執行的。當Signal發生時只會對該struct eloop_signal的 signaled變量加1,以代表Signal已收到並處於Pending狀態。在select()超時或者有Socket事件方式時纔會順便調用eloop_process_pending_signals(), 對每一個處於Pending狀態的struct eloop_signal調用其handler。
-----------------------------------------------------------------------------------------------------------------
下面分析handler方法的由來。
1.監聽rfds集合中socket的變化。若發生變化,就會調用eloop.readers->table[i]->handler方法來處理。該handler方法的來源須要分析下。
XX_init -->eloop_register_read_sock -->eloop_register_sock -->eloop_sock_table_add_sock -->...... -->eloop_run的while循環中等待上層發送命令過來
註冊過程:在一些初始化的方法(XX_init)中,會調用eloop_register_read_sock方法,如wpa_supplicant_ctrl_iface_init方法。而後將socket以及做爲handler的方法名以參數形式傳遞過去(即在這裏完成了回調方法的註冊)。
回調過程:eloop_run方法經過select方法在循環監聽rfds的變化,一旦rfds發生變化,就會調用相應socket的handler方法來處理。
我也只是涉及wpa_supplicant與HAL等經過socket發送/接收消息的流程,因此這裏只分析這個通訊的流程。這一個知識點主要是監聽rfds集合中socket的變化,便是否有可讀消息,因此其實說白了就是監聽上層發送給wpa_supplicant的命令。
在wpa_supplicant_ctrl_iface_init方法[ctrl_iface_unix.c]中,有以下語句:
eloop_register_read_sock(priv->sock,wpa_supplicant_ctrl_iface_receive, wpa_s, priv);
因此這裏註冊的handler方法就是wpa_supplicant_ctrl_iface_receive方法。該方法主要就是接收ctrl_conn發送過來的命令,處理後反饋reponse命令。
該方法在./wpa_supplicant/ctrl_iface_unix.c文件中,分析之。
主要三個方法:recvfrom,wpa_supplicant_ctrl_iface_process,sendto
(1)recvfrom方法從文件描述符中獲取message,保存對方的ip等信息,而後進行比較處理。
(2)wpa_supplicant_ctrl_iface_process方法也屬於比較處理的部分。在我看來只是內容比較多,就單獨寫了方法來操做了。該方法在./wpa_supplicant/ctrl_iface.c文件中。該方法處理完後,會返回reply值。
(3)sendto方法將reply發送給以前保存的對方端。
「ctrl_iface_unix.c」實現wpa_supplicant的Unix domain socket通訊機制中server結點,完成對client結點的響應。
其中最主要的兩個函數爲:
/* 接收並解析client發送request命令,而後根據不一樣的命令調用底層不一樣的處理函數; * 而後將得到response結果回饋到 client 結點。 */ static void wpa_supplicant_ctrl_iface_receive(int sock, void *eloop_ctx, void *sock_ctx) /* 向註冊的monitor interfaces 主動發送event事件,該方法在下一個知識點進行分析 */ static void wpa_supplicant_ctrl_iface_send(struct ctrl_iface_priv *priv, int level, const char *buf, size_t len)
-----------------------------------------------------------------------------------------------------------------
2.監聽wfds集合中socket的變化。
由於我分析的主要是wpa_supplicant與上層或driver層經過socket通訊的流程。因此這裏就把wfds理解成是監聽driver發送過來的消息。監聽到socket有變化,eloop也會調用其結構體中相應的table中的handler方法處理driver發過來的消息,而後再發送消息給上層應用。
(1)首先分析driver和wpa_supplicant間的通訊交互
即,wfds集合中相關聯socket的handler方法是怎樣註冊進來的?
主要仍是經過socket來處理,能夠參考wpa_supplicant下行接口淺析 這篇的分析。
(2)wpa_supplicant和上層(HAL等)間的交互
簡單來講,就是經過wpa_msg方法調用回調方法,將wpa_supplicant的事件發送給monitor interfaces。
下面分析調用wpa_msg方法會執行的流程。
注意如下三個文件,
./wpa_supplicant/ctrl_iface_unix.c // TCP/IP ./wpa_supplicant/ctrl_iface_udp.c // UDP ./wpa_supplicant/ctrl_iface_named_pipe.c // PIPE
這三個文件中流程都是同樣的,一樣,因目前所學有限,只分析ctrl_iface_unix.c相關的流程。
(2.1)註冊回調方法
首先在wpa_supplicant_ctrl_iface_init方法中,會註冊一個回調方法,
wpa_supplicant_ctrl_iface_init---->wpa_msg_register_cb(wpa_supplicant_ctrl_iface_msg_cb); //wpa_msg_register_cb源碼 void wpa_msg_register_cb(wpa_msg_cb_func func) { wpa_msg_cb = func; //將回調方法保存下來 }
1. wpa_msg_register_cb方法做用:register callback function for wpa_msg() messages.
2. 這裏wpa_supplicant_ctrl_iface_msg_cb方法就是callback方法。
(2.2)wpa_msg方法
//wpa_msg方法源碼 void wpa_msg(void *ctx, int level, const char *fmt, ...) { va_list ap; char *buf; const int buflen = 2048; int len; char prefix[130]; buf = os_malloc(buflen); if (buf == NULL) { wpa_printf(MSG_ERROR, "wpa_msg: Failed to allocate message " "buffer"); return; } va_start(ap, fmt); prefix[0] = '\0'; if (wpa_msg_ifname_cb) { const char *ifname = wpa_msg_ifname_cb(ctx); if (ifname) { int res = os_snprintf(prefix, sizeof(prefix), "%s: ", ifname); if (res < 0 || res >= (int) sizeof(prefix)) prefix[0] = '\0'; } } len = vsnprintf(buf, buflen, fmt, ap); va_end(ap); wpa_printf(level, "%s%s", prefix, buf); if (wpa_msg_cb) wpa_msg_cb(ctx, level, buf, len); //這裏就是回調方法起做用的地方了 os_free(buf); }
wpa_msg方法:相似於wpa_printf,但同時它也會把消息發送給全部已經attach到wpa_supplicant的monitors。
所以,一旦調用wpa_msg方法,就會回調wpa_supplicant_ctrl_iface_msg_cb方法。
--->wpa_msg --->wpa_supplicant_ctrl_iface_msg_cb(即wpa_msg_cb被賦予的方法) --->wpa_supplicant_ctrl_iface_send --->sendmsg(ctrl_iface_unix.c中是sendmsg方法)發送到monitors(固然包括monitor_conn)