LwIP使用netif
来描述一个硬件网络接口,但是由于网络接口是直接与硬件打交道的,硬件不同则处理可能不同,必须由用户提供最底层接口。LwIP的网络驱动有一定的模型,/src/netif/ethernetif.c
文件即为底层接口的驱动的模版,用户为自己的网络设备实现驱动时应参照此模块。该文件中的函数通常为与硬件打交道的函数,当有数据接收的时候被调用,以使接收到的数据进入tcpip协议栈。
简单来说,netif
是LwIP抽象出来的各网络接口,协议栈可以使用多个不同的接口,而ethernetif
则提供了netif
访问硬件的各接口,每个不同的接口有不同的ethernetif
。
在LwIP中,是通过结构体netif
来描述一个硬件网络接口的,在单网卡中,这个结构体只有一个;多网卡中可有何网卡数目相同的netif
结构体,它们构成一个数据链。该部分的定义在文件netif.c/h
中,具体如下:
/** 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 {
/** pointer to next in linked list */
struct netif *next;
#if LWIP_IPV4
/** IP address configuration in network byte order */
ip_addr_t ip_addr; /* IP 地址 */
ip_addr_t netmask; /* 子网掩码 */
ip_addr_t gw; /* 网关 */
#endif /* LWIP_IPV4 */
#if LWIP_IPV6
/** Array of IPv6 addresses for this netif. */
ip_addr_t ip6_addr[LWIP_IPV6_NUM_ADDRESSES];
/** The state of each IPv6 address (Tentative, Preferred, etc).
* @see ip6_addr.h */
u8_t ip6_addr_state[LWIP_IPV6_NUM_ADDRESSES];
#endif /* LWIP_IPV6 */
/** This function is called by the network device driver
* to pass a packet up the TCP/IP stack. */
netif_input_fn input; /* 函数指针,从网卡中接收一包数据 */
#if LWIP_IPV4
/** 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.
* For ethernet physical layer, this is usually etharp_output() */
netif_output_fn output; /* IP层调用此函数向网卡发送一包数据 */
#endif /* LWIP_IPV4 */
/** This function is called by ethernet_output() 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; /* ARP模块调用这个函数向网卡发送一包数据 */
#if LWIP_IPV6
/** This function is called by the IPv6 module when it wants
* to send a packet on the interface. This function typically
* first resolves the hardware address, then sends the packet.
* For ethernet physical layer, this is usually ethip6_output() */
netif_output_ip6_fn output_ip6;
#endif /* LWIP_IPV6 */
#if LWIP_NETIF_STATUS_CALLBACK
/** This function is called when the netif state is set to up or down
*/
netif_status_callback_fn status_callback;
#endif /* LWIP_NETIF_STATUS_CALLBACK */
#if LWIP_NETIF_LINK_CALLBACK
/** This function is called when the netif link is set to up or down
*/
netif_status_callback_fn link_callback;
#endif /* LWIP_NETIF_LINK_CALLBACK */
#if LWIP_NETIF_REMOVE_CALLBACK
/** This function is called when the netif has been removed */
netif_status_callback_fn remove_callback;
#endif /* LWIP_NETIF_REMOVE_CALLBACK */
/** This field can be set by the device driver and could point
* to state information for the device. */
void *state; /* 这个成员变量注意以下,主要是将 网卡的某些私有数据传递给上层,用户可以自由发挥,也可以不用。下面会有详细说明。*/
#ifdef netif_get_client_data
void* client_data[LWIP_NETIF_CLIENT_DATA_INDEX_MAX + LWIP_NUM_NETIF_CLIENT_DATA];
#endif
#if LWIP_IPV6_AUTOCONFIG
/** is this netif enabled for IPv6 autoconfiguration */
u8_t ip6_autoconfig_enabled;
#endif /* LWIP_IPV6_AUTOCONFIG */
#if LWIP_IPV6_SEND_ROUTER_SOLICIT
/** Number of Router Solicitation messages that remain to be sent. */
u8_t rs_count;
#endif /* LWIP_IPV6_SEND_ROUTER_SOLICIT */
#if LWIP_NETIF_HOSTNAME
/* the hostname for this netif, NULL is a valid value */
const char* hostname;
#endif /* LWIP_NETIF_HOSTNAME */
#if LWIP_CHECKSUM_CTRL_PER_NETIF
u16_t chksum_flags;
#endif /* LWIP_CHECKSUM_CTRL_PER_NETIF*/
/** maximum transfer unit (in bytes) */
u16_t mtu; /* 网络一次可以传送的最大字节数,对于以太网一般设为 1500 */
/** number of bytes used in hwaddr */
u8_t hwaddr_len; /* 硬件地址长度,对于以太网就是 MAC 地址长度,为 6 各字节 */
/** link level hardware address of this interface */
u8_t hwaddr[NETIF_MAX_HWADDR_LEN];
/** flags (@see @ref netif_flags) */
u8_t flags; /* 网卡状态信息标志位,是很重要的控制字段,它包括网卡功能使能、广播使能、 ARP 使能等等重要控制位。*/
/** descriptive abbreviation */
char name[2]; /* 字段用于保存每一个网络网络接口的名字。用两个字符的名字来标识网络接口使用的设备驱动的种类,名字由设备驱动来设置并且应该反映通过网络接口表示的硬件的种类。
比如蓝牙设备( bluetooth)的网络接口名字可以是 bt,而 IEEE 802.11b WLAN 设备的名字就可以是 wl,当然设置什么名字用户是可以自由发挥的,这并不影响用户对网络接口的使用。
当然,如果两个网络接口具有相同的网络名字,我们就用 num 字段来区分相同类别的不同网络接口。 */
/** number of this interface */
u8_t num; /* 用来标示使用同种驱动类型的不同网络接口 */
#if MIB2_STATS
/** link type (from "snmp_ifType" enum from snmp_mib2.h) */
u8_t link_type;
/** (estimate) link speed */
u32_t link_speed;
/** timestamp at last change made (up/down) */
u32_t ts;
/** counters */
struct stats_mib2_netif_ctrs mib2_counters;
#endif /* MIB2_STATS */
#if LWIP_IPV4 && LWIP_IGMP
/** This function could be called to add or delete an entry in the multicast
filter table of the ethernet MAC.*/
netif_igmp_mac_filter_fn igmp_mac_filter;
#endif /* LWIP_IPV4 && LWIP_IGMP */
#if LWIP_IPV6 && LWIP_IPV6_MLD
/** This function could be called to add or delete an entry in the IPv6 multicast
filter table of the ethernet MAC. */
netif_mld_mac_filter_fn mld_mac_filter;
#endif /* LWIP_IPV6 && LWIP_IPV6_MLD */
#if LWIP_NETIF_HWADDRHINT
u8_t *addr_hint;
#endif /* LWIP_NETIF_HWADDRHINT */
#if ENABLE_LOOPBACK
/* List of packets to be queued for ourselves. */
struct pbuf *loop_first;
struct pbuf *loop_last;
#if LWIP_LOOPBACK_MAX_PBUFS
u16_t loop_cnt_current;
#endif /* LWIP_LOOPBACK_MAX_PBUFS */
#endif /* ENABLE_LOOPBACK */
};
LwIP使用链表(单向链表)来管理多个网络接口。在netif.c
中有如下全局变量struct netif *netif_list;和struct netif *netif_default;
,其中前者就是网络接口链表指针,后者表示默认情况下(有多网口时)使用哪个网络接口。
netif.c
中第一部分就是添加一个会换接口(即127.0.0.1)。具体函数为void netif_init(void)
。该函数在LwIP初始化时被调用(tcpip_init -> lwip_init -> netif_init
)。
要使用LwIP必须调用
tcpip_init
完成对LwIP的初始化。
向网络接口链表中添加新接口必须通过以下函数
struct netif *
netif_add(struct netif *netif,
#if LWIP_IPV4
const ip4_addr_t *ipaddr, const ip4_addr_t *netmask, const ip4_addr_t *gw,
#endif /* LWIP_IPV4 */
void *state, netif_init_fn init, netif_input_fn input);
下面简单具体分析以下添加新接口:
网络接口链表的操作非常简单,新添加的接口在链表最前面!
从网络接口链表中删除指定的接口必须通过以下函数void netif_remove(struct netif *netif);
删除也非常简单,遍历链表找到指定的接口,删除!
那么具体该如何使用呢?其实使用还是非常简单的。首先我们需要针对我们的网络接口顶一个netif
结构体变量struct netif xnetif;
,接下来就是初始化。通过上面的结构不难发现,其中有非常多的函数指针,后续对于网络接口的操作,基本全是通过各个函数指针来实现的!通常,我们会在LwIP初始化中,用以下函数添加自己的网络接口:
/* 通过该函数,将网络接口添加到链表中 */
netif_add(&xnetif, &ipaddr, &netmask, &gw, NULL, ðernetif_init, &tcpip_input);
/* Registers the default network interface.*/
netif_set_default(&xnetif);
if (EthStatus == (ETH_INIT_FLAG | ETH_LINK_FLAG))
{
/* Set Ethernet link flag */
xnetif.flags |= NETIF_FLAG_LINK_UP;
/* When the netif is fully configured this function must be called.*/
netif_set_up(&xnetif);
}
else
{
/* When the netif link is down this function must be called.*/
netif_set_down(&xnetif);
}
上面说过,每个netif接口都需要一个底层接口文件提供访问硬件的支持。在LwIP提供的模板ethernetif.c
文件中,上来就是一个如下的结构体:
struct ethernetif {
struct eth_addr *ethaddr;
/* Add whatever per-interface state that is needed here. */
};
该结构用来描述底层硬件设备,该结构体唯一不可或缺的是MAC地址,它是LWIP用于相应ARP查询的核心数据。其他如果没有特殊需要,可以不添加其他成员数据。主要用于将底层硬件设备的一些私有数据通过netif ->state
传递给上层(在ethernetif_init
函数中赋值netif->state = ethernetif;
)。一般用不到改结构。因为在netif
结构中,基本包含了所有需要的信息。
ethernetif.c
文件中提供的函数主要有以下这么几个:
static void low_level_init(struct netif *netif);
static struct pbuf * low_level_input(struct netif *netif);
static err_t low_level_output(struct netif *netif, struct pbuf *p);
void ethernetif_input( void * pvParameters )- low_level_output;
err_t ethernetif_init(struct netif *netif);
其中对外的接口只有ethernetif_init
函数。
该函数完成对于网络接口的初始化工作。它是在对LwIP初始化函数中通过netif_add( &EMAC_if, &xIpAddr, &xNetMast, &xGateway, NULL, ethernetif_init, tcpip_input );
来被调用的。具体见以下注释:
err_t ethernetif_init(struct netif *netif)
{
LWIP_ASSERT("netif != NULL", (netif != NULL));
#if LWIP_NETIF_HOSTNAME
netif->hostname = "lwip"; /* Initialize interface hostname */
#endif /* LWIP_NETIF_HOSTNAME */
netif->name[0] = IFNAME0;
netif->name[1] = IFNAME1;
#if LWIP_IPV4
#if LWIP_ARP || LWIP_ETHERNET
#if LWIP_ARP
netif->output = etharp_output; /* 数据发送函数,其最终还是会调用 netif->linkoutput */
#else
/* The user should write ist own code in low_level_output_arp_off function */
netif->output = low_level_output_arp_off;
#endif /* LWIP_ARP */
#endif /* LWIP_ARP || LWIP_ETHERNET */
#endif /* LWIP_IPV4 */
#if LWIP_IPV6 //0
netif->output_ip6 = ethip6_output;
#endif /* LWIP_IPV6 */
netif->linkoutput = low_level_output; /* 这个才是最底层发送函数(将数据交给硬件发送)*/
/* initialize the hardware */
low_level_init(netif);
// etharp_init();
// sys_timeout(ARP_TMR_INTERVAL, arp_timer, NULL);
return ERR_OK;
}
该函数主要是对网卡进行一系列的初始化工作,例如:初始化MAC地址、建立接收数据的任务等。
该函数负责从网卡中接收数据。正常从LwIP的缓冲池中申请一个 pbuf结构,存放数据。
该函数即为网卡数据接收任务。LwIP中是通过一个task(ethernetif_input)轮询检查DMA控制器的状态以判断是否有数据接收到。接收的数据传输流程如下:
硬件接口取数 -> low_level_input -> tcpip_input(通过函数指针s_pxNetIf->input)
void ethernetif_input( void * pvParameters )
{
struct pbuf *p;
for( ;; )
{
/* 1. 该信号量会在网卡的中断中有数据时被发送 */
if (xSemaphoreTake( s_xSemaphore, emacBLOCK_TIME_WAITING_FOR_INPUT)==pdTRUE)
{
TRY_GET_NEXT_FRAME:
p = low_level_input( s_pxNetIf ); /* 2. 从网卡中取数据 */
if(p != NULL) /* 有数据 */
{
/* 3. 将数据(pbuf的地址)传递到 TCP/IP协议栈(s_pxNetIf->input在netif_add函数中赋值为函数tcpip_input ) */
if (ERR_OK != s_pxNetIf->input( p, s_pxNetIf))
{
pbuf_free(p);
}
else
{
goto TRY_GET_NEXT_FRAME; /* 4. 接收成功后,返回继续接收数据,直到网卡中没有数据了 */
}
}
/* 5. 如果没有数据了,则会再次返回第 1 步收信号量 */
}
}
}
Output过程是由应用程序以主动方式触发的。在TCP/IP协议栈中,要发送的发送的数据最终被传递给了ip_output_if函数
。查看代码可知,该函数中直接调用了netif->output函数
,在上面的ethernetif_init
函数中有对这个变量【函数指针】赋值,它就是etharp_output
。
要发送的数据的额传输流程如下:
ip_output_if -> etharp_output(通过函数指针netif->output)-> ethernet_output -> low_level_output(通过函数指针netif->linkoutput) -> 硬件设备