本文说明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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34static void scan_result_cb(struct net_if *iface, int status,
struct wifi_scan_result *entry)
{
if (!iface) {
return;
}
if (!entry) {
struct wifi_status scan_status = {
.status = status,
};
//通知wifi scan完成
net_mgmt_event_notify_with_info(NET_EVENT_WIFI_SCAN_DONE,
iface, &scan_status,
sizeof(struct wifi_status));
return;
}
//每scan到一个wifi信号,通知一次scan的结果
net_mgmt_event_notify_with_info(NET_EVENT_WIFI_SCAN_RESULT, iface,
entry, sizeof(struct wifi_scan_result));
}
static int wifi_scan(uint32_t mgmt_request, struct net_if *iface,
void *data, size_t len)
{
const struct device *dev = net_if_get_device(iface);
struct net_wifi_mgmt_offload *off_api =
(struct net_wifi_mgmt_offload *) dev->api;
if (off_api == NULL || off_api->scan == NULL) {
return -ENOTSUP;
}
//将callback注册到底层
return off_api->scan(dev, scan_result_cb);
}
Event接收
Callback接收
通过注册event callback,在指定的event发生时,触发callback1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29//指定要响应的event
#define WIFI_SHELL_MGMT_EVENTS (NET_EVENT_WIFI_SCAN_RESULT | \
NET_EVENT_WIFI_SCAN_DONE)
//处理event的callback函数
static void wifi_mgmt_event_handler(struct net_mgmt_event_callback *cb,
uint32_t mgmt_event, struct net_if *iface)
{
switch (mgmt_event) {
case NET_EVENT_WIFI_SCAN_RESULT:
handle_wifi_scan_result(cb);
break;
case NET_EVENT_WIFI_SCAN_DONE:
handle_wifi_scan_done(cb);
break;
}
}
static int wifi_shell_init(const struct device *unused)
{
//使用callback和指定响应的event进行初始化callback event
net_mgmt_init_event_callback(&wifi_shell_mgmt_cb,
wifi_mgmt_event_handler,
WIFI_SHELL_MGMT_EVENTS);
//将callback注册到mgmt
net_mgmt_add_event_callback(&wifi_shell_mgmt_cb);
return 0;
}
等待Event
mgmt也提供另外一种等待event的方式,示例如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15net_mgmt(NET_REQUEST_WIFI_CONNECT, iface,
&cnx_params, sizeof(struct wifi_connect_req_params));
ret = net_mgmt_event_wait(NET_EVENT_WIFI_CONNECT_RESULT,
&raised_event,
iface,
&info,
&info_length,
K_FOREVER);
if(ret == 0 && (raised_event & NET_EVENT_WIFI_CONNECT_RESULT == NET_EVENT_WIFI_CONNECT_RESULT)){
if(status.status == 0){
//connect successed
}
}
原理分析
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
2
3#define NET_MGMT_LAYER_L2 1
#define NET_MGMT_LAYER_L3 2
#define NET_MGMT_LAYER_L4 3
Sync event 表示有应用不使用callback而是在等待这个event
Layer Code 表示在该Layer下不同的模组功能,例如L2上有_NET_WIFI_CODE,_NET_ETHERNET_CODE等
Command 表示模组功能下不同的request和event,例如WIFI下有1
2
3
4
5
6
7
8
9
10
11
12
13
14enum net_request_wifi_cmd {
NET_REQUEST_WIFI_CMD_SCAN = 1,
NET_REQUEST_WIFI_CMD_CONNECT,
NET_REQUEST_WIFI_CMD_DISCONNECT,
NET_REQUEST_WIFI_CMD_AP_ENABLE,
NET_REQUEST_WIFI_CMD_AP_DISABLE,
};
enum net_event_wifi_cmd {
NET_EVENT_WIFI_CMD_SCAN_RESULT = 1,
NET_EVENT_WIFI_CMD_SCAN_DONE,
NET_EVENT_WIFI_CMD_CONNECT_RESULT,
NET_EVENT_WIFI_CMD_DISCONNECT_RESULT,
};
为了方便定义和使用event和request,net_mgmt提供了下面的宏1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24#define NET_MGMT_EVENT_BIT BIT(31)
#define NET_MGMT_IFACE_BIT BIT(30)
#define NET_MGMT_SYNC_EVENT_BIT BIT(27)
#define NET_MGMT_LAYER(_layer) (_layer << 28)
#define NET_MGMT_LAYER_CODE(_code) (_code << 16)
#define NET_MGMT_EVENT(mgmt_request) \
(mgmt_request & NET_MGMT_EVENT_MASK)
#define NET_MGMT_ON_IFACE(mgmt_request) \
(mgmt_request & NET_MGMT_ON_IFACE_MASK)
#define NET_MGMT_EVENT_SYNCHRONOUS(mgmt_request) \
(mgmt_request & NET_MGMT_SYNC_EVENT_MASK)
#define NET_MGMT_GET_LAYER(mgmt_request) \
((mgmt_request & NET_MGMT_LAYER_MASK) >> 28)
#define NET_MGMT_GET_LAYER_CODE(mgmt_request) \
((mgmt_request & NET_MGMT_LAYER_CODE_MASK) >> 16)
#define NET_MGMT_GET_COMMAND(mgmt_request) \
(mgmt_request & NET_MGMT_COMMAND_MASK)
定义一个event/request的示例如下1
2
3
4
5
6
7
8
9
10
11
12#define _NET_WIFI_LAYER NET_MGMT_LAYER_L2
#define _NET_WIFI_CODE 0x156
#define _NET_WIFI_BASE (NET_MGMT_IFACE_BIT | \
NET_MGMT_LAYER(_NET_WIFI_LAYER) | \
NET_MGMT_LAYER_CODE(_NET_WIFI_CODE))
#define _NET_WIFI_EVENT (_NET_WIFI_BASE | NET_MGMT_EVENT_BIT)
#define NET_REQUEST_WIFI_SCAN \
(_NET_WIFI_BASE | NET_REQUEST_WIFI_CMD_SCAN)
#define NET_EVENT_WIFI_SCAN_RESULT \
(_NET_WIFI_EVENT | NET_EVENT_WIFI_CMD_SCAN_RESULT)
接口分析
初始化
初始化的代码分析如下1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21void net_mgmt_event_init(void)
{
//创建callback管理的链表
sys_slist_init(&event_callbacks);
global_event_mask = 0U;
in_event = -1;
out_event = -1;
//创建event FIFO
(void)memset(events, 0, CONFIG_NET_MGMT_EVENT_QUEUE_SIZE *
sizeof(struct mgmt_event_entry));
//创建callback处理thread
k_thread_create(&mgmt_thread_data, mgmt_stack,
K_KERNEL_STACK_SIZEOF(mgmt_stack),
(k_thread_entry_t)mgmt_thread, NULL, NULL, NULL,
CONFIG_NET_MGMT_EVENT_THREAD_PRIO, 0, K_NO_WAIT);
k_thread_name_set(&mgmt_thread_data, "net_mgmt");
}
event callback的处理
添加event callback, 被添加的callback会在event发生时调用1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29void net_mgmt_add_event_callback(struct net_mgmt_event_callback *cb)
{
NET_DBG("Adding event callback %p", cb);
k_sem_take(&net_mgmt_lock, K_FOREVER);
//将callback加入到链表event_callbacks中
sys_slist_prepend(&event_callbacks, &cb->node);
//标记要响应的event
mgmt_add_event_mask(cb->event_mask);
k_sem_give(&net_mgmt_lock);
}
删除event callback
void net_mgmt_del_event_callback(struct net_mgmt_event_callback *cb)
{
NET_DBG("Deleting event callback %p", cb);
k_sem_take(&net_mgmt_lock, K_FOREVER);
//将callback从链表event_callbacks中删除
sys_slist_find_and_remove(&event_callbacks, &cb->node);
//清除callback的event mask
mgmt_rebuild_global_event_mask();
k_sem_give(&net_mgmt_lock);
}
发送event
发送event,将event加入到Fifo中1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16void net_mgmt_event_notify_with_info(uint32_t mgmt_event, struct net_if *iface,
const void *info, size_t length)
{
//只发送已经注册过的event
if (mgmt_is_event_handled(mgmt_event)) {
NET_DBG("Notifying Event layer %u code %u type %u",
NET_MGMT_GET_LAYER(mgmt_event),
NET_MGMT_GET_LAYER_CODE(mgmt_event),
NET_MGMT_GET_COMMAND(mgmt_event));
//将event push到fifo
mgmt_push_event(mgmt_event, iface, info, length);
//通知thread处理event
k_sem_give(&network_event);
}
}
关于global_event_mask的说明:
在add/del event callback时标记和清除event的操作并不是对称,原因是同一个event可能被几个callback响应,移除其中一个callback,不能直接清event mask。而要根据链表中的callback event重新生成。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15static inline void mgmt_add_event_mask(uint32_t event_mask)
{
global_event_mask |= event_mask;
}
static inline void mgmt_rebuild_global_event_mask(void)
{
struct net_mgmt_event_callback *cb, *tmp;
global_event_mask = 0U;
//重新遍历callback,进行event mask标记
SYS_SLIST_FOR_EACH_CONTAINER_SAFE(&event_callbacks, cb, tmp, node) {
mgmt_add_event_mask(cb->event_mask);
}
}
将注册的event标记入global_event_mask,是为了在发送event时快速检测该event是否需要处理。在发送event时先用mgmt_is_event_handled进行判断,如果符合条件才进行发送1
2
3
4
5
6
7
8
9
10
11
12static inline bool mgmt_is_event_handled(uint32_t mgmt_event)
{
return (((NET_MGMT_GET_LAYER(mgmt_event) &
NET_MGMT_GET_LAYER(global_event_mask)) ==
NET_MGMT_GET_LAYER(mgmt_event)) &&
((NET_MGMT_GET_LAYER_CODE(mgmt_event) &
NET_MGMT_GET_LAYER_CODE(global_event_mask)) ==
NET_MGMT_GET_LAYER_CODE(mgmt_event)) &&
((NET_MGMT_GET_COMMAND(mgmt_event) &
NET_MGMT_GET_COMMAND(global_event_mask)) ==
NET_MGMT_GET_COMMAND(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
2
3
4
5
6
7
8
9
10
11
12
13
14#ifdef CONFIG_NET_L2_WIFI_MGMT
#include <net/wifi_mgmt.h>
#define NET_EVENT_INFO_MAX_SIZE sizeof(struct wifi_scan_result)
#else
#if defined(CONFIG_NET_DHCPV4)
#define NET_EVENT_INFO_MAX_SIZE sizeof(struct net_if_dhcpv4)
#else
#define NET_EVENT_INFO_MAX_SIZE sizeof(struct net_event_ipv6_route)
#endif
#endif /* 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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22struct net_mgmt_event_callback {
sys_snode_t node; //链表节点
//callback处理函数和sync event信号量共用
//在callback时handler保存callback函数
//在wait event时, 使用sync_call通知收到event
union {
net_mgmt_event_handler_t handler;
struct k_sem *sync_call;
};
//用于传递event的数据
const void *info;
size_t info_length;
//event_mask和sync event的raised event共用
//只有在sync event的情况下,使用该字段反馈收到的event
union {
uint32_t event_mask;
uint32_t raised_event;
};
};
使用单链表event_callbacks管理callback,由net_mgmt_add_event_callback和net_mgmt_del_event_callback进行callback的移除。
流程分析
Event发送流程比较简单,就是通过push到event FIFO,这里展开分析event分发处理,event的分发处理在mgmt_thread中完成1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35static void mgmt_thread(void)
{
struct mgmt_event_entry *mgmt_event;
while (1) {
//等待event信号,当发送event时会发送信号network_event
k_sem_take(&network_event, K_FOREVER);
k_sem_take(&net_mgmt_lock, K_FOREVER);
NET_DBG("Handling events, forwarding it relevantly");
//从FIFO中取出event
mgmt_event = mgmt_pop_event();
if (!mgmt_event) {
//如果event为0,表示没有取到event,但此时有network_event通知,说明是之前FIFO满了
NET_DBG("Some event got probably lost (%u)",
k_sem_count_get(&network_event));
//FIFO中已经没有可用event,因此同步复位network_event
k_sem_init(&network_event, 0, UINT_MAX);
k_sem_give(&net_mgmt_lock);
continue;
}
//分发处理event
mgmt_run_callbacks(mgmt_event);
//清空event
mgmt_clean_event(mgmt_event);
k_sem_give(&net_mgmt_lock);
k_yield();
}
}
信号量在初始化的时候最大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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26int net_mgmt_event_wait(uint32_t mgmt_event_mask,
uint32_t *raised_event,
struct net_if **iface,
const void **info,
size_t *info_length,
k_timeout_t timeout)
{
return mgmt_event_wait_call(NULL, mgmt_event_mask,
raised_event, iface, info, info_length,
timeout);
}
int net_mgmt_event_wait_on_iface(struct net_if *iface,
uint32_t mgmt_event_mask,
uint32_t *raised_event,
const void **info,
size_t *info_length,
k_timeout_t timeout)
{
NET_ASSERT(NET_MGMT_ON_IFACE(mgmt_event_mask));
NET_ASSERT(iface);
return mgmt_event_wait_call(iface, mgmt_event_mask,
raised_event, NULL, info, info_length,
timeout);
}
都是调用的mgmt_event_wait_call,差别是在后者送了iface,意味只响应该iface发送的event,注意:在使用net_mgmt_event_wait_on_iface时需要调用者对送入的mgmt_event_mask设置NET_MGMT_IFACE_BIT。
等待event的流程如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56static int mgmt_event_wait_call(struct net_if *iface,
uint32_t mgmt_event_mask,
uint32_t *raised_event,
struct net_if **event_iface,
const void **info,
size_t *info_length,
k_timeout_t timeout)
{
//初始化等待通知信号量
struct mgmt_event_wait sync_data = {
.sync_call = Z_SEM_INITIALIZER(sync_data.sync_call, 0, 1),
};
//借用event callback做等待event处理,将等待通知信号量和event记录在内
struct net_mgmt_event_callback sync = {
.sync_call = &sync_data.sync_call,
.event_mask = mgmt_event_mask | NET_MGMT_SYNC_EVENT_BIT,
};
int ret;
//如果要匹配iface,设置iface
if (iface) {
sync_data.iface = iface;
}
NET_DBG("Synchronous event 0x%08x wait %p", sync.event_mask, &sync);
//将要等待的event和通知信号量加入到callback链表中
net_mgmt_add_event_callback(&sync);
//等待event发生通知
ret = k_sem_take(sync.sync_call, timeout);
if (ret == -EAGAIN) {
ret = -ETIMEDOUT;
} else {
if (!ret) {
//将发生的event送出
if (raised_event) {
*raised_event = sync.raised_event;
}
//将发生的event的iface送出
if (event_iface) {
*event_iface = sync_data.iface;
}
//将event的info送出
if (info) {
*info = sync.info;
if (info_length) {
*info_length = sync.info_length;
}
}
}
}
return ret;
}
对于等待event,在mgmt_run_callbacks会对其进行判断,如果匹配会发送信号量通知结束等待, 代码片段如下1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20//检查sync event位, 后文分析sync event流程
if (NET_MGMT_EVENT_SYNCHRONOUS(cb->event_mask)) {
struct mgmt_event_wait *sync_data =
CONTAINER_OF(cb->sync_call,
struct mgmt_event_wait, sync_call);
//如果有iface,则匹配iface
if (sync_data->iface &&
sync_data->iface != mgmt_event->iface) {
continue;
}
NET_DBG("Unlocking %p synchronous call", cb);
//将发生的event和iface都送到cb内
cb->raised_event = mgmt_event->event;
sync_data->iface = mgmt_event->iface;
//将等待event callback从callback中移除
sys_slist_remove(&event_callbacks, prev, &cb->node);
//发送信号,通知收到匹配的event
k_sem_give(cb->sync_call);
}
从代码分析我们可以注意到放入的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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18net_mgmt(NET_REQUEST_WIFI_SCAN, iface, NULL, 0);
while(1){
net_mgmt_event_wait_on_iface(iface,
NET_EVENT_WIFI_SCAN_RESULT | NET_EVENT_WIFI_SCAN_DONE,
&raised_event,
&info,
&info_length,
K_FOREVER);
if(raised_event == NET_EVENT_WIFI_SCAN_RESULT){
//save scan result
}
if(raised_event == NET_EVENT_WIFI_SCAN_DONE){
//scan done, exit wait
break;
}
}
在请求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