原文連接:http://blog.csdn.net/qq_21949217/article/details/46004433網絡
在個人上一篇文章《hostapd源代碼分析(一):網絡接口和BSS的初始化》中,介紹了兩個重要的數據結構hostapd_iface和hostapd_data以及網絡接口和BSS的初始化設置的過程。下面,我要在這一篇文章中詳細介紹hostapd的工做機制。hostapd的模塊結構以下數據結構
從上圖中能夠看出,hostapd經過一個叫作「event_loop」的核心模塊來處理來自各個模塊的事件。一開始我以爲hostapd是以多線程的方式來異步處理各個事件的,但其實hostapd從頭至尾都是單一線程——是的,咱們的hostapd是移植到的MIPS的嵌入式系統上面(咱們用的是RouterStation Pro),這麼多的線程在嵌入式Linux上面是不現實的。其實,hostapd是經過socket來接受其餘模塊發來的消息的,並經過select()或者poll()系統調用來輪詢各個socket的描述符,一旦發現衆多socket描述符中有可用的描述符時,便調用相應的回調函數來處理相關事件。(關於select()或者poll()的用法,請各位讀者參考《Advanced Programming in the UNIX Environment》和《UNIX network programming》等書籍中的相關介紹,本文不作贅述)多線程
首先,咱們來看幾個關於event loop的數據結構。打開src/utils/eloop.c,部分代碼以下:異步
//eloop_sock表示一個註冊的socket struct eloop_sock { int sock; //socket的描述符 void *eloop_data; //回調函數的第一個參數 void *user_data; //回調函數的第二個參數 eloop_sock_handler handler; //當事件發生時調用的回調函數入口 .... }; //eloop_sock_table表示已經註冊的socket列表 struct eloop_sock_table { int count; //已註冊的socket個數 struct eloop_sock *table; //具體的socket註冊信息(描述符,回調函數參數,回調函數入口等) .... }; //eloop_data表示全部的socket事件 struct eloop_data { int max_sock; //全部socket描述符中的最大值 int count; /* sum of all table counts */ struct eloop_sock_table readers; //socket「讀事件」列表 struct eloop_sock_table writers; //socket「寫事件」列表 struct eloop_sock_table exceptions;//socket「意外事件」列表 .... };
再看幾個關於event loop的函數:socket
int eloop_register_sock(int sock, eloop_event_type type, eloop_sock_handler handler, void *eloop_data, void *user_data) { struct eloop_sock_table *table; assert(sock >= 0); table = eloop_get_sock_table(type); return eloop_sock_table_add_sock(table, sock, handler, eloop_data, user_data); } void eloop_unregister_sock(int sock, eloop_event_type type) { struct eloop_sock_table *table; table = eloop_get_sock_table(type); eloop_sock_table_remove_sock(table, sock); } int eloop_register_read_sock(int sock, eloop_sock_handler handler, void *eloop_data, void *user_data) { return eloop_register_sock(sock, EVENT_TYPE_READ, handler, eloop_data, user_data); } void eloop_unregister_read_sock(int sock) { eloop_unregister_sock(sock, EVENT_TYPE_READ); }
咱們先看看eloop_register_read_sock函數。很明顯,這個函數是把socket描述符和期相對應的回調函數註冊到socket描述符表中去。這個函數會調用eloop_register_sock函數,並設置EVENT_TYPE_READ標誌,表示這個socket主要用於「讀」(也即「接收」)。同理,eloop_unregister_read_sock和eloop_unregister_sock是用來把某個socket描述符從表中刪除(通常在hostapd退出的時候調用)。接下來再看eloop是如何執行的,找到eloop_run()函數:函數
1 void eloop_run(void) 2 { 3 4 fd_set *rfds, *wfds, *efds; //讀、寫、意外文件描述符集合 5 struct timeval _tv; 6 int res; 7 struct os_reltime tv, now; 8 9 rfds = os_malloc(sizeof(*rfds)); 10 wfds = os_malloc(sizeof(*wfds)); 11 efds = os_malloc(sizeof(*efds)); 12 if (rfds == NULL || wfds == NULL || efds == NULL) 13 goto out; 14 15 while (!eloop.terminate && 16 (!dl_list_empty(&eloop.timeout) || eloop.readers.count > 0 || 17 eloop.writers.count > 0 || eloop.exceptions.count > 0)) { 18 struct eloop_timeout *timeout; 19 timeout = dl_list_first(&eloop.timeout, struct eloop_timeout, 20 list); 21 if (timeout) { 22 os_get_reltime(&now); 23 if (os_reltime_before(&now, &timeout->time)) 24 os_reltime_sub(&timeout->time, &now, &tv); 25 else 26 tv.sec = tv.usec = 0; 27 28 _tv.tv_sec = tv.sec; 29 _tv.tv_usec = tv.usec; 30 } 31 //經過FD_SET宏設置「讀」、「寫」、「意外」的文件描述符集合 32 eloop_sock_table_set_fds(&eloop.readers, rfds); 33 eloop_sock_table_set_fds(&eloop.writers, wfds); 34 eloop_sock_table_set_fds(&eloop.exceptions, efds); 35 //經過select()檢查各個文件描述符的狀態 36 res = select(eloop.max_sock + 1, rfds, wfds, efds, 37 timeout ? &_tv : NULL); 38 39 if (res < 0 && errno != EINTR && errno != 0) { 40 wpa_printf(MSG_ERROR, "eloop: %s: %s", <span style="font-family: Arial, Helvetica, sans-serif;">"select"</span>, strerror(errno)); 41 goto out; 42 } 43 44 eloop_process_pending_signals(); 45 46 /* check if some registered timeouts have occurred */ 47 //檢查是否有超時發生 48 timeout = dl_list_first(&eloop.timeout, struct eloop_timeout, 49 list); 50 if (timeout) { //若是有超時發生,執行相應的回調函數處理超時事件 51 os_get_reltime(&now); 52 if (!os_reltime_before(&now, &timeout->time)) { 53 void *eloop_data = timeout->eloop_data; 54 void *user_data = timeout->user_data; 55 eloop_timeout_handler handler = 56 timeout->handler; 57 eloop_remove_timeout(timeout); 58 handler(eloop_data, user_data); 59 } 60 61 } 62 63 if (res <= 0) 64 continue; 65 66 //處理各個socket的事件 67 eloop_sock_table_dispatch(&eloop.readers, rfds); 68 eloop_sock_table_dispatch(&eloop.writers, wfds); 69 eloop_sock_table_dispatch(&eloop.exceptions, efds); 70 } 71 72 eloop.terminate = 0; 73 out: 74 os_free(rfds); 75 os_free(wfds); 76 os_free(efds); 77 return; 78 }
經過以上代碼,咱們知道了hostapd其實是經過select()機制來輪詢檢查各個socket的狀態,一旦發現某個socket描述符可讀、可寫、或者是意外的話,就會經過eloop_sock_table_dispach函數來調用相應的回調函數去處理相關事件。其實,源代碼中還有用poll()機制的實現,他們的原理都差很少,各位有興趣的讀者能夠自行查閱。對了,上面的代碼提到了eloop_sock_table_dispatch函數來處理各個socket事件,那麼它是怎麼實現的呢?咱們看一下下面的代碼:oop
static void eloop_sock_table_dispatch(struct eloop_sock_table *table, fd_set *fds) { int i; if (table == NULL || table->table == NULL) return; table->changed = 0; for (i = 0; i < table->count; i++) { //檢查socket表中每一個描述符是否可用(讀、寫、意外) if (FD_ISSET(table->table[i].sock, fds)) { //當某個socket描述符處於可用狀態時,調用相應的回調函數來處理 table->table[i].handler(table->table[i].sock, table->table[i].eloop_data, table->table[i].user_data); if (table->changed) break; } } }
怎麼樣?看到了熟悉的FD_ISSET宏了吧?spa
在eloop_run()中,不光處理了各個socket描述符的事件,還有信號(好比按下Ctrl+C),超時(timeout)等事件的處理。這裏再也不贅述。到此,讀者應該理解了hostapd的event loop工做機制了吧?瞭解了event loop的工做機制之後,咱們就能夠對hostapd的功能進行擴展了。好比我作的關於OpenFlow AP項目,我把hostapd和控制器(controller)之間交換數據的socket描述符和對應的回調函數加入到socket描述符表中去,hostapd就能夠接收來自控制器的指令,並處理OpenFlow協議了。.net