姓名:朱小鵬 ? ?學(xué)號(hào):16010130023
轉(zhuǎn)載:
http://blog.sina.com.cn/s/blog_62a85b950101am9n.html
【嵌牛導(dǎo)讀】:low_level_init函數(shù)是與我們使用的與硬件密切相關(guān)初始化函數(shù)
【嵌牛鼻子】:以太網(wǎng)數(shù)據(jù)接收
【嵌牛提問(wèn)】:LWIP是怎樣來(lái)處理以太網(wǎng)數(shù)據(jù)接收?
【嵌牛正文】:
昨天說(shuō)到low_level_init函數(shù)是與我們使用的與硬件密切相關(guān)初始化函數(shù),看看:
static void low_level_init(struct netif *netif)
{
netif->hwaddr_len = ETHARP_HWADDR_LEN; //設(shè)置變量enc28j60的hwaddr_len字段
netif->hwaddr[0] = 'F';//初始化變量enc28j60的MAC地址
netif->hwaddr[1] = 'O';//設(shè)什么地址用戶自由發(fā)揮吧,但是不要與其他
netif->hwaddr[2] = 'R';//網(wǎng)絡(luò)設(shè)備的MAC地址重復(fù)。
netif->hwaddr[3] = 'E';
netif->hwaddr[4] = 'S';
netif->hwaddr[5] = 'T';
netif->mtu = 1500;//最大允許傳輸單元
//允許該網(wǎng)卡廣播和ARP功能,并且該網(wǎng)卡允許有硬件鏈路連接
netif->flags= NETIF_FLAG_BROADCAST |\
NETIF_FLAG_ETHARP | NETIF_FLAG_LINK_UP;
enc28j60_init(netif->hwaddr);//與底層驅(qū)動(dòng)硬件驅(qū)動(dòng)程序密切相關(guān)的硬件初始化函數(shù)
}
至此,終于變量enc28j60被初始化好了,而且它描述的網(wǎng)卡芯片enc28j60也被初始化好了,而且變量enc28j60也被鏈入鏈表netif_list。
接著上上上上面的語(yǔ)句(8)調(diào)用netif_set_default函數(shù)初始化缺省網(wǎng)絡(luò)接口。協(xié)議棧除了有個(gè)netif_list全局變量指向netif網(wǎng)絡(luò)接口結(jié)構(gòu)的鏈表,還有個(gè)全局變量netif_default全局變量指向缺省的網(wǎng)絡(luò)接口結(jié)構(gòu)。當(dāng)IP層有數(shù)據(jù)發(fā)送時(shí),它首先會(huì)以netif_list為索引選擇滿足某個(gè)條件的網(wǎng)絡(luò)接口發(fā)送數(shù)據(jù)包,但是,當(dāng)找不到這樣的接口時(shí),協(xié)議棧就會(huì)調(diào)用缺省的網(wǎng)絡(luò)接口直接發(fā)送數(shù)據(jù)包,所以(8)中的意思是把變量enc28j60描述的網(wǎng)絡(luò)接口設(shè)置為缺省的網(wǎng)絡(luò)接口。
(9)調(diào)用函數(shù)netif_set_up使能網(wǎng)絡(luò)接口,這通過(guò)一個(gè)簡(jiǎn)單語(yǔ)句來(lái)實(shí)現(xiàn):
netif->flags |= NETIF_FLAG_UP;
至此,網(wǎng)卡初始化完成,能正常接收和發(fā)送數(shù)據(jù)包了。下面我們來(lái)討論討論關(guān)于網(wǎng)卡數(shù)據(jù)包的接收和發(fā)送。
LWIP中實(shí)現(xiàn)了接收一個(gè)數(shù)據(jù)包和發(fā)送一個(gè)數(shù)據(jù)包函數(shù)的框架,這兩個(gè)函數(shù)分別是low_level_input和low_level_output,用戶需要使用實(shí)際網(wǎng)卡驅(qū)動(dòng)程序完成這兩個(gè)函數(shù)。在第一篇中講過(guò),一個(gè)典型的LWIP應(yīng)用系統(tǒng)包括這樣的三個(gè)進(jìn)程:首先是上層應(yīng)用程序進(jìn)程,然后是LWIP協(xié)議棧進(jìn)程,最后是底層硬件數(shù)據(jù)包接收進(jìn)程。這里我們就來(lái)講講第三個(gè)進(jìn)程,看看數(shù)據(jù)包是怎樣被接收并往上層傳遞的。但在這之前,有必要說(shuō)說(shuō)以太網(wǎng)網(wǎng)卡所收到的數(shù)據(jù)包的格式。如下圖,
LWIP使用了一個(gè)eth_hdr的數(shù)據(jù)結(jié)構(gòu)來(lái)描述以太網(wǎng)數(shù)據(jù)包包頭的14個(gè)字節(jié)。如下,
PACK_STRUCT_BEGIN
struct eth_hdr {
PACK_STRUCT_FIELD(struct eth_addr dest);//目標(biāo)MAC地址
PACK_STRUCT_FIELD(struct eth_addr src);//源MAC地址
PACK_STRUCT_FIELD(u16_t type);//類型
} PACK_STRUCT_STRUCT;
PACK_STRUCT_END
其中PACK_STRUCT_xxx都是與編譯器字對(duì)齊相關(guān)的宏定義,這里不作詳細(xì)介紹了。上面的dest、src和type三個(gè)字段分別和上圖中的目的MAC地址、源MAC地址和類型域字段對(duì)應(yīng)。
在上面討論的基礎(chǔ)上,我們來(lái)看看這個(gè)數(shù)據(jù)包接收進(jìn)程,源代碼如下:
voidethernetif_input(void *arg)//創(chuàng)建該進(jìn)程時(shí),要將某個(gè)網(wǎng)絡(luò)接口結(jié)構(gòu)的netif結(jié)構(gòu)指
{//針作為參數(shù)傳入
struct eth_hdr *ethhdr;
struct pbuf *p;
struct netif *netif = (struct netif *)arg;
while (1)
{
p = low_level_input (netif);//接收一個(gè)數(shù)據(jù)包
if (p == NULL)//如果數(shù)據(jù)包為空,
continue;//則循環(huán)結(jié)束,啟動(dòng)下次接收過(guò)程
ethhdr = p->payload;//取得數(shù)據(jù)包內(nèi)數(shù)據(jù)
switch (htons(ethhdr->type))//判斷數(shù)據(jù)包類型
{//只對(duì)IP數(shù)據(jù)包和ARP數(shù)據(jù)包進(jìn)行處理
case ETHTYPE_IP://IP數(shù)據(jù)包
case ETHTYPE_ARP://ARP數(shù)據(jù)包
if (netif->input(p, netif)!=ERR_OK)//將數(shù)據(jù)包發(fā)送到上層應(yīng)用函數(shù)
{
pbuf_free(p);
p = NULL;
}
break;
default:
pbuf_free(p);
p = NULL;
break;
}//switch
}//while
}//main函數(shù)
要?jiǎng)?chuàng)建上面的這個(gè)進(jìn)程,需要把個(gè)網(wǎng)絡(luò)接口結(jié)構(gòu)的netif結(jié)構(gòu)指針作為參數(shù)傳入,在UC/OSII中要用到下面的語(yǔ)句實(shí)現(xiàn),
OSTaskCreate(ethernetif_input,(void *)&enc28j60,
&T_ETHERNETIF_INPUT_STK[T_ETHERNETIF_INPUT_STKSIZE-1]
ETH_IF_TASK_PRIO);
在數(shù)據(jù)包接收進(jìn)程中,有三個(gè)需要注意的地方。一是數(shù)據(jù)包接收的方法是查詢方式,即處理器不斷向網(wǎng)卡芯片中讀取數(shù)據(jù),如果讀不到數(shù)據(jù),則控制器會(huì)重新啟動(dòng)一個(gè)讀取時(shí)序;如果能夠成功讀取到數(shù)據(jù),則將數(shù)據(jù)通過(guò)網(wǎng)卡注冊(cè)的input函數(shù)交往上層進(jìn)行處理。使用查詢方式實(shí)現(xiàn)的數(shù)據(jù)包接收進(jìn)程其優(yōu)先級(jí)必須低于系統(tǒng)中其他進(jìn)程的優(yōu)先級(jí),否則它會(huì)阻塞比它優(yōu)先級(jí)低的進(jìn)程的運(yùn)行。上面的程序有個(gè)可以改進(jìn)的地方,即在讀取到的數(shù)據(jù)包為空時(shí),接收進(jìn)程調(diào)用系統(tǒng)函數(shù)將自己延時(shí)一段時(shí)間再啟動(dòng)下一個(gè)讀取過(guò)程,這樣可以使其不能阻止優(yōu)先級(jí)更低的進(jìn)程的運(yùn)行,缺點(diǎn)是數(shù)據(jù)包的接收得不到及時(shí)的響應(yīng)。其實(shí)數(shù)據(jù)包的接收可以采用中斷的方式來(lái)實(shí)現(xiàn),這種方式是一種比較好的方式。一般的網(wǎng)卡芯片都有中斷功能,即當(dāng)網(wǎng)卡接收到一個(gè)數(shù)據(jù)包后,它可以產(chǎn)生中斷信號(hào)告訴控制器自己接收到一個(gè)數(shù)據(jù)包??刂破鞔藭r(shí)啟動(dòng)一個(gè)讀取數(shù)據(jù)包時(shí)序,就能有效的讀取到非空數(shù)據(jù)包。所以可以這樣來(lái)實(shí)現(xiàn)一個(gè)接收數(shù)據(jù)包進(jìn)程:在無(wú)數(shù)據(jù)包收到時(shí),數(shù)據(jù)包接收進(jìn)程阻塞在一個(gè)信號(hào)量下,當(dāng)有數(shù)據(jù)包到來(lái)時(shí),網(wǎng)卡芯片產(chǎn)生一個(gè)中斷信號(hào),處理器進(jìn)入中斷處理,并釋放一個(gè)信號(hào)量。中斷退出后,數(shù)據(jù)包接收進(jìn)程得到信號(hào)量,并從網(wǎng)卡芯片中讀取數(shù)據(jù)包,并將數(shù)據(jù)包遞交給上層進(jìn)行處理。
第二個(gè)需要注意的地方是htons(ethhdr->type)函數(shù)的使用,htons函數(shù)的功能是將一個(gè)半字長(zhǎng)的數(shù)據(jù)從網(wǎng)絡(luò)字節(jié)順序轉(zhuǎn)換到我們的處理器支持的字節(jié)順序。解釋一下,在計(jì)算機(jī)體系結(jié)構(gòu)和計(jì)算機(jī)通信領(lǐng)域中,對(duì)于半字、字等的存儲(chǔ)機(jī)制有可能不同。目前通常采用的存儲(chǔ)機(jī)制主要有兩種:big-endian和little-endian,即大端和小端。對(duì)于大端模式,某個(gè)半字或字?jǐn)?shù)據(jù)的高位字節(jié)被在內(nèi)存的低地址端,低位字節(jié)排放在內(nèi)存的高地址端。對(duì)于小端模式,則恰好相反。由于我們使用的ARM處理器使用的是小端模式,而接收到的網(wǎng)絡(luò)字節(jié)數(shù)據(jù)用的是大端模式,所以這里調(diào)用函數(shù)htons實(shí)現(xiàn)大端與小端的轉(zhuǎn)換,實(shí)際就是將兩個(gè)字節(jié)交換順序即可。這樣調(diào)用htons(ethhdr->type)后,ethhdr->type的值就為0x0800或0x0806等。
最后需要注意的地方,netif->input在結(jié)構(gòu)enc28j60初始化時(shí)已經(jīng)被設(shè)置為指向tcpip_input函數(shù),所以實(shí)際上上面是調(diào)用tcpip_input函數(shù)往上層遞交數(shù)據(jù)包。tcpip_input屬于IP層函數(shù),從這里我們可以看出LWIP的一個(gè)很大的特點(diǎn),即各層之間沒(méi)有明顯的界限劃分。像前面所講的那樣,LWIP協(xié)議棧進(jìn)程完成初始化相關(guān)工作后,會(huì)阻塞在一個(gè)郵箱上等待數(shù)據(jù)包的輸入,這就對(duì)了,tcpip_input函數(shù)就是向這個(gè)郵箱發(fā)送一條消息,且該消息中包含了收到的數(shù)據(jù)包存儲(chǔ)的地址。LWIP協(xié)議棧進(jìn)程從郵箱中取到該地址后就可以對(duì)數(shù)據(jù)包進(jìn)行處理了。
至此,數(shù)據(jù)包的接收可算大功告成,關(guān)于數(shù)據(jù)包的發(fā)送,這點(diǎn)很簡(jiǎn)單,因?yàn)樗槐叵駭?shù)據(jù)包接收那樣要使用一個(gè)專門的進(jìn)程來(lái)實(shí)現(xiàn),而是這樣的:當(dāng)上層有數(shù)據(jù)包要發(fā)送時(shí),直接調(diào)用netif->linkoutput發(fā)送數(shù)據(jù)包就可以了。netif->linkoutput在結(jié)構(gòu)enc28j60初始化時(shí)已經(jīng)被設(shè)置為指向low_level_output函數(shù),該函數(shù)和底層硬件驅(qū)動(dòng)密切相關(guān),用于實(shí)現(xiàn)發(fā)送一個(gè)數(shù)據(jù)包的功能。用戶應(yīng)該結(jié)合具體網(wǎng)卡驅(qū)動(dòng)實(shí)現(xiàn)該函數(shù)。