网络接口管理
参照TCP/IP协议的分层结构,可以从逻辑上将LwIP分为4个层次:链路层、网络层、传输层和应用层,其中网络接口管理属于链路层的范凑。运行LwIP的嵌入式设备可以有多个网络接口。为了实现对所有网络接口的有效管理,协议栈内部使用了一个名为netif的网络接口结构来描述各种网络设备。
另外,环境接口提供了一种对硬件接口的纯软件模拟,它允许用户在没有硬件网络接口的环境下运行协议栈,实现对协议栈的调试,同时使用环回接口还可以实现同一个设备上的两个进程基本TCP/IP进行互相通信。
本文内容包括:
- 网络接口管理的作用
- 网络接口结构netif
- 环回接口的概念及作用
一、引言
对网络接口的有效管理,是协议栈能与外部进行通信的关键。把网络接口的管理描述为链路层的部分,旨在对具体网络硬件、软件进行统一的封装,并为协议栈上层(IP层)提供统一的接口服务。为了实现对接口结构的有效管理,LwIP会为每个接口分配一个netif结构,用这个结构来描述每种接口的特性。
内核将所有网络结构的netif结构组织在一个叫做netif_list的链表上,当有IP数据包需要发送时,IP层会根据数据包的目的IP地址,在netif_list链表中选择一个最合适的网络接口,并调用其注册的数据包发送函数将数据包发送出去;在网卡接收到数据包时,其注册的数据包输入函数也会被调用,完成奖数据包递交给IP层的任务。
从整个过程来看,网络接口管理有效的为上层屏蔽掉了底层各个硬件接口间的差异,并为底层网络接口程序的编写提供了规范化的接口定义。
二、网络接口结构
数据结构netif是内核的重要组成部分,它完成了对各种类型网络接口的抽象。
1. 数据结构
//网络接口最大物理地址长度,这里定义为以太网网卡MAC地址的长度:6 #define NETIF_MAX_HWADDR_LEN 6U //下面几个宏为网卡接口属性、状态相关的宏,主要用于描述netif中flags字段的各位 #define NETIF_FLAG_UP 0x01U //网络接口是否已被上层使能 #define NETIF_FLAG_BROADCAST 0x02U //网络接口是否支持广播 #define NETIF_FLAG_POINTTOPOINT 0x04U //网络接口是否属于点对点连接 #define NETIF_FLAG_DHCP 0x08U //网络接口是否支持DHCP功能 #define NETIF_FLAG_LINK_UP 0x10U //网络接口的底层链表是否已经使能 #define NETIF_FLAG_ETHARP 0x20U //网络接口是否支持ARP功能 #define NETIF_FLAG_ETHERNET 0x40U #define NETIF_FLAG_IGMP 0x80U //网络接口是否支持IGMP功能 /** Generic data structure used for all lwIP network interfaces. * The following fields should be filled in by the initialization * function for the device driver: hwaddr_len, hwaddr[], mtu, flags */ struct netif { struct netif *next; //指向下一个netif结构,在构成链表netif_list时使用 /** IP address configuration in network byte order */ ip_addr_t ip_addr; //网络接口的IP地址 ip_addr_t netmask; //子网掩码 ip_addr_t gw; //网关地址 //下面为三个函数指针,调用它们指向的函数就可以完成数据包的发送或接收 /** This function is called by the network device driver * to pass a packet up the TCP/IP stack. */ netif_input_fn input; /** This function is called by the IP module when it wants * to send a packet on the interface. This function typically * first resolves the hardware address, then sends the packet. */ netif_output_fn output; /** This function is called by the ARP module when it wants * to send a packet on the interface. This function outputs * the pbuf as-is on the link medium. */ netif_linkoutput_fn linkoutput;//该函数实现底层数据包的发送 /** This field can be set by the device driver and could point * to state information for the device. */ void *state; //该字段用户可以自由设置 /** maximum transfer unit (in bytes) */ u16_t mtu; //该接口允许的最大数据包长度,对于以太网一般设为1500 /** number of bytes used in hwaddr */ u8_t hwaddr_len; //该接口物理地址长度 /** link level hardware address of this interface */ u8_t hwaddr[NETIF_MAX_HWADDR_LEN]; //该接口的物理地址 /** flags (see NETIF_FLAG_ above) */ u8_t flags; //该接口的状态、属性字段 /** descriptive abbreviation */ char name[2]; //该接口的名字 /** number of this interface */ u8_t num; //该接口的编号 #if ENABLE_LOOPBACK /* List of packets to be queued for ourselves. */ struct pbuf *loop_first; //指向发送给自己的数据包的第一个pbuf struct pbuf *loop_last; //指向发送给自己的数据包的最后一个pbuf #if LWIP_LOOPBACK_MAX_PBUFS u16_t loop_cnt_current; #endif /* LWIP_LOOPBACK_MAX_PBUFS */ #endif /* ENABLE_LOOPBACK */ };
2. 函数实现
(1)对于网络接口管理,对应的管理函数比较少。这里重点讲述如何向系统注册一个网络接口设备的函数netif_add:
//定义两个全局型的netif指针 struct netif *netif_list; //系统的全局型netif链表 struct netif *netif_default; //记录系统缺省(默认)网络接口 /** * @function:Add a network interface to the list of lwIP netifs. * * @param netif:a pre-allocated netif structure * @param ipaddr:IP address for the new netif * @param netmask:network mask for the new netif * @param gw:default gateway IP address for the new netif * @param state:opaque data passed to the new netif * @param init:callback function that initializes the interface * @param input:callback function that is called to pass * ingress packets up in the protocol layer stack. * * @return:registered netif, or NULL if failed. */ struct netif * netif_add(struct netif *netif, ip_addr_t *ipaddr, ip_addr_t *netmask, ip_addr_t *gw, void *state, netif_init_fn init, netif_input_fn input) { static u8_t netifnum = 0; //定义静态变量netifnum,它记录网络接口的编号 LWIP_ASSERT("No init function given", init != NULL); /* reset new interface configuration state */ ip_addr_set_zero(&netif->ip_addr); ip_addr_set_zero(&netif->netmask); ip_addr_set_zero(&netif->gw); netif->flags = 0; /*填写结构体各个字段值 */ netif->state = state; netif->num = netifnum++; //网络接口编号 netif->input = input; //注册input函数 //调用函数设置网络接口的三个地址字段值 netif_set_addr(netif, ipaddr, netmask, gw); /*init:调用网络接口的初始化函数,初始化网络接口 */ if (init(netif) != ERR_OK) { return NULL; //初始化失败,则返回空 } /* add this netif to the list */ netif->next = netif_list; netif_list = netif; return netif; //返回netif结构指针 }
(2)netif_add函数只是简单地初始化了netif结构的几个字段,然后回调网络接口定义的初始化函数init来完成网络接口的初始化工作。为了完成整个网络接口的初始化过程,需要自行编写netif_init函数,以向系统注册网卡设备。如下所示:
void netif_init(void) { IP4_ADDR(&gw, 192,168,1,1); //初始化三个地址
IP4_ADDR(&ipaddr, 192,168,1,37);
IP4_ADDR(&netmask, 255,255,255,0); netif_add(&xx_netif, &xx_ipaddr, &netmask, &gw, NULL, ethernetif_init, ethernetif_input); netif_set_default(&xx_netif); //设置系统的默认网络接口 netif_set_up(&xx_netif); //使能网络接口 }
上面的函数中,调用netif_add函数时,传递给它的两个函数参数是ethernetif_init和ethernetif_input,其中前者就是网卡初始化函数ethernetif_init,以下讲述源码提供者为以太网网卡驱动程序编写的默认初始化函数。(ethernetif_input是ARP层的一个函数,它的功能是提取以太网帧中的ARP地址数据,并将帧中的IP数据递交给IP层,关于此函数将在下篇文章中讲述。)
(3)网卡初始化ethernetif_init函数
/* Define those to better describe your network interface. */ #define IFNAME0 'e' #define IFNAME1 'n' //定义描述网卡用户信息的结构,该结构无实际用处 //源码作者只在用该结构来示意netif中state的用法,该可以可以指向任何用户关心的信息 struct ethernetif { struct eth_addr *ethaddr; /* Add whatever per-interface state that is needed here. */ }; /** * Should be called at the beginning of the program to set up the * network interface. It calls the function low_level_init() to do the * actual setup of the hardware. * * This function should be passed as a parameter to netif_add(). * * @param netif:the lwip network interface structure for this ethernetif * @return:ERR_OK if the loopif is initialized * ERR_MEM if private data couldn't be allocated * any other err_t on error */ err_t ethernetif_init(struct netif *netif) { struct ethernetif *ethernetif; LWIP_ASSERT("netif != NULL", (netif != NULL)); ethernetif = mem_malloc(sizeof(struct ethernetif)); if (ethernetif == NULL) { LWIP_DEBUGF(NETIF_DEBUG, ("ethernetif_init: out of memory\n")); return ERR_MEM; //申请失败,返回内存错误 } netif->state = ethernetif; //将state字段指向ethernetif netif->name[0] = IFNAME0; //设置名字字段 netif->name[1] = IFNAME1; /* We directly use etharp_output() here to save a function call. * You can instead declare your own function an call etharp_output() * from it if you have to do some checks before sending (e.g. if link * is available...) */ netif->output = etharp_output; //注册IP数据包输出函数,这里使用ARP的相关函数 netif->linkoutput = low_level_output; //注册以太网数据帧输出函数 ethernetif->ethaddr = (struct eth_addr *)&(netif->hwaddr[0]);//指向网卡的地址信息 /* initialize the hardware */ low_level_init(netif); //调用网卡底层初始化函数,源码已提供模板 return ERR_OK; }
(4)网卡底层初始化low_level_init函数:
/** * In this function, the hardware should be initialized. * Called from ethernetif_init(). * * @param netif the already initialized lwip network interface structure * for this ethernetif */ static void low_level_init(struct netif *netif) { struct ethernetif *ethernetif = netif->state; /* set MAC hardware address length */ netif->hwaddr_len = ETHARP_HWADDR_LEN; /* set MAC hardware address */ netif->hwaddr[0] = ; ... netif->hwaddr[5] = ; /* maximum transfer unit */ netif->mtu = 1500; /* device capabilities */ /* don't set NETIF_FLAG_ETHARP if this device is not an ethernet one */ netif->flags = NETIF_FLAG_BROADCAST | NETIF_FLAG_ETHARP | NETIF_FLAG_LINK_UP; /* Do whatever else is needed to initialize interface. */ //需要在此完成网卡复位与初始化程序段,或函数 }
- 终:经过上面过程,网卡设备相关的上层结构域底层硬件都初始化好了, 这样协议栈就可以使用网卡了。
参考资料 《嵌入式网络那些事——LwIP协议深度剖析与实战演练》 朱升林编著