网络接口管理

     参照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协议深度剖析与实战演练》 朱升林编著

 

posted @ 2013-12-11 21:49  星寒  阅读(972)  评论(0编辑  收藏  举报