嵌入式LwIP学习笔记之网络接口管理1

一、引言

  LWIP是基于TCP/IP协议开发,共分为四个层次:链路层、网络层、传输层和应用层。运行LWIP的嵌入式设备可以有多个不同种类的网络接口:以太网接口、串行链路接口、环回接口等。协议栈内部使用了一个名为netif的网络接口结构来实现对所有网络接口的有效管理。本次的学习内容有:

  1、网络接口管理的作用;

2、网络接口结构netif;

      网络接口管理中还有一个重要概念:环回接口的概念及作用留作下回学习总结。

二、网络接口管理                   

协议栈能与外部进行通信的关键是对网络接口的有效管理。网络接口的管理作为链路层的一部分,对具体的网络硬件、软件进行统一的封装,并为协议栈上层(IP层)提供统一的接口服务。为实现对网络接口的有效管理,LwIP为每个接口都分配一个netif结构,用来描述每种接口的特性,如接口IP地址,接口状态,同时为每个接口注册对应的操作函数。

2.1、数据结构

  源文件中 netif.c 和 netif.h 文件实现了与网络接口结构管理相关的所有数据结构和函数。来看看结构 netif 是怎样被定义的,如下代码所示(以下已经去掉了官方的英文注释):

————netif.h————————————————

//网络接口最大物理地址长度,这里定义为以太网网卡 MAC 地址的长度 6

#defineNETIF_MAX_HWADDR_LEN 6U

//下面几个宏为网络接口属性、状态相关的宏,主要用于描述netif 中 flags 字段的各位

#defineNETIF_FLAG_UP 0x01U                //网络接口是否已被上层使能

#defineNETIF_FLAG_BROADCAST 0x02U         //网络接口是否支持广播

#define NETIF_FLAG_POINTTOPOINT 0x04U //网络接口是否属于点到点连接

#defineNETIF_FLAG_DHCP 0x08U              //网络接口是否支持 DHCP 功能

#defineNETIF_FLAG_LINK_UP 0x10U  //网络接口的底层链路是否已使能

#defineNETIF_FLAG_ETHARP 0x20U   //网络接口是否支持 ARP 功能

#defineNETIF_FLAG_IGMP 0x40U              //网络接口是否支持 IGMP 功能

//下面是结构 netif 的定义

structnetif

{

  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); //该函数实现底层数据包发送

    netif_status_callback_fn status_callback;//当前链路netif up或者down时调用此函数

    netif_status_callback_fn remove_callback;//删除数据链路netif

void *state; //该字段用户可以自由设置,例如用于指向一些底层设备相关的信息

    struct dhcp  *dhcp; //使用DHCP客户端功能时,定义该结构

struct

autoip *autoip;//为netif分配一个自动IP匹配的内存区

char

*hostname; //定义主机名

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字段指向该函数。output 指向函数的三个参数分别是 pbuf类型、netif 类型和 ip_addr 类型,返回值是 err_t 类型。其中 pbuf 代表要发送的 IP 数据包,ipaddr代表数据包的目的 IP 地址

   linkoutput 字段指向的函数主要在以太网卡通信中被 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.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, structip_addr *netmask,

structip_addr *gw,

void*state,

err_t

(* init)(structnetif *netif),

err_t

(* input)(struct pbuf *p, structnetif *netif))

{

  static u8_t netifnum = 0; //定义静态变量 netifnum,它记录网络接口的编号

/* 初始化ip地址、子网掩码、mac地址*/

  ip_addr_set_zero(&netif->ip_addr);

 ip_addr_set_zero(&netif->netmask);

 ip_addr_set_zero(&netif->gw);

  netif->flags =0;

#if LWIP_DHCP

  /* netif not under DHCP control by default */

  netif->dhcp =NULL;

#endif /* LWIP_DHCP */

#if LWIP_AUTOIP

  /* netif not under AutoIP control by default*/

  netif->autoip = NULL;

#endif/* LWIP_AUTOIP */

#if LWIP_NETIF_STATUS_CALLBACK

  netif->status_callback = NULL;

#endif /* LWIP_NETIF_STATUS_CALLBACK */

#if LWIP_NETIF_LINK_CALLBACK

  netif->link_callback = NULL;

#endif /* LWIP_NETIF_LINK_CALLBACK */

#if LWIP_IGMP

  netif->igmp_mac_filter = NULL;

#endif/* LWIP_IGMP */

#if ENABLE_LOOPBACK

  netif->loop_first = NULL;

 netif->loop_last = NULL;

#endif /* ENABLE_LOOPBACK */

//填写结构体各个字段值

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,这是源码提供者为以太网网卡驱动程序编写的默认初始化函数。ethernet_input 是 ARP 层的一个函数,它的功能是提取以太网帧中的 ARP 地址数据,并将帧中的 IP 数据递交给 IP 层,ethernetif_init的源码实现如下所示。


#define IFNAME0 'e' //定义以太网网卡的名字字符

#defineIFNAME1 'n'

//定义描述网卡用户信息的结构

//netif结构中的 state 字段的用法,该字段可以指向任何用户关心的信息

structethernetif

{

//该结构只包含一个简单的指针

  struct eth_addr *ethaddr;

//函数参数 netif:网络接口结构的指针

err_t

ethernetif_init(structnetif *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; //返回成功

}

         上面的内容,网络设备相关的上层结构与底层硬件都初始化OK,此时协议栈可以正常使用网卡了。

你可能感兴趣的:(嵌入式LwIP学习笔记之网络接口管理1)