重要結構體!!!
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代碼相關的
進入正題:
1.關鍵點:一個成員變量:static struct eloop_data eloop;
這個變量會處理三大類型的Event事件:Socket事件,Timeout事件,Signal事件。
socket事件還分爲三個類型:
* @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結構體分析事件的處理:
Socket事件:有readers,writers,exceptions三個eloop_sock_table結構體,每個裏面都有一個eloop_sock類型的指針table,這裏可以將該指針變量理解成動態數組。可以向各個table裏面添加、刪除eloop_sock。事件分發就是遍歷eloop_sock_table,依次運行裏面的每個handler。
Timeout事件:每個struct eloop_timeout都被放在一個雙向鏈表中, 鏈表頭就是eloop_data中的“timeout”項。這些struct eloop_timeout按超時先後排序。
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)