3 onps棧移植說明——添加網卡( 二 )


#define THETHIIRECV_PRIO21//* ethernet網卡接收線程(任務)優先級#define THETHIIRECV_STK_SIZE384 * 4 //* 接收線程棧大??,这阜d灰嘍源笠恍?,太小会报?#define THETHIIRECV_TIMESLICE 10//* 單次任務調度線程能夠工作的最大時間片static void start_thread_ethernet_ii_recv(void *pvParam){rt_thread_t tid = rt_thread_create("EthRcv", thread_ethernet_ii_recv, pvParam, THETHIIRECV_STK_SIZE, THETHIIRECV_PRIO, THETHIIRECV_TIMESLICE);if(tid != RT_NULL)rt_thread_startup(tid);}其余os與之類似 。我們啟動的這個以太網接收線程完成實際的以太網層的報文接收及處理工作 。其輪詢等待網卡接收中斷函數發送的報文到達信號,收到信號則立即讀取并處理到達的報文 。我們在后面講述網卡接收函數的移植細節時還會提到這個接收線程 。
對于網卡發送函數 , 有一點需要注意的是——其原型必須符合協議棧的要求 , 因為我們在進行網卡注冊時還要向協議棧注冊發送函數的入口地址 。前面在介紹ethernet_add()注冊函數時我們已經給出了發送函數的原型定義 , 也就是pfunEmacSend參數指向的函數原型 。協議棧的目標系統是資源受限的單片機系統,為了最大限度節省內存,協議棧采用了寫時零復制(zero copy)技術 , 網卡發送函數需要結合協議棧的buf list機制編寫實現代碼,其偽代碼實現如下:
int ethernet_send(SHORT sBufListHead, UCHAR *pubErr){SHORT sNextNode = sBufListHead;UCHAR *pubData;USHORT usDataLen;//* 調用buf_list_get_len()函數計算當前要發送的ethernet報文長度,其由協議棧提供UINT unEthPacketLen = buf_list_get_len(sBufListHead);//* 逐個取出buf list節點發送出去__lblGetNextNode:pubData = https://www.huyubaike.com/biancheng/(UCHAR *)buf_list_get_next_node(&sNextNode, &usDataLen); //* 獲取下一個節點,buf_list_get_next_node()函數由協議棧提供if (NULL == pubData) //* 返回空意味著已經到達鏈表尾部,沒有要發送的數據了,直接返回就可以了return (int)unEthPacketLen;//* 啟動發送 , 將取出的數據發送出去,其中pubData指向要發送的數據,usDataLen為要發送的數據長度,這兩個值已經通過buf_list_get_next_node()函數得到//* 在這里添加與具體目標網卡相關的數據發送代碼……//* 取下一個數據節點goto __lblGetNextNode;}關于buf list,其實現機制其實很簡單 。以udp通訊為例,用戶要發送數據到對端,會直接調用udp發送函數,將數據傳遞給udp層 。udp層收到用戶數據后 , 為了節省內存,避免復制,協議棧直接將用戶數據掛接到buf list鏈表上成為鏈表的數據節點 。接著,udp層會再申請一個節點把udp報文頭掛接到數據節點的前面 , 組成一個擁有兩個節點的完整udp報文鏈表——鏈表第一個節點掛載udp報文頭 , 第二個節點掛載用戶要發送的數據 。至此,udp層的報文封裝工作完成,數據繼續向ip層傳遞 。ip層會繼續申請一個節點把ip報文頭掛接到udp報文頭節點的前面,組成一個擁有三個節點的完整ip 報文鏈表 。ip報文在ip層經過路由選擇后被送達數據鏈路層,也就是ethernet層 。在這一層,協議棧再將ethernet ii報文頭掛接到ip報文頭節點的前面 。至此 , 整個報文的封裝完成 。協議棧此時會根據網卡注冊信息調用對應網卡的ethernet_send()函數將報文發送出去 。ethernet_send()函數的核心處理邏輯就是按照上述機制再依序取出鏈表節點攜帶的各層報文數據 , 然后順序發送出去 。
網卡移植拼圖的最后一塊就是完成網卡接收函數,把網卡收到的數據推送給協議棧 。其偽代碼實現如下:
//* 網卡接收函數 , 可以是接收中斷服務子函數,也可以是普通函數,普通函數必須確保能夠在數據到達的第一時間就能讀取并推送給協議棧void ethernet_recv(void){EN_ONPSERR enErr;unsigned int unPacketLen;unsigned char *pubRcvedPacket;//* 在這里添加與具體網卡相關的代碼,等待接收報文到達,如果數據到達將報文長度賦值unPacketLen變量,將報文首地址賦值給pubRcvedPacket…………//* 讀取到達報文并將其推送給協議棧進行處理 , 首先利用協議棧mmu模塊動態申請一塊內存用于保存到達的報文unsigned char *pubPacket = (UCHAR *)buddy_alloc(sizeof(ST_SLINKEDLIST_NODE) + unPacketLen, &enErr);//* 申請成功 , 根據協議棧要求,剛才申請的內存按照PST_SLINKEDLIST_NODE鏈表節點方式組織并保存剛剛收到的報文PST_SLINKEDLIST_NODE pstNode = (PST_SLINKEDLIST_NODE)pubPacket;pstNode->uniData.unVal = unPacketLen;memcpy(pubPacket + sizeof(ST_SLINKEDLIST_NODE), (UCHAR *)pubRcvedPacket, unPacketLen);//* 將上面組織好的報文節點放入接收鏈表,這個接收鏈表由協議棧管理,ethernet_put_packet()函數由協議棧提供//* thread_ethernet_ii_recv()接收線程負責等待ethernet_put_packet()函數投遞的信號并讀取這個鏈表//* 參數l_pstNetifEth為前面注冊網卡時由協議棧返回的PST_NETIF指針值ethernet_put_packet(l_pstNetifEth, pstNode);}

推薦閱讀