Android wpa_supplicant源码分析--运行方式

1 wpa_supplicant的运行方式

wpa_supplicant采用单线程运行的方式,靠事件进行驱动运行,其核心模块eloop_data,其中包含几条链表,分别代表了不同的事件类型。
Wpa_s中的事件分为3类,socket,signal,timeout事件,分别挂载到eloop_data链表中,其中socket又详细的分为read,write,exception 3种sock table 表。每种事件都有事先分配好的handler函数,当事件触发时进行相应的处理。
eloop是一个全局的结构体变量,保存了监听的socket和相应的handler

struct eloop_data
{
    int max_sock;
    int count; /* sum of all table counts */
    //用于读写的socket的信息
    struct eloop_sock_table  readers;
    struct eloop_sock_table writers;
    struct eloop_sock_table exceptions;
    //存储超时执行任务,相应的结构体
    struct dl_list timeout;

    //linux中信号的监听
    int signal_count;
    struct eloop_signal *signals;
    int signaled;
    int pending_terminate;
    //为1表示eloop正在运行,为0表示eloop需要停止
    int terminate;
}

网上扣个图http://www.cnblogs.com/chenbin7/p/3266135.html,描述了eloop_data的组成方式。

这里写图片描述

1.1 socket事件

在之前的接口初始化过程中wap_s创建了对framework和driver分别创建了的socket,wpa_s在运行时会采用轮询的所有的socket,当其中有数据时,会读取数据并使用事先分配给该socket的数据处理函数进行相应的处理。处理完本次socket的数据后再查询下一条socket。
向eloop_data中注册一个socket采用如下函数
static int eloop_sock_table_add_sock(struct eloop_sock_table *table, int sock, eloop_sock_handler handler, void *eloop_data, void *user_data)
table指定的3个sock table中的一个
handler的类型为
typedef void (*eloop_sock_handler)(int sock, void *eloop_ctx, void *sock_ctx);
后面的两个参数 eloop_ctx和sock_ctx传入handler中,然后做相应的类型转换,供处理socket中数据时使用。
虽说有3种sock table,但目前wpa_s中使用的只有read sock_table。

1.2 signal事件

注册single 处理
int eloop_register_signal(int sig, eloop_signal_handler handler, void *user_data)

1.3 timeout事件

注册超时任务,加入eloop_data中的time_out链表中
int eloop_register_timeout(unsigned int secs, unsigned int usecs, eloop_timeout_handler handler, void *eloop_data, void *user_data)
超时事件顾名思义就是岩石多少秒后再执行handler函数,由于wpa_s单线程轮询处理的机制,handler的执行可能会稍许的延后。

1.4 radio work

在wpa_s中还包含着另一个重要的链表wpa_s->radio,该链表中包含着wpa_s指定驱动进行的一系列射频操作,例如scan,associate,authentication等,由于驱动的射频类操作能一项一项的执行,且耗时很长,所以只能先将待操作的事件存储到链表中,等待驱动的顺序执行。执行完当前的radio_work后,将下一个radio work添加到timeout 链表中。

添加一个射频操作:

int radio_add_work(struct wpa_supplicant *wpa_s, unsigned int freq,
           const char *type, int next,
           void (*cb)(struct wpa_radio_work *work, int deinit),
           void *ctx)

在radio_work 的cb()执行到最后需要调用_work_done 处理下一个radio work

void radio_work_done(struct wpa_radio_work *work)
{
    ...
    radio_work_check_next(wpa_s);
    {
        eloop_cancel_timeout(radio_start_next_work, radio, NULL);
        eloop_register_timeout(0, 0, radio_start_next_work, radio, NULL);
    {
}

2 eloop run

Wpa_s运行时,通过 event loop中while循环,一遍遍监听socket(FKWS向下传递的消息的socket和接收driver上报事件的event)和timeout链表,如果socket中有需要处理的数据,在调用相应的函数
这里写图片描述

eloop运行时,在while循环中,通过select方法坚挺socket的数据变化

void eloop_run(void)
{
    //select方法所需要的三种读set、写set、异常set。还有等待超时
    fd_set *rfds, *wfds, *efds;
    struct timeval _tv;
    //select 所需的fd_set数据初始化
    rfds = os_malloc(sizeof(*rfds));

    while (!eloop.terminate &&  (!dl_list_empty(&eloop.timeout) || eloop.readers.count > 0 || eloop.writers.count > 0 || eloop.exceptions.count > 0)) {

        //获取timeout链表头的,检查应该被执性的时间
        //如果还没到,计算出超时函数,写入到select函数等待时间中,
        //如果已经到了,那么select不在延时阻塞,保证能够立即执性timeout中handler
        struct eloop_timeout *timeout = dl_list_first(&eloop.timeout, struct eloop_timeout, list);
        if (timeout) {
            if (os_reltime_before(&now, &timeout->time))
                os_reltime_sub(&timeout->time, &now, &tv);
            else
                tv.sec = tv.usec = 0;
            _tv = tv;
        }

        //将相应的sock注册到 rfds, wfds, efds
        eloop_sock_table_set_fds(&eloop.readers, rfds);
        //调用slelct方法,监听fd_set中注册的sock信息,android wpa_s中好像只用了read的,如果为了提高效率,可以去掉不用的socket
        res = select(eloop.max_sock + 1, rfds, wfds, efds, timeout ? &_tv : NULL);


        //处理注册的signal处理函数,检查是够需要处理相应的handler
        eloop_process_pending_signals();

        //检查是否到执行timeout函数的时间
        timeout = dl_list_first(&eloop.timeout, struct eloop_timeout, list);
        if (timeout) {
            if (!os_reltime_before(&now, &timeout->time)) {
                //执行
                handler(eloop_data, user_data);
            }
        }

        //根据select函数返回的fd_set,检查哪个sock有数据,然后调用相关的sock handler
        eloop_sock_table_dispatch(&eloop.readers, rfds);
    }
}

代码流程图
这里写图片描述

附:select方法说明

select函数原型
int select(int maxfdp,fd_set *readfds,fd_set *writefds,fd_set *errorfds,struct timeval *timeout); 
参数说明:
struct fd_set中存放文件描述符(file descriptor)包括socket的值。
    fd_set有一些宏可以操作清空集合FD_ZERO(fd_set *),
    将一个给定的文件描述符加入集合之中FD_SET(int ,fd_set *),
    将一个给定的文件描述符从集合中删除FD_CLR(int ,fd_set*),
    检查集合中指定的文件描述符是否可以读写FD_ISSET(int ,fd_set* )。

readfds 监听读变化, writefds 监听写变化, errorfds 监听错误

struct timeval* timeout是select的超时时间,这个参数至关重要,它可以使select处于三种状态,
    第一,若将NULL以形参传入,就是将select置于阻塞状态,一定等到监视文件描述符集合中某个文件描述符发生变化为止;
    第二,若将时间值设为00毫秒,就变成一个纯粹的非阻塞函数,不管文件描述符是否有变化,都立刻返回继续执行;
    第三,timeout的值大于0,这就是等待的超时时间,即select在timeout时间内阻塞,若超时时间之内有事件到来直接返回,若没有在超时后直接返回。

返回值:
负值:select执行错误。
0:监听的文件描述符无变化
正值:有变化的文件描述符的数目
  • 0
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值