LWIP学习笔记之网络接口管理(二)

一、引言

  LWIP分为四个层次:链路层、网络层、传输层和应用层。运行LWIP的嵌入式设备可以有多个网络接口:以太网接口、串行链路接口、环回接口等。为了实现对所有网络接口的有效管理,协议栈内部使用了一个名为netif的网络接口结构来描述各种网络设备。本章讨论的内容包括:

  网络接口管理的作用;

  网络接口结构netif;

二、网络接口管理

1、数据结构  

  源文件中 netif.c 和 netif.h 文件实现了与网络接口结构管理相关的所有数据结构和函数。来看看结构 netif 是怎样被定义的,如下代码所示。 

————netif.h————————————————
//网络接口最大物理地址长度,这里定义为以太网网卡 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_IGMP 0x40U //网络接口是否支持 IGMP 功能 //下面是结构 netif 的定义 struct netif 
{   struct netif *next; //指向下一个 netif 结构,在构成链表 netif_list 时使用   struct ip_addr ip_addr; //网络接口的 IP 地址   struct ip_addr netmask; //子网掩码   struct ip_addr gw; //网关地址   //下面为三个函数指针,调用它们指向的函数就可以完成数据包的发送或接收   err_t (* input)(struct pbuf *p, struct netif *inp); //该函数向 IP 层输入数据包   err_t (* output)(struct netif *netif, struct pbuf *p,struct ip_addr *ipaddr); //该函数发送 IP 包   err_t (* linkoutput)(struct netif *netif, struct pbuf *p); //该函数实现底层数据包发送   void *state; //该字段用户可以自由设置,例如用于指向一些底层设备相关的信息   u16_t mtu; //该接口允许的最大数据包长度   u8_t hwaddr_len; //该接口物理地址长度   u8_t hwaddr[NETIF_MAX_HWADDR_LEN]; //该接口的物理地址   u8_t flags; //该接口的状态、属性字段   char name[2]; //该接口的名字   u8_t num; //接口的编号   //在接口自输入使能或者有环回接口的情况下,下面的字段   //用于描述接口发送给自己的数据包   struct pbuf *loop_first; //指向发送给自己的数据包的第一个 pbuf   struct pbuf *loop_last; //指向发送给自己的数据包的最后一个 pbuf };

  next 字段是指向下一个 netif 结构的指针,前面也说过,一个设备可能会有多个网络接口(例如路由器),LwIP 会把所有网络接口的 netif 结构组成一个链表进行管理,有一个名为 netif_list 的全局变量指向该链表的首部。next 字段就是用于组成链表时用。   

  ip_addr、netmask、gw 三个字段用于描述该网络接口的网络地址属性,依次称它们为接口 IP地址、子网掩码和网关地址。IP 地址和网络接口必须一一对应,即设备有几个硬件网络接口,它就得有几个 IP 地址;子网掩码可以用来判断某个目的 IP 地址与当前网络接口 IP 地址是否处于同一子网中,IP 层会选择与目的 IP 处于同一子网的网络接口来发送数据包。gw 字段在数据包的发送、转发过程中也有重要作用,如果目的 IP 地址与所有网络接口都不属于同一子网,LwIP 将会把数据包发送到网关处,因为它认为网关设备会对该 IP 包进行正确的转发,此外网关也为设备提供了许多高级服务,如 DHCP、DNS 等。   

  input 字段指向一个函数,这个函数将网络设备接收到的数据包提交给 IP 层(在以太网中,通常这个函数需要解析以太网数据帧,然后将从中得到的 IP 数据包递交)。被指向的函数具有两个参数:一个是 pbuf 类型,代表将要递交的数据包;另一个为 netif 类型,代表递交数据包的网络设备,函数返回值是 err_t 类型。网络设备初始化时应该向 input 字段注册相应的输入函数,后面将详细讨论这个问题。     

  output 字段指向一个函数,这个函数和具体网络接口设备驱动密切相关,它用于将 IP 层数据包发送到目的地址处。在 IP 层看来,每个网络接口都会提供一个这样的函数供它调用,当 IP 层发送数据包时,它会遍历 netif_list 链表,找出最合适的网络接口,并调用其注册的 output 函数发送 数据包。不同的网络接口需要根据实际接口特性来编写相关的发送函数,并在初始化时将 output字段指向该函数。就以太网网卡的数据包发送而言,LwIP 在源代码 etharp.c 文件中实现了一个名为 etharp_output 的函数,该函数就可以直接完成 IP 数据包在以太网中的发送,所以在初始化以太网卡结构时,可以直接将 output 字段指向函数 etharp_output。output 指向函数的三个参数分别是 pbuf类型、netif 类型和 ip_addr 类型,返回值是 err_t 类型。其中 pbuf 代表要发送的 IP 数据包,ipaddr代表数据包的目的 IP 地址

   linkoutput 字段和上面的 output 基本上是相似的功能,但是更底层一些,这个函数主要在以太网卡通信中被 ARP 模块调用,用来完成以太网数据帧的发送,在其他类型的网络接口中,这个字段的值就没啥用处了。上面说的函数 etharp_output,它一方面接收 IP 层数据包,另一方面将该数据包封装为以太网数据帧(填写物理地址等字段),最后便调用 linkoutput 字段注册的函数将以太网 数 据 帧 发 送 出 去 。 

  为了实现对这些数据包的管理,在网络接口结构 netif 中定义了两个指针 loop_first 和loop_last,它们分别用于指向数据包 pbuf 链表的第一个 pbuf 和最后一个 pbuf。这里的 pbuf 链表比较特殊,因为所有数据包的 pbuf 都组织在同一条链表上,这就说明一条 pbuf 链表上可能存在多个 数据包,这时 pbuf 结构的 next 字段是否为空并不能成为判断一个数据包结束与否的标志,那在这样的一条 pbuf 链表中,怎样去将各个数据包的 pbuf 划分开呢?答案在于 pbuf 结构中的 tot_len 字段和 len 字段值,当两个字段的值相等时,就代表一个数据包的结束。 

2、函数实现  

  向系统注册一个网络接口设备的函数 netif_add:

————netif.c————————————————
//定义两个全局型的 netif 指针
struct netif *netif_list; //系统的全局型 netif 链表 struct netif *netif_default; //记录系统缺省(默认)网络接口 //函数功能:向 LwIP 内核注册一个网络接口结构 //参数 netif:指向一个已分配好的 netif 结构体 //参数 ipaddr:网络接口的 IP 地址 //参数 netmask:网络接口子网掩码 //参数 gw:网关地址 //参数 state:用户自定义的一些数据信息 //参数 init:网络接口的初始化函数 //参数 input:网络接口向 IP 层提交数据包的函数 //返回值:成功注册的网络接口结构指针 struct netif *netif_add(struct netif *netif, struct ip_addr *ipaddr, struct ip_addr *netmask, struct ip_addr *gw, void *state, err_t (* init)(struct netif *netif), err_t (* input)(struct pbuf *p, struct netif *netif)) {   static u8_t netifnum = //定义静态变量 netifnum,它记录网络接口的编号   //清空 netif 结构的各个字段值   netif­>ip_addr.addr = 0;   netif­>netmask.addr = 0;   netif­>gw.addr = 0;   netif­>flags = 0;   netif­>loop_first = NULL;   netif­>loop_last = NULL;   //填写结构体各个字段值   netif­>state = state;   netif­>num = netifnum++; //网络接口编号   netif­>input = input; //注册 input 函数   //调用函数 netif_set_addr 设置网络接口的三个地址字段值   netif_set_addr(netif, ipaddr, netmask, gw);   //调用网络接口的初始化函数,初始化网络接口   if (init(netif) != ERR_OK)
  {
    //初始化失败,则返回空     return NULL;   }   //将初始化成功的 netif 结构加入 netif_list 链表   netif­>next = netif_list;   netif_list = netif;   //返回 netif 结构指针   return netif; }

  netif_add 函数只是简单的初始化了 netif 结构的几个字段,然后回调网络接口定义的初始化函数 init 来完成网络接口的初始化工作。 

IP4_ADDR(&gw, 192,168,1,1); //初始化三个地址 IP4_ADDR(&ipaddr, 192,168,1,37); IP4_ADDR(&netmask, 255,255,255,0//调用 netif_add 函数 netif_add(&rtl8019_netif, &ipaddr, &netmask, &gw, NULL, ethernetif_init,ethernet_input); netif_set_default(&rtl8019_netif); //设置系统的默认网络接口为 rtl8019_netif netif_set_up(&rtl8019_netif); //使能网络接口 rtl8019_netif

  在上面这段代码中,调用 netif_add 函数时,传递给它的两个函数参数是 ethernetif_init 和ethernet_input,其中前者就是网卡初始化函数 ethernetif_init,这是源码提供者为以太网网卡驱动程序编写的默认初始化函数,这个函数在第 5 章中也有所提及,这里来看看它的具体实现,源代码如下所示。ethernet_input 是 ARP 层的一个函数,它的功能是提取以太网帧中的 ARP 地址数据,并将帧中的 IP 数据递交给 IP 层,关于这个函数在下一章中会讲解。 

#define IFNAME0 'e' //定义以太网网卡的名字字符
#define IFNAME1 'n'
//定义描述网卡用户信息的结构,该结构无实际用处,源码作者旨在用该结构来示意 //netif 结构中的 state 字段的用法,该字段可以指向任何用户关心的信息 struct ethernetif 
{ //该结构只包含一个简单的指针   struct eth_addr *ethaddr; }; //函数参数 netif:网络接口结构的指针 err_t ethernetif_init(struct netif *netif) {   struct ethernetif *ethernetif;   ethernetif = mem_malloc(sizeof(struct ethernetif)); //为用户信息结构申请一个内存堆空间   if (ethernetif == NULL) { //申请失败,返回内存错误     return ERR_MEM;    }   netif­>state = ethernetif; //将 state 字段指向 ethernetif   netif­>name[0] = IFNAME0; //设置名字字段   netif­>name[1] = IFNAME1;   netif­>output = etharp_output; //注册 IP 数据包输出函数,这里使用 ARP 的相关函数   netif­>linkoutput = low_level_output; //注册以太网数据帧输出函数   //将 ethernetif 结构赋一个用户关心的值,这里无具体意义   ethernetif­>ethaddr = (struct eth_addr *)&(netif­>hwaddr[0]); //指向网卡的地址信息   low_level_init(netif); //调用网卡底层初始化函数,这个函数已经讲过了   return ERR_OK; //返回成功 }

一、引言

  LWIP分为四个层次:链路层、网络层、传输层和应用层。运行LWIP的嵌入式设备可以有多个网络接口:以太网接口、串行链路接口、环回接口等。为了实现对所有网络接口的有效管理,协议栈内部使用了一个名为netif的网络接口结构来描述各种网络设备。本章讨论的内容包括:

  网络接口管理的作用;

  网络接口结构netif;

二、网络接口管理

1、数据结构  

  源文件中 netif.c 和 netif.h 文件实现了与网络接口结构管理相关的所有数据结构和函数。来看看结构 netif 是怎样被定义的,如下代码所示。 

————netif.h————————————————
//网络接口最大物理地址长度,这里定义为以太网网卡 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_IGMP 0x40U //网络接口是否支持 IGMP 功能 //下面是结构 netif 的定义 struct netif 
{   struct netif *next; //指向下一个 netif 结构,在构成链表 netif_list 时使用   struct ip_addr ip_addr; //网络接口的 IP 地址   struct ip_addr netmask; //子网掩码   struct ip_addr gw; //网关地址   //下面为三个函数指针,调用它们指向的函数就可以完成数据包的发送或接收   err_t (* input)(struct pbuf *p, struct netif *inp); //该函数向 IP 层输入数据包   err_t (* output)(struct netif *netif, struct pbuf *p,struct ip_addr *ipaddr); //该函数发送 IP 包   err_t (* linkoutput)(struct netif *netif, struct pbuf *p); //该函数实现底层数据包发送   void *state; //该字段用户可以自由设置,例如用于指向一些底层设备相关的信息   u16_t mtu; //该接口允许的最大数据包长度   u8_t hwaddr_len; //该接口物理地址长度   u8_t hwaddr[NETIF_MAX_HWADDR_LEN]; //该接口的物理地址   u8_t flags; //该接口的状态、属性字段   char name[2]; //该接口的名字   u8_t num; //接口的编号   //在接口自输入使能或者有环回接口的情况下,下面的字段   //用于描述接口发送给自己的数据包   struct pbuf *loop_first; //指向发送给自己的数据包的第一个 pbuf   struct pbuf *loop_last; //指向发送给自己的数据包的最后一个 pbuf };

  next 字段是指向下一个 netif 结构的指针,前面也说过,一个设备可能会有多个网络接口(例如路由器),LwIP 会把所有网络接口的 netif 结构组成一个链表进行管理,有一个名为 netif_list 的全局变量指向该链表的首部。next 字段就是用于组成链表时用。   

  ip_addr、netmask、gw 三个字段用于描述该网络接口的网络地址属性,依次称它们为接口 IP地址、子网掩码和网关地址。IP 地址和网络接口必须一一对应,即设备有几个硬件网络接口,它就得有几个 IP 地址;子网掩码可以用来判断某个目的 IP 地址与当前网络接口 IP 地址是否处于同一子网中,IP 层会选择与目的 IP 处于同一子网的网络接口来发送数据包。gw 字段在数据包的发送、转发过程中也有重要作用,如果目的 IP 地址与所有网络接口都不属于同一子网,LwIP 将会把数据包发送到网关处,因为它认为网关设备会对该 IP 包进行正确的转发,此外网关也为设备提供了许多高级服务,如 DHCP、DNS 等。   

  input 字段指向一个函数,这个函数将网络设备接收到的数据包提交给 IP 层(在以太网中,通常这个函数需要解析以太网数据帧,然后将从中得到的 IP 数据包递交)。被指向的函数具有两个参数:一个是 pbuf 类型,代表将要递交的数据包;另一个为 netif 类型,代表递交数据包的网络设备,函数返回值是 err_t 类型。网络设备初始化时应该向 input 字段注册相应的输入函数,后面将详细讨论这个问题。     

  output 字段指向一个函数,这个函数和具体网络接口设备驱动密切相关,它用于将 IP 层数据包发送到目的地址处。在 IP 层看来,每个网络接口都会提供一个这样的函数供它调用,当 IP 层发送数据包时,它会遍历 netif_list 链表,找出最合适的网络接口,并调用其注册的 output 函数发送 数据包。不同的网络接口需要根据实际接口特性来编写相关的发送函数,并在初始化时将 output字段指向该函数。就以太网网卡的数据包发送而言,LwIP 在源代码 etharp.c 文件中实现了一个名为 etharp_output 的函数,该函数就可以直接完成 IP 数据包在以太网中的发送,所以在初始化以太网卡结构时,可以直接将 output 字段指向函数 etharp_output。output 指向函数的三个参数分别是 pbuf类型、netif 类型和 ip_addr 类型,返回值是 err_t 类型。其中 pbuf 代表要发送的 IP 数据包,ipaddr代表数据包的目的 IP 地址

   linkoutput 字段和上面的 output 基本上是相似的功能,但是更底层一些,这个函数主要在以太网卡通信中被 ARP 模块调用,用来完成以太网数据帧的发送,在其他类型的网络接口中,这个字段的值就没啥用处了。上面说的函数 etharp_output,它一方面接收 IP 层数据包,另一方面将该数据包封装为以太网数据帧(填写物理地址等字段),最后便调用 linkoutput 字段注册的函数将以太网 数 据 帧 发 送 出 去 。 

  为了实现对这些数据包的管理,在网络接口结构 netif 中定义了两个指针 loop_first 和loop_last,它们分别用于指向数据包 pbuf 链表的第一个 pbuf 和最后一个 pbuf。这里的 pbuf 链表比较特殊,因为所有数据包的 pbuf 都组织在同一条链表上,这就说明一条 pbuf 链表上可能存在多个 数据包,这时 pbuf 结构的 next 字段是否为空并不能成为判断一个数据包结束与否的标志,那在这样的一条 pbuf 链表中,怎样去将各个数据包的 pbuf 划分开呢?答案在于 pbuf 结构中的 tot_len 字段和 len 字段值,当两个字段的值相等时,就代表一个数据包的结束。 

2、函数实现  

  向系统注册一个网络接口设备的函数 netif_add:

————netif.c————————————————
//定义两个全局型的 netif 指针
struct netif *netif_list; //系统的全局型 netif 链表 struct netif *netif_default; //记录系统缺省(默认)网络接口 //函数功能:向 LwIP 内核注册一个网络接口结构 //参数 netif:指向一个已分配好的 netif 结构体 //参数 ipaddr:网络接口的 IP 地址 //参数 netmask:网络接口子网掩码 //参数 gw:网关地址 //参数 state:用户自定义的一些数据信息 //参数 init:网络接口的初始化函数 //参数 input:网络接口向 IP 层提交数据包的函数 //返回值:成功注册的网络接口结构指针 struct netif *netif_add(struct netif *netif, struct ip_addr *ipaddr, struct ip_addr *netmask, struct ip_addr *gw, void *state, err_t (* init)(struct netif *netif), err_t (* input)(struct pbuf *p, struct netif *netif)) {   static u8_t netifnum = //定义静态变量 netifnum,它记录网络接口的编号   //清空 netif 结构的各个字段值   netif­>ip_addr.addr = 0;   netif­>netmask.addr = 0;   netif­>gw.addr = 0;   netif­>flags = 0;   netif­>loop_first = NULL;   netif­>loop_last = NULL;   //填写结构体各个字段值   netif­>state = state;   netif­>num = netifnum++; //网络接口编号   netif­>input = input; //注册 input 函数   //调用函数 netif_set_addr 设置网络接口的三个地址字段值   netif_set_addr(netif, ipaddr, netmask, gw);   //调用网络接口的初始化函数,初始化网络接口   if (init(netif) != ERR_OK)
  {
    //初始化失败,则返回空     return NULL;   }   //将初始化成功的 netif 结构加入 netif_list 链表   netif­>next = netif_list;   netif_list = netif;   //返回 netif 结构指针   return netif; }

  netif_add 函数只是简单的初始化了 netif 结构的几个字段,然后回调网络接口定义的初始化函数 init 来完成网络接口的初始化工作。 

IP4_ADDR(&gw, 192,168,1,1); //初始化三个地址 IP4_ADDR(&ipaddr, 192,168,1,37); IP4_ADDR(&netmask, 255,255,255,0//调用 netif_add 函数 netif_add(&rtl8019_netif, &ipaddr, &netmask, &gw, NULL, ethernetif_init,ethernet_input); netif_set_default(&rtl8019_netif); //设置系统的默认网络接口为 rtl8019_netif netif_set_up(&rtl8019_netif); //使能网络接口 rtl8019_netif

  在上面这段代码中,调用 netif_add 函数时,传递给它的两个函数参数是 ethernetif_init 和ethernet_input,其中前者就是网卡初始化函数 ethernetif_init,这是源码提供者为以太网网卡驱动程序编写的默认初始化函数,这个函数在第 5 章中也有所提及,这里来看看它的具体实现,源代码如下所示。ethernet_input 是 ARP 层的一个函数,它的功能是提取以太网帧中的 ARP 地址数据,并将帧中的 IP 数据递交给 IP 层,关于这个函数在下一章中会讲解。 

#define IFNAME0 'e' //定义以太网网卡的名字字符
#define IFNAME1 'n'
//定义描述网卡用户信息的结构,该结构无实际用处,源码作者旨在用该结构来示意 //netif 结构中的 state 字段的用法,该字段可以指向任何用户关心的信息 struct ethernetif 
{ //该结构只包含一个简单的指针   struct eth_addr *ethaddr; }; //函数参数 netif:网络接口结构的指针 err_t ethernetif_init(struct netif *netif) {   struct ethernetif *ethernetif;   ethernetif = mem_malloc(sizeof(struct ethernetif)); //为用户信息结构申请一个内存堆空间   if (ethernetif == NULL) { //申请失败,返回内存错误     return ERR_MEM;    }   netif­>state = ethernetif; //将 state 字段指向 ethernetif   netif­>name[0] = IFNAME0; //设置名字字段   netif­>name[1] = IFNAME1;   netif­>output = etharp_output; //注册 IP 数据包输出函数,这里使用 ARP 的相关函数   netif­>linkoutput = low_level_output; //注册以太网数据帧输出函数   //将 ethernetif 结构赋一个用户关心的值,这里无具体意义   ethernetif­>ethaddr = (struct eth_addr *)&(netif­>hwaddr[0]); //指向网卡的地址信息   low_level_init(netif); //调用网卡底层初始化函数,这个函数已经讲过了   return ERR_OK; //返回成功 }

猜你喜欢

转载自www.cnblogs.com/xxs71/p/9138957.html