2. 网络接口管理:网络接口的注册与配置、链路层地址管理、网络接口事件回调
好,咱们接着聊网络接口管理。说实话,这部分是Zephyr网络协议栈里最基础、也最容易踩坑的地方。我刚开始接触Zephyr时,以为网络接口就是简单配个IP地址完事。后来才发现,接口的注册、链路层地址管理、事件回调,这三块要是没搞明白,后面做TCPIP通信基本就是碰运气。
2.1 网络接口的注册与配置
在Zephyr里,每个网络接口都对应一个 struct net_if 结构体。你可以把它想象成一张网卡的抽象代表。系统启动时,这些接口是怎么来的?
说白了,有两种方式:
- 静态注册:通过设备树(DT)和Kconfig配置,编译时就定好了。大部分项目都用这种方式。
- 动态注册:运行时调用
net_if_register()手动添加。我一般只在测试或热插拔场景下用。
我个人习惯,能用静态就别用动态。为什么?因为动态注册你得自己管理生命周期,稍不留神就内存泄漏了。我曾经在一个项目里,因为动态注册的接口没及时释放,导致系统跑了三天后网络彻底瘫痪。嗯,从那以后我对动态注册就格外小心。
来看一个典型的静态注册例子。设备树里定义好以太网节点,Zephyr会自动生成对应的网络接口。你只需要在应用层拿到这个接口的指针:
#include <zephyr/net/net_if.h>
struct net_if *my_iface;
my_iface = net_if_get_default(); // 获取默认接口
拿到接口后,配置IP地址是常规操作。我建议用 net_if_config_ipv4_set() 或 net_if_config_ipv6_set():
struct in_addr addr;
struct net_if_addr *ifaddr;
if (net_addr_pton(AF_INET, "192.168.1.100", &addr)) {
printk("地址格式错误\n");
return;
}
ifaddr = net_if_ipv4_addr_add(my_iface, &addr, NET_ADDR_MANUAL, 0);
if (!ifaddr) {
printk("添加IP地址失败\n");
}
这里有个坑:NET_ADDR_MANUAL 表示手动配置,如果你用DHCP,应该用 NET_ADDR_DHCP。我见过有人混用这两个标志,结果地址冲突了,排查了半天。
net_if_is_up() 接口是否已启用。如果接口没起来,配了IP也白搭。
2.2 链路层地址管理
链路层地址,说白了就是MAC地址。每个网络接口都有一个唯一的MAC地址,Zephyr用 struct net_linkaddr 来表示。
你可能会问:MAC地址是硬件决定的,还需要管理吗?
需要。尤其是在以下场景:
- 你想覆盖硬件默认的MAC地址(比如某些开发板的MAC地址全是0)
- 你需要在运行时动态修改MAC(比如做MAC地址欺骗测试)
- 你使用虚拟接口(如Wi-Fi的STA和AP模式共用同一个物理网卡)
获取当前MAC地址很简单:
struct net_if *iface = net_if_get_default();
struct net_linkaddr *lladdr = net_if_get_link_addr(iface);
printk("MAC地址: %02x:%02x:%02x:%02x:%02x:%02x\n",
lladdr->addr[0], lladdr->addr[1], lladdr->addr[2],
lladdr->addr[3], lladdr->addr[4], lladdr->addr[5]);
设置MAC地址呢?用 net_if_set_link_addr():
uint8_t new_mac[] = {0x02, 0x00, 0x00, 0x00, 0x00, 0x01};
net_if_set_link_addr(iface, new_mac, sizeof(new_mac), NET_LINK_ETHERNET);
注意第三个参数 NET_LINK_ETHERNET,它告诉协议栈这个接口的链路层类型。如果你用的是Wi-Fi或蓝牙,要换成对应的类型。我曾经犯过一个错误:给一个蓝牙接口设置了以太网类型的链路层地址,结果上层协议栈解析数据包时全乱了。
net_if_down() 再 net_if_up(),否则某些驱动可能不会重新加载新地址。
另外,Zephyr还支持链路层地址的自动生成。比如IPv6的链路本地地址,就是基于MAC地址自动生成的。你想想看,这省了多少事。
2.3 网络接口事件回调
这部分我觉得是网络接口管理的精髓。什么叫事件回调?就是当网络接口状态发生变化时,系统主动通知你,而不是你轮询去查。
Zephyr提供了 net_mgmt 事件机制。你可以注册一个回调函数,监听特定的事件类型。常见的事件有:
| 事件宏 | 说明 |
|---|---|
NET_EVENT_IF_UP |
接口启用 |
NET_EVENT_IF_DOWN |
接口禁用 |
NET_EVENT_IPV4_ADDR_ADD |
添加了IPv4地址 |
NET_EVENT_IPV6_ADDR_ADD |
添加了IPv6地址 |
NET_EVENT_L4_CONNECTED |
传输层连接建立 |
注册回调的代码长这样:
static struct net_mgmt_event_callback mgmt_cb;
void my_event_handler(struct net_mgmt_event_callback *cb,
uint32_t mgmt_event,
struct net_if *iface)
{
if (mgmt_event == NET_EVENT_IF_UP) {
printk("接口 %p 已启用\n", iface);
} else if (mgmt_event == NET_EVENT_IF_DOWN) {
printk("接口 %p 已禁用\n", iface);
}
}
// 在初始化时注册
net_mgmt_init_event_callback(&mgmt_cb, my_event_handler,
NET_EVENT_IF_UP | NET_EVENT_IF_DOWN);
net_mgmt_add_event_callback(&mgmt_cb);
这里有个细节:net_mgmt_init_event_callback() 的第三个参数是事件掩码,你可以用 | 组合多个事件。我建议不要监听太多不相关的事件,否则回调函数会被频繁调用,影响性能。
我记得有一次做产品调试,发现设备偶尔断网。用轮询方式根本抓不到问题,因为断网只持续几百毫秒就恢复了。后来我注册了 NET_EVENT_IF_DOWN 回调,每次断网都打印日志,终于定位到是Wi-Fi驱动在信号弱时自动重连导致的。你看,回调机制在这种场景下多有用。
另外,Zephyr还支持自定义事件。你可以定义自己的事件ID,通过 net_mgmt_event_notify() 触发。这个功能我一般在做协议栈扩展时用,比如自定义一个“链路质量变差”的事件。
好了,网络接口管理这块就聊这么多。总结一下:注册接口要选对方式,配置地址要小心标志位,管理MAC地址要注意类型,事件回调是调试利器。下一节咱们聊IP地址和路由表,那又是另一番天地了。