wpa_supplicnat之eloop_run分析

重要結構體!!!

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事件:有readerswritersexceptions三個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鏈表的第一項(因爲是按超時先後排序的,所以第一項肯定是最先超時的),計算超時時間距現在還有多久,並據此設置selecttimeout參數。

2)設置rfdswfdsefds三個fd_set:方法是遍歷各個eloop_sock_table,把每個sock描述符加入相應的fd_set裏面。

3)調用select。可能阻塞在此處。

4eloop_process_pending_signals();  處理Signal事件。後面會分析。

5)判斷是否有超時發生,如果是則調用其handler,並從timeout鏈表移除。然後繼續下次循環。

6)如果不是超時事件,則應該是rfds, wfds或者efds事件,fd_set裏面會被改變,存放發生事件的描述符。因此分別遍歷三個sock_table,如果其描述符在fd_set裏面則調用其handler方法。

7)繼續下次循環。

 

值得一提的是,這裏對Signal的處理有點特別。在eloop_register_signal() 函數註冊的signalhandler並不是當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發生變化,就會調用相應sockethandler方法來處理。

 

我也只是涉及wpa_supplicantHAL等通過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文件中,分析之。

主要三個方法:recvfromwpa_supplicant_ctrl_iface_processsendto

(1)recvfrom方法從文件描述符中獲取message,保存對方的ip等信息,然後進行比較處理。

(2)wpa_supplicant_ctrl_iface_process方法也屬於比較處理的部分。在我看來只是內容比較多,就單獨寫了方法來操作了。該方法在./wpa_supplicant/ctrl_iface.c文件中。該方法處理完後,會返回reply值。

(3)sendto方法將reply發送給之前保存的對方端。

 

ctrl_iface_unix.c”實現wpa_supplicantUnix 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)首先分析driverwpa_supplicant間的通信交互

即,wfds集合中相關聯sockethandler方法是怎樣註冊進來的?

主要還是通過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)
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章