姓名:朱小鵬 ? ?學(xué)號: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ù)接收
【嵌牛提問】:LWIP是怎樣來處理以太網(wǎng)數(shù)據(jù)接收?
【嵌牛正文】:
昨天說到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ū)動硬件驅(qū)動程序密切相關(guān)的硬件初始化函數(shù)
}
至此芦缰,終于變量enc28j60被初始化好了幔翰,而且它描述的網(wǎng)卡芯片enc28j60也被初始化好了,而且變量enc28j60也被鏈入鏈表netif_list饭耳。
接著上上上上面的語句(8)調(diào)用netif_set_default函數(shù)初始化缺省網(wǎng)絡(luò)接口。協(xié)議棧除了有個netif_list全局變量指向netif網(wǎng)絡(luò)接口結(jié)構(gòu)的鏈表执解,還有個全局變量netif_default全局變量指向缺省的網(wǎng)絡(luò)接口結(jié)構(gòu)寞肖。當(dāng)IP層有數(shù)據(jù)發(fā)送時,它首先會以netif_list為索引選擇滿足某個條件的網(wǎng)絡(luò)接口發(fā)送數(shù)據(jù)包,但是新蟆,當(dāng)找不到這樣的接口時觅赊,協(xié)議棧就會調(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ò)接口吮螺,這通過一個簡單語句來實現(xiàn):
netif->flags |= NETIF_FLAG_UP;
至此,網(wǎng)卡初始化完成帕翻,能正常接收和發(fā)送數(shù)據(jù)包了鸠补。下面我們來討論討論關(guān)于網(wǎng)卡數(shù)據(jù)包的接收和發(fā)送。
LWIP中實現(xiàn)了接收一個數(shù)據(jù)包和發(fā)送一個數(shù)據(jù)包函數(shù)的框架嘀掸,這兩個函數(shù)分別是low_level_input和low_level_output紫岩,用戶需要使用實際網(wǎng)卡驅(qū)動程序完成這兩個函數(shù)。在第一篇中講過横殴,一個典型的LWIP應(yīng)用系統(tǒng)包括這樣的三個進(jìn)程:首先是上層應(yīng)用程序進(jìn)程被因,然后是LWIP協(xié)議棧進(jìn)程,最后是底層硬件數(shù)據(jù)包接收進(jìn)程衫仑。這里我們就來講講第三個進(jìn)程梨与,看看數(shù)據(jù)包是怎樣被接收并往上層傳遞的。但在這之前文狱,有必要說說以太網(wǎng)網(wǎng)卡所收到的數(shù)據(jù)包的格式粥鞋。如下圖,
LWIP使用了一個eth_hdr的數(shù)據(jù)結(jié)構(gòu)來描述以太網(wǎng)數(shù)據(jù)包包頭的14個字節(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都是與編譯器字對齊相關(guān)的宏定義,這里不作詳細(xì)介紹了苏研。上面的dest等浊、src和type三個字段分別和上圖中的目的MAC地址、源MAC地址和類型域字段對應(yīng)摹蘑。
在上面討論的基礎(chǔ)上筹燕,我們來看看這個數(shù)據(jù)包接收進(jìn)程,源代碼如下:
voidethernetif_input(void *arg)//創(chuàng)建該進(jìn)程時衅鹿,要將某個網(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);//接收一個數(shù)據(jù)包
if (p == NULL)//如果數(shù)據(jù)包為空撒踪,
continue;//則循環(huán)結(jié)束,啟動下次接收過程
ethhdr = p->payload;//取得數(shù)據(jù)包內(nèi)數(shù)據(jù)
switch (htons(ethhdr->type))//判斷數(shù)據(jù)包類型
{//只對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ù)
要創(chuàng)建上面的這個進(jìn)程大渤,需要把個網(wǎng)絡(luò)接口結(jié)構(gòu)的netif結(jié)構(gòu)指針作為參數(shù)傳入制妄,在UC/OSII中要用到下面的語句實現(xiàn),
OSTaskCreate(ethernetif_input,(void *)&enc28j60,
&T_ETHERNETIF_INPUT_STK[T_ETHERNETIF_INPUT_STKSIZE-1]
ETH_IF_TASK_PRIO);
在數(shù)據(jù)包接收進(jìn)程中泵三,有三個需要注意的地方耕捞。一是數(shù)據(jù)包接收的方法是查詢方式衔掸,即處理器不斷向網(wǎng)卡芯片中讀取數(shù)據(jù),如果讀不到數(shù)據(jù)砸脊,則控制器會重新啟動一個讀取時序具篇;如果能夠成功讀取到數(shù)據(jù)纬霞,則將數(shù)據(jù)通過網(wǎng)卡注冊的input函數(shù)交往上層進(jìn)行處理凌埂。使用查詢方式實現(xiàn)的數(shù)據(jù)包接收進(jìn)程其優(yōu)先級必須低于系統(tǒng)中其他進(jìn)程的優(yōu)先級,否則它會阻塞比它優(yōu)先級低的進(jìn)程的運行诗芜。上面的程序有個可以改進(jìn)的地方瞳抓,即在讀取到的數(shù)據(jù)包為空時,接收進(jìn)程調(diào)用系統(tǒng)函數(shù)將自己延時一段時間再啟動下一個讀取過程伏恐,這樣可以使其不能阻止優(yōu)先級更低的進(jìn)程的運行孩哑,缺點是數(shù)據(jù)包的接收得不到及時的響應(yīng)。其實數(shù)據(jù)包的接收可以采用中斷的方式來實現(xiàn)翠桦,這種方式是一種比較好的方式横蜒。一般的網(wǎng)卡芯片都有中斷功能,即當(dāng)網(wǎng)卡接收到一個數(shù)據(jù)包后销凑,它可以產(chǎn)生中斷信號告訴控制器自己接收到一個數(shù)據(jù)包丛晌。控制器此時啟動一個讀取數(shù)據(jù)包時序斗幼,就能有效的讀取到非空數(shù)據(jù)包澎蛛。所以可以這樣來實現(xiàn)一個接收數(shù)據(jù)包進(jìn)程:在無數(shù)據(jù)包收到時,數(shù)據(jù)包接收進(jìn)程阻塞在一個信號量下蜕窿,當(dāng)有數(shù)據(jù)包到來時谋逻,網(wǎng)卡芯片產(chǎn)生一個中斷信號,處理器進(jìn)入中斷處理桐经,并釋放一個信號量毁兆。中斷退出后,數(shù)據(jù)包接收進(jìn)程得到信號量阴挣,并從網(wǎng)卡芯片中讀取數(shù)據(jù)包气堕,并將數(shù)據(jù)包遞交給上層進(jìn)行處理。
第二個需要注意的地方是htons(ethhdr->type)函數(shù)的使用屯吊,htons函數(shù)的功能是將一個半字長的數(shù)據(jù)從網(wǎng)絡(luò)字節(jié)順序轉(zhuǎn)換到我們的處理器支持的字節(jié)順序送巡。解釋一下,在計算機體系結(jié)構(gòu)和計算機通信領(lǐng)域中盒卸,對于半字骗爆、字等的存儲機制有可能不同。目前通常采用的存儲機制主要有兩種:big-endian和little-endian蔽介,即大端和小端摘投。對于大端模式煮寡,某個半字或字?jǐn)?shù)據(jù)的高位字節(jié)被在內(nèi)存的低地址端,低位字節(jié)排放在內(nèi)存的高地址端犀呼。對于小端模式幸撕,則恰好相反。由于我們使用的ARM處理器使用的是小端模式外臂,而接收到的網(wǎng)絡(luò)字節(jié)數(shù)據(jù)用的是大端模式坐儿,所以這里調(diào)用函數(shù)htons實現(xiàn)大端與小端的轉(zhuǎn)換,實際就是將兩個字節(jié)交換順序即可宋光。這樣調(diào)用htons(ethhdr->type)后貌矿,ethhdr->type的值就為0x0800或0x0806等。
最后需要注意的地方罪佳,netif->input在結(jié)構(gòu)enc28j60初始化時已經(jīng)被設(shè)置為指向tcpip_input函數(shù)逛漫,所以實際上上面是調(diào)用tcpip_input函數(shù)往上層遞交數(shù)據(jù)包。tcpip_input屬于IP層函數(shù)赘艳,從這里我們可以看出LWIP的一個很大的特點酌毡,即各層之間沒有明顯的界限劃分。像前面所講的那樣蕾管,LWIP協(xié)議棧進(jìn)程完成初始化相關(guān)工作后枷踏,會阻塞在一個郵箱上等待數(shù)據(jù)包的輸入,這就對了娇掏,tcpip_input函數(shù)就是向這個郵箱發(fā)送一條消息呕寝,且該消息中包含了收到的數(shù)據(jù)包存儲的地址。LWIP協(xié)議棧進(jìn)程從郵箱中取到該地址后就可以對數(shù)據(jù)包進(jìn)行處理了婴梧。
至此下梢,數(shù)據(jù)包的接收可算大功告成,關(guān)于數(shù)據(jù)包的發(fā)送塞蹭,這點很簡單孽江,因為它不必像數(shù)據(jù)包接收那樣要使用一個專門的進(jìn)程來實現(xiàn),而是這樣的:當(dāng)上層有數(shù)據(jù)包要發(fā)送時番电,直接調(diào)用netif->linkoutput發(fā)送數(shù)據(jù)包就可以了岗屏。netif->linkoutput在結(jié)構(gòu)enc28j60初始化時已經(jīng)被設(shè)置為指向low_level_output函數(shù),該函數(shù)和底層硬件驅(qū)動密切相關(guān)漱办,用于實現(xiàn)發(fā)送一個數(shù)據(jù)包的功能这刷。用戶應(yīng)該結(jié)合具體網(wǎng)卡驅(qū)動實現(xiàn)該函數(shù)。