zoukankan      html  css  js  c++  java
  • wpa_supplicant之eloop_run分析

    部分内容转自http://blog.chinaunix.net/uid-20273473-id-3128151.html

    重要结构体!!!

    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] = '';
        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] = '';              
            }
        }
        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)
  • 相关阅读:
    Android--MediaPlayer高级
    Android--SoundPool
    Android--MP3播放器MediaPlayer
    Android--加载大分辨率图片到内存
    Android--Task和BackStack高级
    Android--Activity的启动模式
    Android--操作图片Exif信息
    JDK5.0特性,使用ProcessBuilder执行本地命令
    MySQL 读写分离 使用驱动com.mysql.jdbc.ReplicationDriver
    kafka delete topic
  • 原文地址:https://www.cnblogs.com/chenbin7/p/3266135.html
Copyright © 2011-2022 走看看