LwIP协议栈支持多种不同的网络接口(网卡),由于网卡是直接跟硬件平台打交道,硬件不同则处理也是不同。那Iwip如何兼容这些不同的网卡呢?
LwIP提供统一的接口,底层函数需要用户自行完成,例如网卡的初始化、接收和发送数据等。当LwIP底层接收到网络数据时,需要经过层层递交才会传入内核处理;相反,LwIP发送数据时也会调用网卡的发送数据。对于没有接触过LwIP的人新手来说,我们如何编写这个底层代码呢?不用担心,LwIP提供了一个ethernetif.c 文件作为底层接口的驱动模版。用户只根据自己的网络设备进行参照修改即可。
在LwIP中,netif数据结构用于描述一个网络接口的特性。这个结构体是在netif.h文件中定义的
struct netif {
/* 指向下一个 netif 结构的指针 */
struct netif *next;
/* IP 地址相关配置 */
ip_addr_t ip_addr; /* 网络接口的 IP 地址 */
ip_addr_t netmask; /* 子网掩码 */
ip_addr_t gw; /* 网关地址 */
/* 该函数向 IP 层输入数据包 */
netif_input_fn input;
/* 该函数发送 IP 包 */
netif_output_fn output;
/* 该函数实现底层数据包发送 */
netif_linkoutput_fn linkoutput;
/* 该字段用户可以自由设置,例如用于指向一些底层设备相关的信息 */
void *state;
void* client_data[LWIP_NETIF_CLIENT_DATA_INDEX_MAX +
LWIP_NUM_NETIF_CLIENT_DATA];
/* 该接口允许的最大数据包长度 */
u16_t mtu;
/* 该接口物理地址长度 */
u8_t hwaddr_len;
/* 该接口的物理地址 */
u8_t hwaddr[NETIF_MAX_HWADDR_LEN];
/* 该接口的状态、属性字段 */
u8_t flags;
/* 该接口的名字 */
char name[2];
/* 接口的编号 */
u8_t num;
/* 需要发送的路由器请求消息的数量 */
u8_t rs_count;
};
这些字段用于描述各个网卡的差异,每个网卡都通过一个 netif 结构体进行抽象表示。多 个网卡对应多个 netif,这些 netif 以链表的形式链接起来,形成一个单向链表。
这个链表的首个节点由 netif_list 指针指向,lwIP 内核使用该指针遍历和查询 netif 链表。
struct netif *netif_list; /* 网络接口链表指针 */
struct netif *netif_default; /* 哪个网络接口(多网口时候) */
static u8_t netif_num; /* 为网口分配唯一标识 */
在 lwIP 中,netif.c 文件包含了管理网络接口的核心函数。其中,netif_default 指针指向默 认的网络接口,当网络层需要发送数据包时,系统优先选择该指针所指向的网卡进行数据发送。 如果该网卡没有响应,则会选择其他网卡进行发送
1.netif_add 函数
该函数用于将新创建的 netif 插入到 netiflist 队列中,以表示添加了一个网络接口。
struct netif *
netif_add( struct netif *netif,
const ip4_addr_t *ipaddr, const ip4_addr_t *netmask,
const ip4_addr_t *gw,
void *state, netif_init_fn init, netif_input_fn input)
{
/* 清空主机 IP 地址、子网掩码、网关等信息。 */
ip_addr_set_zero_ip4(&netif->ip_addr);
ip_addr_set_zero_ip4(&netif->netmask);
ip_addr_set_zero_ip4(&netif->gw);
netif->output = netif_null_output_ip4;
/* 传输的最大数据长度 */
netif->mtu = 0;
/* 网络的接口状态 */
netif->flags = 0;
memset(netif->client_data, 0, sizeof(netif->client_data));
/* 传递进来的参数填写网卡 state、input 等字段的相关信息 */
netif->state = state;
/* 并为当前网卡 分配唯一标识 num */
netif->num = netif_num;
/* 网卡输入 */
netif->input = input;
/* 调用网卡设置函数 netif_set_addr()设置网卡 IP 地址、子网掩码、网关 */
netif_set_addr(netif, ipaddr, netmask, gw);
/* 为 netif 调用用户指定的初始化函数 */
if (init(netif) != ERR_OK) {
return NULL;
}
/* 将这个 netif 添加到列表中 */
netif->next = netif_list;
netif_list = netif;
mib2_netif_added(netif);
netif_invoke_ext_callback(netif, LWIP_NSC_NETIF_ADDED, NULL);
return netif;
}
每一个 netif 结构体是对一个网卡进行抽象,包含了该网 卡的收发函数、状态等信息。
注:新插入的 netif 结构体是在 netiflist 队列的首部插入
2.netif_set_default 函数
该函数用于将指定的 netif 结构体设置为默认网卡,使 lwIP 内核优先对该网卡进行操作。
void
netif_set_default(struct netif *netif)
{
if (netif == NULL)
{
/* 删除默认路由 */
mib2_remove_route_ip4(1, netif);
}
else
{
/* 添加默认路由 */
mib2_add_route_ip4(1, netif);
}
netif_default = netif; /* 选择那个网络接口 */
}
/*********************怎么使用函数 netif_set_default()*********************/
/* 通过该函数,将网络接口添加到链表中 */
netif_add(&xnetif, &ipaddr, &netmask, &gw, NULL, ðernetif_init,&tcpip_input);
/* 注册默认的网络接口 */
netif_set_default(&xnetif);
调用该函数,可以设置指定的网络接口为默认网卡。在函数内部,会更新相应的默认 网卡指针,并确保后续的网络操作优先使用该网卡进行数据传输。