《LwIP协议栈源码详解——TCPIP协议的实现》⽹络接⼝
结构
我只是不想,将这份⼼动付诸⾔语。前⾯还有⼀句:信任他⼈,并不意味着软弱。我只是假装对万物⼀⽆所知,好借此获得你所有的温柔。谢谢你所做的⼀切,现在⼀切⼜将重新开始。我只有将这份⽆法忘怀的思念送给你。
⼈们总说”⿊夜会过去”,但那只是善意的谎⾔。我想就算⼀个⼈,应该也能⽣存下去,因为你的笑容已经永远铭刻在我⼼中,还有那应该已经被我舍弃的信任别⼈的⼼。
以上内容系剽窃于某某美⼥的歌词。(ps:真的是歌词,与⼼情真⽆关,啊啊啊)
今天我们来讨论LWIP是怎样来处理与底层硬件,即⽹卡芯⽚间的关系的。为什么要⾸先讨论这个问题呢?与许多其他
的TCP/IP实现⼀样,LWIP也是以分层的协议为参照来设计实现TCP/IP的。LWIP从逻辑上看分为四层:链路层、⽹络层、传输层和应⽤层。注意,虽然LWIP也采⽤了分层机制,但它没有在各层之间进⾏严格的划分,各层协议之间可以进⾏或多或少的交叉存取,即上层可以意识到下层协议所使⽤的缓存处理机制。因此各层可以更有效地重⽤缓冲区。⽽且,应⽤进程和协议栈代码可以使⽤相同的内存,应⽤
可以直接读写内部缓存,因此节省了执⾏拷贝的开销。我们将从LWIP的最底层链路层起步,开始整个LWIP内部协议之旅。
在LWIP中,是通过⼀个叫做netif的⽹络结构体来描述⼀个硬件⽹络接⼝的。这个接⼝结构⽐较简单,下⾯我们从源代码结构来分析分析这个结构:
struct netif {
struct netif *next; // 指向下⼀个netif结构的指针
struct ip_addr ip_addr; // IP地址相关配置
struct ip_addr netmask;
struct ip_addr gw;
err_t (* input)(struct pbuf *p, struct netif *inp); //调⽤这个函数可以从⽹卡上取得⼀个
// 数据包
err_t (* output)(struct netif *netif, struct pbuf *p, // IP层调⽤这个函数可以向⽹卡发送
struct ip_addr *ipaddr); // ⼀个数据包
err_t (* linkoutput)(struct netif *netif, struct pbuf *p); // ARP模块调⽤这个函数向⽹
// 卡发送⼀个数据包
void *state; // ⽤户可以独⽴发挥该指针,⽤于指向⽤户关⼼的⽹卡信息
u8_t hwaddr_len; // 硬件地址长度,对于以太⽹就是MAC地址长度,为6各字节
u8_t hwaddr[NETIF_MAX_HWADDR_LEN]; //MAC地址
u16_t mtu; // ⼀次可以传送的最⼤字节数,对于以太⽹⼀般设为1500
u8_t flags; // ⽹卡状态信息标志位
char name[2]; // ⽹络接⼝使⽤的设备驱动类型的种类
u8_t num; // ⽤来标⽰使⽤同种驱动类型的不同⽹络接⼝
};
next字段是指向下⼀个netif结构的指针。我们的⼀个产品可能会有多个⽹卡芯⽚,LWIP会把所有⽹卡芯⽚的结构体链成
⼀个链表进⾏管理,有⼀个netif_list的全局变量指向该链表的头部。next字段就是⽤于链表⽤。
ip_addr、netmask、gw三个字段⽤于发送和处理数据包⽤,分别表⽰IP地址、⼦⽹掩码和⽹关地址。前两个字段在数据包发送时有重要作⽤,第三个字段似乎没什么⽤。IP地址和⽹卡设备必须⼀⼀对应。如果你连什么叫IP地址、⼦⽹掩码和它们的作⽤都不晓得,那你有必要去看看TCP/IP协议详解卷1第三章。
input字段指向⼀个函数,这个函数将⽹卡设备接收到的数据包提交给IP层,使⽤时将input指针指向该函数即可,后⾯将详细讨论这个问题。该函数的两个参数是pbuf类型和netif类型的,返回参数是err_t类型。其中pbuf代表接收到的数据包。
output字段向⼀个函数,这个函数和具体⽹络接⼝设备驱动密切相关,它⽤于IP层将⼀个数据包发送到⽹络接⼝上。⽤户需要根据实际⽹卡编写该函数,并将output字段指向该函数。该函数的三个参数是pbuf类型、netif类型和ip_addr类型,返回参数是err_t类型。其中pbuf代表要发送的数据包。ipaddr代表⽹卡需要将该数据包发送到的地址,该地址应该是接收实际的链路层帧的主机的 IP 地址,⽽不⼀定为数据包最终需要到达的IP地址。例如,当要发送 IP信息包到⼀个并不在本地⽹络⾥的主机上时,
链路层帧会被发送到⽹络⾥的⼀个路由器上。在这种情况下,给 output 函数的 IP地址将是这个路由器的地址。
linkoutput字段和上⾯的output基本上是起相同的作⽤,但是这个函数是在ARP模块中被调⽤的,这⾥不赘述了。注意这个函数只有两个参数。实际上output字段函数的实现最终还是调⽤linkoutput字段函数将数据包发送出去的。
state字段可以指向⽤户关⼼的关于设备的⼀些信息,⽤户可以⾃由发挥,也可以不⽤。hwaddr_len和hwaddr[]表
⽰MAC地址长度和MAC地址,⼀般MAC地址长度为6。
mtu字段表⽰该⽹络⼀次可以传送的最⼤字节数,对于以太⽹⼀般设为1500,不多说。
flags字段是⽹卡状态信息标志位,是很重要的控制字段,它包括⽹卡功能使能、⼴播使能、ARP使能等等重要控制位。
name[]字段⽤于保存每⼀个⽹络⽹络接⼝的名字。⽤两个字符的名字来标识⽹络接⼝使⽤的设备驱动的种类,名字由设备驱动来设置并且应该反映通过⽹络接⼝表⽰的硬件的种类。⽐如蓝⽛设备(bluetooth)的⽹络接⼝名字可以是 bt,⽽ IEEE 802.11b WLAN设备的名字就可以是 wl,当然设置
什么名字⽤户是可以⾃由发挥的,这并不影响⽤户对⽹络接⼝的使⽤。当然,如果两个⽹络接⼝具有相同的⽹络名字,我们就⽤num字段来区分相同类别的不同⽹络接⼝。
到这⾥,你可能⼀头雾⽔,太抽象的东西太容易让⼈纠结。好吧,我们举个例⼦来看看⼀个以太⽹⽹卡接⼝结构是这样被初始化,还有数据包是如何接收和发送的。先来看初始化过程,源码:
static struct netif enc28j60; (1)
struct ip_addr ipaddr, netmask, gw; (2)
IP4_ADDR(&gw, 192,168,0,1); (3)
IP4_ADDR(&ipaddr, 192,168,0,60); (4)
IP4_ADDR(&netmask, 255,255,255,0); (5)
netif_init(); (6)
netif_add(&enc28j60, &ipaddr, &netmask, &gw, NULL, ethernetif_init, tcpip_input); (7)
netif_set_default(&enc28j60); (8)
netif_set_up(&enc28j60); (9)
上⾯的(1)声明了⼀个netif结构的变量enc28j60,由于在我的板⼦上使⽤的是⽹卡芯⽚enc28j60,所以我选择使⽤了这个名字。(2)声明了三个分别⽤于暂存IP地址、⼦⽹掩码和⽹关地址的变量,它们是32位长度的。(3)~ (5)分别是对上述三个地址值的初始化,该过程简单。
(6)很简单,它只需初始化上⾯所述的全局变量netif_list即可:netif_list = NULL。
(7)调⽤netif_add函数初始化变量enc28j60,其中⽐较重要的两个参数是ethernetif_init和tcpip_input,前者是⽤户⾃⼰定义的底层接⼝初始化函数,tcpip_input函数是向IP层递交数据包的函数,从前⾯的讲述中可以很明显的看出,该值会被传递
给enc28j60的input字段。再来看看源码:
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 = 0;
netif->ip_addr.addr = 0; //复位变量enc28j60中各字段的值
netif->netmask.addr = 0;
netif->gw.addr = 0;
netif->flags = 0; //该⽹卡不允许任何功能使能
netif->state = state; //指向⽤户关⼼的信息,这⾥为NULL
netif->num = netifnum++; //设置num字段,
netif->input = input; //如前所诉,input函数被赋值
netif_set_addr(netif, ipaddr, netmask, gw); //设置变量enc28j60的三个地址
tcpip协议pdfif (init(netif) != ERR_OK) { //⽤户⾃⼰的底层接⼝初始化函数
return NULL;
}
netif->next = netif_list; //将初始化后的节点插⼊链表netif_list
netif_list = netif; // netif_list指向链表头
return netif;
}
上⾯的初始化函数调⽤了⽤户⾃⼰定义的底层接⼝初始化函数,这⾥为ethernetif_init,再来看看它的源代码:err_t ethernetif_init(struct netif *netif)
{
netif->name[0] = IFNAME0; //初始化变量enc28j60的name字段
netif->name[1] = IFNAME1; // IFNAME在⽂件外定义的,这⾥不必关⼼它的具体值
netif->output = etharp_output; //IP层发送数据包函数
netif->linkoutput = low_level_output; // //ARP模块发送数据包函数
low_level_init(netif); //底层硬件初始化函数
return ERR_OK;
}
天,还有函数调⽤!low_level_init函数就是与我们使⽤的硬件密切相关的函数了。啊啊啊啊啊啊啊,没写完,明天再来吧!!
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论