Zephyr网络管理模块分析-Event机制

本文说明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的情况

static 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发生时,触发callback

//指定要响应的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的方式,示例如下:

net_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初始化的时,做下面三件事

  1. 一个callback链表,用于管理响应event的callback
  2. 创建一个FIFO用来缓存驱动/协议栈送上来的event
  3. 创建一个thread用来分发处理event

整个流程可以概括为下面几个过程:

  1. 应用通过net_mgmt_add_event_callback/net_mgmt_del_event_callback 添加或删除event callback
  2. 驱动或者协议栈通过net_mgmt_event_notify_with_info向FIFO写入event
  3. 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属于哪一层,有下面几个选项

#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下有

enum 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提供了下面的宏

#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的示例如下

#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)	

接口分析 链接到标题

初始化 链接到标题

初始化的代码分析如下

void 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发生时调用

void 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中

void 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重新生成。

static 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进行判断,如果符合条件才进行发送

static 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指定,目前是由下面方式指定

#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完成,这里列出主要的特征,需要了解细节可以查看代码:

  1. 使用events作为FIFO内存,可以缓存event的数量由CONFIG_NET_MGMT_EVENT_QUEUE_SIZE指定,默认是2,可配置范围1~1024
  2. 满足先进先出由全局变量out_event和in_event控制读出和写入,当FIFO满后,新的数据覆盖久的数据
  3. 如果发送event要携带的数据大于NET_EVENT_INFO_MAX_SIZE,event不会被放入到FIFO而直接被丢弃
  4. event被pop使用完后需要使用mgmt_clean_event对其内容进行清空

Callback链表 链接到标题

Callback链表节点数据结构如下

struct 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中完成

static 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进行处理。

static inline void mgmt_run_callbacks(struct mgmt_event_entry *mgmt_event)
{
	sys_snode_t *prev = NULL;
	struct net_mgmt_event_callback *cb, *tmp;

	//遍历callback list event_callbacks
	SYS_SLIST_FOR_EACH_CONTAINER_SAFE(&event_callbacks, cb, tmp, node) {
		//对匹配的event进行处理
		// 采用排除匹配,layer和layer code只要不相同就不处理,command只要包含就处理
		if (!(NET_MGMT_GET_LAYER(mgmt_event->event) ==
		      NET_MGMT_GET_LAYER(cb->event_mask)) ||
		    !(NET_MGMT_GET_LAYER_CODE(mgmt_event->event) ==
		      NET_MGMT_GET_LAYER_CODE(cb->event_mask)) ||
		    (NET_MGMT_GET_COMMAND(mgmt_event->event) &&
		     NET_MGMT_GET_COMMAND(cb->event_mask) &&
		     !(NET_MGMT_GET_COMMAND(mgmt_event->event) &
		       NET_MGMT_GET_COMMAND(cb->event_mask)))) {
			continue;
		}

		//将event信息保存到callback中
		if (mgmt_event->info_length) {
			cb->info = (void *)mgmt_event->info;
			cb->info_length = mgmt_event->info_length;
		} else {
			cb->info = NULL;
			cb->info_length = 0;
		}

		//检查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);

			if (sync_data->iface &&
			    sync_data->iface != mgmt_event->iface) {
				continue;
			}

			NET_DBG("Unlocking %p synchronous call", cb);

			cb->raised_event = mgmt_event->event;
			sync_data->iface = mgmt_event->iface;

			sys_slist_remove(&event_callbacks, prev, &cb->node);

			k_sem_give(cb->sync_call);
		} else {
			//不是sync event,调用callback进行处理

			cb->handler(cb, mgmt_event->event, mgmt_event->iface);
			prev = &cb->node;
		}
	}
}

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_waitnet_mgmt_event_wait_on_iface,两个函数实现如下

int 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的流程如下:

static 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会对其进行判断,如果匹配会发送信号量通知结束等待, 代码片段如下

		//检查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_waitnet_mgmt_event_wait_on_iface的通过event_mask设置同时等待多个event时,只要有其中一个event发生,等待就结束。 另外一个问题:多次循环调用net_mgmt_event_waitnet_mgmt_event_wait_on_iface等待不同的event呢,例如下面代码:

net_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