4.3.2 wpa_supplicant_init函数分析
wpa_supplicant_init代码如下所示。
[—>wpa_supplicant.c::wpa_supplicant_init]
- struct wpa_global * wpa_supplicant_init(struct wpa_params *params)
- {
- struct wpa_global *global;
- int ret, i;
- ......
- #ifdef CONFIG_DRIVER_NDIS
- ......// windows driver支持
- #endif
- #ifndef CONFIG_NO_WPA_MSG
- // 设置全局回调函数,详情见下文解释
- wpa_msg_register_ifname_cb(wpa_supplicant_msg_ifname_cb);
- #endif /* CONFIG_NO_WPA_MSG */
- // 输出日志文件设置,本例未设置该文件
- wpa_debug_open_file(params->wpa_debug_file_path);
- ......
- ret = eap_register_methods();// ①注册EAP方法
- ......
- global = os_zalloc(sizeof(*global)); // 创建一个wpa_global对象
- ...... // 初始化global中的其他参数
- wpa_printf(MSG_DEBUG, "wpa_supplicant v" VERSION_STR);
- // ②初始化事件循环机制
- if (eloop_init()) {......}
- // 初始化随机数相关资源,用于提升后续随机数生成的随机性
- // 这部分内容不是本书的重点,感兴趣的读者请自行研究
- random_init(params->entropy_file);
- // 初始化全局控制接口对象。由于本例中未设置全局控制接口,故该函数的处理非常简单,请读者自行阅读该函数
- global->ctrl_iface = wpa_supplicant_global_ctrl_iface_init(global);
- ......
- // 初始化通知机制相关资源,它和dbus有关。本例没有包括dbus相关内容,略
- if (wpas_notify_supplicant_initialized(global)) {......}
- // ③wpa_driver是一个全局变量,其作用见下文解释
- for (i = 0; wpa_drivers[i]; i++)
- global->drv_count++;
- ......
- // 分配全局driver wrapper上下文信息数组
- global->drv_priv = os_zalloc(global->drv_count * sizeof(void *));
- ......
- return global;
- }
wpa_supplicant_init函数的主要功能是初始化wpa_global以及一些与整个程序相关的资源,包括随机数资源、eloop事件循环机制以及设置消息全局回调函数。
此处先简单介绍消息全局回调函数,一共有两个。
·wpa_msg_get_ifname_func:有些输出信息中需要打印出网卡接口名。该回调函数用于获取网卡接口名。
·wpa_msg_cb_func:除了打印输出信息外,还可通过该回调函数进行一些特殊处理,如把输出信息发送给客户端进行处理。
上述两个回调函数相关的代码如下所示。
[—>wpa_debug.c]
- // wpa_msg_ifname_cb用于获取无线网卡接口名
- // WPAS为其设置的实现函数为wpa_supplicant_msg_ifname_cb
- // 读者可自行阅读此函数
- static wpa_msg_get_ifname_func wpa_msg_ifname_cb = NULL;
- void wpa_msg_register_ifname_cb(wpa_msg_get_ifname_func func){
- wpa_msg_ifname_cb = func;
- }
- // WPAS中,wpa_msg_cb的实现函数是wpa_supplicant_ctrl_iface_msg_cb,它将输出信息发送给客户端
- // 图4-2最后两行的信息就是由此函数发送给客户端的。而且前面的"<3>"也是由它添加的
- static wpa_msg_cb_func wpa_msg_cb = NULL;
- void wpa_msg_register_cb(wpa_msg_cb_func func){
- wpa_msg_cb = func;
- }
现在来看wpa_supplicant_init中列出的三个关键点,首先是eap_register_method函数。
1.eap_register_methods函数
该函数本身非常简单,它主要根据编译时的配置项来初始化不同的eap方法。其代码如下所示。
[—>eap_register.c::eap_register_methods]
- int eap_register_methods(void)
- {
- int ret = 0;
- #ifdef EAP_MD5 // 作为supplicant端,编译时将定义EAP_MD5
- if (ret == 0)
- ret = eap_peer_md5_register();
- #endif /* EAP_MD5 */
- ......
- #ifdef EAP_SERVER_MD5 // 作为Authenticator端,编译时将定义EAP_SERVER_MD5
- if (ret == 0)
- ret = eap_server_md5_register();
- #endif /* EAP_SERVER_MD5 */
- ......
- return ret;
- }
如上述代码所示,eap_register_methods函数将根据编译配置项来注册所需的eap method。例如,MD5身份验证方法对应的注册函数是eap_peer_md5_register,该函数内部将填充一个名为eap_method的数据结构,其定义如图4-8所示。
图4-8所示的struct eap_method结构体声明于eap_i.h中,其内部一些变量及函数指针的定义和RFC4137有较大关系。此处,我们暂时列出其中一些简单的成员变量。4.4节将详细介绍RFC4137相关的知识。
来看第二个关键函数eloop_init,它和图4-1所示WPAS软件架构中的event loop模块有关。
2.eloop_init函数及event loop模块
eloop_init函数本身特别简单,它仅初始化了WPAS中事件驱动的核心数据结构体eloop_data。WPAS事件驱动机制的实现非常简单,它就是利用epoll(如果编译时设置了CONFIG_ELOOP_POLL选项)或select实现了I/O复用。
提醒 select(或epoll)是I/O复用的重要函数,属于基础知识范畴。请不熟悉的读者自行学习相关内容。
从事件角度来看,WPAS的事件驱动机制支持5种类型的event。
·read event:读事件,例如来自socket的可读事件。
·write event:写事件,例如socket的可写事件。
·exception event:异常事件,如果socket操作发生错误,则由错误事件处理。
·timeout event:定时事件,通过select的等待超时机制来实现定时事件。
·signal:信号事件,信号事件来源于Kernel。WPAS允许为一些特定信号设置处理函数。
以上这些事件相关的信息都保存在eloop_data结构体中,如图4-9所示。
图4-8 eap_method数据结构
图4-9 eloop_data结构体
简单介绍一下eloop提供的事件注册API及eloop事件循环核心处理函数eloop_run。首先是事件注册API函数,相关代码如下所示。
[—>eloop.h]
- // 注册socket读事件处理函数,参数sock代表一个socket句柄。一旦该句柄上有读事件发生,则handler函数
- // 将被事件处理循环(见下文eloop_run函数)调用
- int eloop_register_read_sock(int sock, eloop_sock_handler handler,
- void *eloop_data, void *user_data);
- // 注册socket事件处理函数,具体是哪种事件(只能是读、写或异常)由type参数决定
- int eloop_register_sock(int sock, eloop_event_type type,
- eloop_sock_handler handler,void *eloop_data, void *user_data);
- // 注册超时事件处理函数
- int eloop_register_timeout(unsigned int secs, unsigned int usecs,
- eloop_timeout_handler handler, void *eloop_data, void *user_data);
- // 注册信号事件处理函数,具体要处理的信号由sig参数指定
- int eloop_register_signal(int sig, eloop_signal_handler handler, void *user_data);
最后,向读者展示一下WPAS事件驱动机制的运行原理,其代码在eloop_run函数中,如下所示。
[—>eloop.c::eloop_run]
- void eloop_run(void)
- {
- fd_set *rfds, *wfds, *efds; // fd_set是select中用到的一种参数类型
- struct timeval _tv;
- int res;
- struct os_time tv, now;
- // 事件驱动循环
- while (!eloop.terminate &&
- (!dl_list_empty(&eloop.timeout) || eloop.readers.count > 0 ||
- eloop.writers.count > 0 || eloop.exceptions.count > 0)) {
- struct eloop_timeout *timeout;
- // 判断是否有超时事件需要等待
- timeout = dl_list_first(&eloop.timeout, struct eloop_timeout,list);
- if (timeout) {
- os_get_time(&now);
- if (os_time_before(&now, &timeout->time))
- os_time_sub(&timeout->time, &now, &tv);
- else
- tv.sec = tv.usec = 0;
- _tv.tv_sec = tv.sec;
- _tv.tv_usec = tv.usec;
- }
- // 将外界设置的读事件添加到对应的fd_set中
- eloop_sock_table_set_fds(&eloop.readers, rfds);
- ......// 设置写、异常事件到fd_set中
- // 调用select函数
- res = select(eloop.max_sock + 1, rfds, wfds, efds,timeout ? &_tv : NULL);
- if(res < 0) {......// 错误处理}
- // 先处理信号事件
- eloop_process_pending_signals();
- // 判断是否有超时事件发生
- timeout = dl_list_first(&eloop.timeout, struct eloop_timeout,list);
- if (timeout) {
- os_get_time(&now);
- if (!os_time_before(&now, &timeout->time)) {
- void *eloop_data = timeout->eloop_data;
- void *user_data = timeout->user_data;
- eloop_timeout_handler handler = timeout->handler;
- eloop_remove_timeout(timeout); // 注意,超时事件只执行一次
- handler(eloop_data, user_data); // 处理超时事件
- }
- }
- ......// 处理读/写/异常事件。方法和下面这个函数类似
- eloop_sock_table_dispatch(&eloop.readers, rfds);
- ......// 处理wfds和efds
- }
- out:
- return;
- }
eloop_run中的while循环是WPAS进程的运行中枢。不过其难度也不大。
下面来看wpa_supplicant_init代码中的第三个关键点,即wpa_drivers变量。
3.wpa_drivers数组和driver i/f模块
wpa_drivers是一个全局数组变量,它通过extern方式声明于main.c中,其定义却在drivers.c中,如下所示。
[—>drivers.c::wpa_drivers定义]
- struct wpa_driver_ops *wpa_drivers[] =
- {
- #ifdef CONFIG_DRIVER_WEXT
- &wpa_driver_wext_ops,
- #endif /* CONFIG_DRIVER_WEXT */
- #ifdef CONFIG_DRIVER_NL80211
- &wpa_driver_nl80211_ops,
- #endif /* CONFIG_DRIVER_NL80211 */
- ......// 其他driver接口
- }
wpa_drivers数组成员指向一个wpa_driver_ops类型的对象。wpa_driver_ops是driver i/f模块的核心数据结构,其内部定义了很多函数指针。而正是通过定义函数指针的方法,WPAS能够隔离上层使用者和具体的driver。
注意 此处的driver并非通常意义所指的那些运行于Kernel层的驱动。读者可认为它们是Kernel层wlan驱动在用户空间的代理模块。上层使用者通过它们来和Kernel层的驱动交互。为了避免混淆,本书后续将用driver wrapper一词来表示WPAS中的driver。而driver一词将专指Kernel里对应的wlan驱动。
另外,wpa_drivers数组包含多少个driver wrapper对象也由编译选项来控制(如代码中所示的CONFIG_DRIVER_WEXT宏,它们可在android.cfg中被修改)。
此处先列出wpa_driver_nl80211_ops的定义。
[—>driver_nl80211.c::wpa_driver_nl80211_ops]
- const struct wpa_driver_ops wpa_driver_nl80211_ops = {
- .name = "nl80211", // driver wrapper的名称
- .desc = "Linux nl80211/cfg80211", // 描述信息
- .get_bssid = wpa_driver_nl80211_get_bssid, // 用于获取bssid
- ......
- .scan2 = wpa_driver_nl80211_scan, // 扫描函数
- ......
- .get_scan_results2 = wpa_driver_nl80211_get_scan_results,
- // 获取扫描结果
- ......
- .disassociate = wpa_driver_nl80211_disassociate, // 触发disassociation操作
- .authenticate = wpa_driver_nl80211_authenticate, // 触发authentication操作
- .associate = wpa_driver_nl80211_associate, // 触发association操作
- // driver wrapper全局初始化函数,该函数的返回值保存在wpa_global成员变量drv_pri数组中
- .global_init = nl80211_global_init,
- ......
- .init2 = wpa_driver_nl80211_init, // driver wrapper初始化函数
- ......
- #ifdef ANDROID // Android平台定义了该宏
- .driver_cmd = wpa_driver_nl80211_driver_cmd,// 该函数用于处理和具体驱动相关的命令
- #endif
- };
本节介绍了main函数中第一个的关键点wpa_supplicant_init,其中涉及的知识有:几个重要数据结构,如wpa_global、wpa_interface、eap_method、wpa_driver_ops等;event loop的工作原理;消息全局回调函数和wpa_drivers等内容。
下面来分析main中第二个关键函数wpa_supplicant_add_iface。