本文说明Zephyr网络管理模块Event机制的使用方法与实现原理.
在Zephyr网络管理模块分析-注册请求机制一文中已经说明了net_mgmt的作用和注册请求机制,本文继续说明Event机制的使用方法和实现原理。
使用方法
Event发送
wifi_scan被调用时,将scan_result_cb注册到驱动,当驱动scan到信号或者scan结束后会调用scan_result_cb,在scan_result_cb使用net_mgmt_event_notify_with_info通知scan的情况
1 | static void scan_result_cb(struct net_if *iface, int status, |
Event接收
Callback接收
通过注册event callback,在指定的event发生时,触发callback
1 | //指定要响应的event |
等待Event
mgmt也提供另外一种等待event的方式,示例如下:
1 | net_mgmt(NET_REQUEST_WIFI_CONNECT, iface, |
原理分析
net_mgmt.c的主要内容就是event机制,Zephyr通过CONFIG_NET_MGMT_EVENT=y
来开启event机制的支持。
在net_mgmt_event_init初始化的时,做下面三件事
- 一个callback链表,用于管理响应event的callback
- 创建一个FIFO用来缓存驱动/协议栈送上来的event
- 创建一个thread用来分发处理event
整个流程可以概括为下面几个过程:
- 应用通过net_mgmt_add_event_callback/net_mgmt_del_event_callback 添加或删除event callback
- 驱动或者协议栈通过net_mgmt_event_notify_with_info向FIFO写入event
- mgmt_thread 遍历callback,分发FIFO中的event, 调用callback。如果是event等待,通知结束等待。
Event/Request构成
在net_mgmt中Event和Request都是用32bit数来表示,如下图:
Event flag为1时表示是event,为0时是request。
Iface On为1时表示处理该event时需要对比iface是否匹配
Layer 表示这个event/request属于哪一层,有下面几个选项
1 | #define NET_MGMT_LAYER_L2 1 |
Sync event 表示有应用不使用callback而是在等待这个event
Layer Code 表示在该Layer下不同的模组功能,例如L2上有_NET_WIFI_CODE,_NET_ETHERNET_CODE等
Command 表示模组功能下不同的request和event,例如WIFI下有
1 | enum net_request_wifi_cmd { |
为了方便定义和使用event和request,net_mgmt提供了下面的宏
1 | #define NET_MGMT_EVENT_BIT BIT(31) |
定义一个event/request的示例如下
1 | #define _NET_WIFI_LAYER NET_MGMT_LAYER_L2 |
接口分析
初始化
初始化的代码分析如下
1 | void net_mgmt_event_init(void) |
event callback的处理
添加event callback, 被添加的callback会在event发生时调用
1 | void net_mgmt_add_event_callback(struct net_mgmt_event_callback *cb) |
发送event
发送event,将event加入到Fifo中
1 | void net_mgmt_event_notify_with_info(uint32_t mgmt_event, struct net_if *iface, |
关于global_event_mask的说明:
在add/del event callback时标记和清除event的操作并不是对称,原因是同一个event可能被几个callback响应,移除其中一个callback,不能直接清event mask。而要根据链表中的callback event重新生成。
1 | static inline void mgmt_add_event_mask(uint32_t event_mask) |
将注册的event标记入global_event_mask,是为了在发送event时快速检测该event是否需要处理。在发送event时先用mgmt_is_event_handled进行判断,如果符合条件才进行发送
1 | static inline bool mgmt_is_event_handled(uint32_t mgmt_event) |
当有多个不同的event callback被添加的时候其event mask都会被按位或到global_event_mask上,极端的情况下global_event_mask被能变为全1,就变为全部放行了。
这种过滤机制是一种很初步的过滤机制,在add event callback较多的情况下,过滤效果并不好。只能保证已经add的event不被漏掉,而不能保证所有没有添加的event都被过滤掉。
Event处理流程
Event FIFO
event的数据结构如下
struct mgmt_event_entry {
uint32_t event; //event
struct net_if *iface; //由那个iface产生的event
//event所带的数据信息
uint8_t info[NET_EVENT_INFO_MAX_SIZE];
size_t info_length;
};
event要带数据需要配置CONFIG_NET_MGMT_EVENT_INFO=y
,可以带数据的最大长度由NET_EVENT_INFO_MAX_SIZE指定,目前是由下面方式指定
1 | #ifdef CONFIG_NET_L2_WIFI_MGMT |
FIFO的管理是通过mgmt_push_event和mgmt_pop_event完成,这里列出主要的特征,需要了解细节可以查看代码:
- 使用events作为FIFO内存,可以缓存event的数量由CONFIG_NET_MGMT_EVENT_QUEUE_SIZE指定,默认是2,可配置范围1~1024
- 满足先进先出由全局变量out_event和in_event控制读出和写入,当FIFO满后,新的数据覆盖久的数据
- 如果发送event要携带的数据大于NET_EVENT_INFO_MAX_SIZE,event不会被放入到FIFO而直接被丢弃
- event被pop使用完后需要使用mgmt_clean_event对其内容进行清空
Callback链表
Callback链表节点数据结构如下
1 | struct net_mgmt_event_callback { |
使用单链表event_callbacks管理callback,由net_mgmt_add_event_callback和net_mgmt_del_event_callback进行callback的移除。
流程分析
Event发送流程比较简单,就是通过push到event FIFO,这里展开分析event分发处理,event的分发处理在mgmt_thread中完成
1 | static void mgmt_thread(void) |
信号量在初始化的时候最大cnt允许UINT_MAX,net_mgmt_event_notify_with_info每发一个event就发一次信号量通知thread处理event,当event的FIFO满的时候,event虽然没有被加入到FIFO内但信号量照样发,这就会出现信号量的计数大于加入到FIFO内event的情况。当FIFO内event为空后,信号量还没有恢复到0,说明曾经掉过event。
在mgmt_thread中取得event后使用mgmt_run_callbacks进行处理。
1 | static inline void mgmt_run_callbacks(struct mgmt_event_entry *mgmt_event) |
mgmt_thread的堆栈大小由CONFIG_NET_MGMT_EVENT_STACK_SIZE
配置,默认为768 Byte,如果你注册的callback函数有较多局部变量或者调用函数层次比较多,需要增加该配置项的大小。
mgmt_thread的优先级由CONFIG_NET_MGMT_EVENT_THREAD_PRIO
默认为7,当配置了CONFIG_NET_TC_THREAD_COOPERATIVE
优先级默认为-1,一般情况下不建议修改这个优先级。
对于event的匹配,只严格检查layer和layer code相等匹配,在注册callback event时候允许多个event或后注册,由于是command并不是bit差异的这里进行与检查进行与检查,实际在callback函数内再自行判断是那个event发生了,避免错误响应。
同步event流程
前面主要是分析callback机制,最后再看一下同步event流程,同步event的等待使用net_mgmt_event_wait
和net_mgmt_event_wait_on_iface
,两个函数实现如下
1 | int net_mgmt_event_wait(uint32_t mgmt_event_mask, |
都是调用的mgmt_event_wait_call,差别是在后者送了iface,意味只响应该iface发送的event,注意:在使用net_mgmt_event_wait_on_iface时需要调用者对送入的mgmt_event_mask设置NET_MGMT_IFACE_BIT。
等待event的流程如下:
1 | static int mgmt_event_wait_call(struct net_if *iface, |
对于等待event,在mgmt_run_callbacks会对其进行判断,如果匹配会发送信号量通知结束等待, 代码片段如下
1 | //检查sync event位, 后文分析sync event流程 |
从代码分析我们可以注意到放入的callback list的等待event,再响应有就从callback list中删除,也就是说无论net_mgmt_event_wait
和net_mgmt_event_wait_on_iface
的通过event_mask设置同时等待多个event时,只要有其中一个event发生,等待就结束。
另外一个问题:多次循环调用net_mgmt_event_wait
和net_mgmt_event_wait_on_iface
等待不同的event呢,例如下面代码:
1 | net_mgmt(NET_REQUEST_WIFI_SCAN, iface, NULL, 0); |
在请求scan后,根据wifi信号的数量多少会有多次NET_EVENT_WIFI_SCAN_RESULT发生和一次NET_EVENT_WIFI_SCAN_DONE发生,上面代码本意是先逐个等待NET_EVENT_WIFI_SCAN_RESULT保存scan的结果,然后在收到NET_EVENT_WIFI_SCAN_DONE退出scan。
但实际可能出现下面问题: mgmt_run_callbacks的线程优先级大于等于执行上面代码片段线程的优先级:可能会漏event或者根本收不到event,因为在NET_REQUEST_WIFI_SCAN后,可能还没执行到while(1)内就立马就来event,此时callback list内并没有要等待的event,所以直接漏掉。
一般情况下mgmt_run_callbacks的线程都是-1,而应用程序更多会被设置为抢占式线程,所以更建议使用Callback方式而不是wait event方式。
参考
https://docs.zephyrproject.org/latest/reference/networking/net_mgmt.html