一个系统中可能有多个网络接口,有可能是以太网,有可能是WiFi,也有可能是其他的网络接口。
在lwip中每一个网络接口都由一个netif结构体表示。表示不同网卡的netif结构体链接成一个链表。全局变量struct netif *netif_list指向该链表表头。
全局变量struct netif *netif_default指向的netif结构所表示的网卡为缺省网卡。在发送消息的时候,会首先会通过这个网卡,若是没有回应,再使用其他网卡。
在之前的移植实例中,因为只有一个以太网卡作为网络接口,所以网卡就用struct netif netif表示了。
struct netif定义如下
struct netif {
struct netif *next;
char name[2];
int num;
struct ip_addr ip_addr;
struct ip_addr netmask;
struct ip_addr gw;
void (* input)(struct pbuf *p, struct netif *inp);
int (* output)(struct netif *netif, struct pbuf *p,
struct ip_addr *ipaddr);
void *state;
};
(1) next
指向链表中下一个netif结构
(2) ip_addr、netmask 和 gw
这三个结构体成员分别用来存储本网卡的 IP 地址、子网掩码和网关。
(3) err_t (* input)(struct pbuf *p, struct netif *inp)
这个函数是用来接收底层硬件输入的数据包的。函数的返回值是 err_t 类型,它是由 LwIP 定义的一种用于表示操作成功、失败等参数的变量。函数的输入参数为 pbuf 类型的 p 指针和 netif 类型的 inp 指针,表示我们要接收 inp 网卡通过底层硬件接收到的数据包,并把数据包保存到结构体 p 中。
(4) err_t (* output)(struct netif *netif, struct pbuf *p, struct ip_addr *ipaddr)
本成员也是一个函数指针,它指向一个用于 IP 层输出的函数。在 IP 层有数据包需要发送时,就通过该指针调用 output 函数。本函数输入参数有 netif、pbuf 和 ip_addr 三个参数,表示使用 netif 网卡、把 pbuf 结构体的内容发送到 IP 地址为 ip_addr 的设备中。 但由于在硬件底层收发数据时是使用 MAC 地址的,所以本函数把 IP 地址经过转化得到 MAC地址后,调用下一个成员——linkoutput 函数。
(5) err_t (* linkoutput)(struct netif *netif, struct pbuf *p)
这个函数指针是指向最底层的网卡发送数据包函数。它是被上面的 output 函数调用的。在 output 函数中的 pbuf 数据包实际上缺少了 MAC 包中的 DA 段(目标 MAC 地址),output 函数把它的输入参数 IP 地址转化成 MAC 地址后,添加到 pbuf 的 payload 成员,组成完整 MAC 包,再由本函数 linkoutput 输出到网络中。
(6) state
state 指针指向一用户感兴趣的信息,这部分可以由用户自行配置,也可以不使用。
(7) mtu
本成员记录了最大传输单元,我们知道 MAC 数据段的最大长度为 1500 字节,所以本成员我们一般会直接赋值为 1500。
(8) hwaddr_len 和 hwaddr[NETIF_MAX_HWADDR_LEN]
hwaddr_len 存储的是 MAC 地长度,hwaddr[]数组则存储了本网卡的 MAC 完整地址。
(9) flags
本成员保存了网卡允许使用的功能,如使用广播地址、ARP 功能等。
(10) name 和 num
name 用于保存网络接口的名字,可以自由设置,名字是两个字节的
网络接口的初始化过程,根据之前的移植工程的步骤(主要netcong.c中)
struct netif netif; //定义网卡netif结构体
struct ip_addr ipaddr;
struct ip_addr netmask;
struct ip_addr gw;
IP4_ADDR(&ipaddr, 192, 168, 1, 254); //ip
IP4_ADDR(&netmask, 255, 255, 255, 0); //掩码
IP4_ADDR(&gw, 192, 168, 1, 1);//网关
netif_add(&netif, &ipaddr, &netmask, &gw, NULL, ðernetif_init, ðernet_input);
//将之前设定的ip,掩码,网关初始化初始化netif结构体,结构体相关成员设定。
//链路层输出函数ethernet_input函数(ethnetif.c)与绑定结构体成员函数指针input
//netif_add函数的还有一个功能是调用底层硬件初始化函数ethernetif_init(ethnetif.c )对底层硬件进行初始化,同时netif结构体的部分成员在这里初始化。ethernetif_init部分如下:
netif->state = ethernetif;
netif->name[0] = IFNAME0;//网络接口模块
netif->name[1] = IFNAME1;
netif->output = etharp_output; //将netif结构体的输出函数指正与etharp_output(etharp.c)绑定
netif->linkoutput = low_level_output;//将netif结构体的arp连接输出指针与low_level_output(ethernetif.c)绑定
ethernetif->ethaddr = (struct eth_addr *)&(netif->hwaddr[0]);
low_level_init(netif);//对网络模块进行初始化,具体会根据网络接口,之前的移植实例用的Enc28j60模块。该函数同时会将硬件地址赋值给netif 相关成员,netif结构体也被链入链表 netif_list。
netif_set_default(&netif);
//设定该网卡为默认网卡,全局变量netif_default指针指向该结构体
netif_set_up(&netif);//实现该接口
网络接口初初始化之后,除了节本的网络参数之外(ip,mac等)。
两个全局变量指针netif_list,netif_default的都指向了改网络接口节点。
当
IP 层有数据发送时,它首先会以 netif_list 为索引选择满足
某个条件的网络接口发送数据包,但是,当找不到这样的接口时,协议栈就会调用缺省的网
络接口直接发送数据包。
netif结构体相关函数指针成员都绑定了相关函数
netif->input =
ethernet_input;
netif->output = etharp_output;
netif->linkoutput = low_level_output;
当网卡有接收到数据(查询或者中断的方式)的时候,调用netif->input即ethernet_input(ethnernetif.c)会根据不同的报文类型,会执行不同的动作,具体看函数源码。例如若是ETHTYPE_IP时ip报文的时候,则调用ip_input(p, netif)将发往ip,交由ip层处理。
若是ip层有数据需要发送出去,则调用netif->output即etharp_output(etharp_out.c)将数据发送到底层。
若是底层数据发送到网络接口上发送出去,则调用
netif->linkoutput 即low_level_output(ethnernetif.c)将数据通过网卡发送出去。