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置于阻塞状态,一定等到监视文件描述符集合中某个文件描述符发生变化为止;
第二,若将时间值设为0秒0毫秒,就变成一个纯粹的非阻塞函数,不管文件描述符是否有变化,都立刻返回继续执行;
第三,timeout的值大于0,这就是等待的超时时间,即select在timeout时间内阻塞,若超时时间之内有事件到来直接返回,若没有在超时后直接返回。
返回值:
负值:select执行错误。
0:监听的文件描述符无变化
正值:有变化的文件描述符的数目