現在的位置: 首頁 > 綜合 > 正文

TI的TCP/IP協議棧–NDK

2018年04月11日 ⁄ 綜合 ⁄ 共 10619字 ⁄ 字型大小 評論關閉

這是之前用TI的DM642做視頻編碼器用到的網路協議棧,源碼TI官網上有的下載。維基網上也有關於NDK的一些技術文檔,都是英文的,看了費勁。
看這個之前我對TCP/IP協議幾乎不了解,拿到這個就開始看英文文檔,天昏地暗的,邊看邊整理些東西,沒基礎真的痛苦,硬著頭皮看吧。下面都是我邊看邊整理的,怕丟了,放到這,以後還有用。
 
一、NDK中創建任務的方法:
1、用標準的DSP/BIOS API
struct TSK_Attrs ta;
 ta = TSK_ATTRS;
 ta.priority = OS_TASKPRINORM;
 ta.stack = 0;
 ta.stacksize = stacksize;
 ta.stackseg = 0;
 ta.environ = 0;
 ta.name = "TaskName";
 ta.exitflag = 0;
hMyTask = TSK_create( (Fxn)entrypoint, &ta, arg1, arg2, arg3 );
2、用NDK的任務抽象API
hMyTask = TaskCreate( entrypoint, "TaskName", OS_TASKPRINORM, stacksize, arg1, arg2, arg3 );
In both cases, hMyTask is a handle to a DSP/BIOS TSK task thread.
 
二、內存分配
應用程序在分配內存時最好使用標準的malloc()/free()函數,或者使用DSP/BIOS來分配。
 
三、NDK初始化和配置
1、必須包含NETCTRL.LIB,NETCTRL模塊是協議棧初始化、配置和事件調度的核心。
2、由DSP/BIOS創建的線程是程序的入口點,並且最終成為NETCTRL調度線程。這個控制線程直到協議棧關閉才返回給調用者。
3、在調用其他任何協議棧API之前必須先調用NC_SystemOpen()函數。它初始化協議棧及其所需內存環境。它的兩個參數Priority和OpMode分別決定調度任務的優先順序和調度器何時開始執行。
   Priority包括NC_PRIORITY_LOW 和 NC_PRIORITY_HIGH兩種,
   OpMode包括NC_OPMODE_POLLING 和 NC_OPMODE_INTERRUPT兩種,大部分情況使用interrupt模式,而polling模式會持續運行,當使用polling模式時,優先順序必須設為低(NC_PRIORITY_LOW)。
4、使用實例:
//
// THIS IS THE FIRST THING DONE IN AN APPLICATION!!
//
rc = NC_SystemOpen( NC_PRIORITY_LOW, NC_OPMODE_INTERRUPT );
if( rc )
{
 printf("NC_SystemOpen Failed (%d)\n",rc);
 for(;;);
}
5、系統配置,包括以下參數:
· Network Hostname
· IP Address and Subnet Mask
· IP Address of Default Routes
· Services to be Executed (DHCP, DNS, HTTP, etc.)
· IP Address of name servers
· Stack Properties (IP routing, socket buffer size, ARP timeouts, etc.)
系統配置開始時調用CfgNew()來創建配置句柄。
配置好之後調用NC_NetStart()函數,該函數有4個參數,配置句柄,指向開始回調函數的指針,指向結束函數的指針,指向IP地址事件的函數。開始和結束函數都只被調用一次。開始函數在初始化結束準備執行網路應用程序時調用,結束函數在系統完全關閉時調用,意味著協議棧將不能執行網路應用。IP地址事件函數能夠多次被調用。
NC_NetStart()到系統關閉才返回一個關閉代碼。
//
// Boot the system using our configuration
//
// We keep booting until the function returns 0. This allows
// us to have a "reboot" command.
//
do
{
 rc = NC_NetStart( hCfg, NetworkStart, NetworkStop, NetworkIPAddr );
} while( rc > 0 );
As an example of a network start callback, the NetworkStart() function below opens a user SMTP server
application by calling an open function to create the main application thread.
//
// NetworkStart
//
// This function is called after the configuration has booted
//
static SMTP_Handle hSMTP;
static void NetworkStart( )
{
 // Create an SMTP server
 task hSMTP = SMTP_open( );
}
//
// NetworkStop
//
// This function is called when the network is shutting down
//
static void NetworkStop()
{
 // Close our SMTP server task
 SMTP_close( hSMTP );
}
NetworkIPAddr()函數通常用來同步網路任務,該網路任務需要在執行前設置一個本地IP地址。
void NetIPCb( IPN IPAddr, uint IfIndex, uint fAdd );
    IPAddr            增加或者移除的IP地址
    IfIndex           外設介面獲取或者移除IP地址的標識
    fAdd              增加一個IP地址時設為1,移除IP地址時設為0
//
// NetworkIPAddr
//
// This function is called whenever an IP address binding is
// added or removed from the system.
//
static void NetworkIPAddr( IPN IPAddr, uint IfIdx, uint fAdd )
{
 IPN IPTmp;
 if( fAdd )
  printf("Network Added: ");
 else
  printf("Network Removed: ");
 // Print a message
 IPTmp = ntohl( IPAddr );
 printf("If-%d:%d.%d.%d.%d\n", IfIdx,
          (UINT8)(IPTmp>>24) & 0xFF,
          (UINT8)(IPTmp>>16) & 0xFF,
          (UINT8)(IPTmp>>8) & 0xFF,
          (UINT8) IPTmp & 0xFF );
}
6、關閉協議棧的方法:
①手動關閉,NC_NetStop(1)重啟網路棧,NC_NetStop(0)關閉網路棧。
②當檢測到致命錯誤時關閉,NC_NetStop(-1)。
  // We do not want the stack to abort on any error禁止錯誤引起的關閉
  uint rc = DBG_NONE;
  CfgAddEntry( hCfg, CFGTAG_OS, CFGITEM_OS_DBGABORTLEVEL,
               CFG_ADDMODE_UNIQUE, sizeof(uint), (UINT8 *)&rc, 0 );
7、追蹤服務狀態
當使用NETTOOLS庫時,NETTOOLS狀態回調函數被引入,這個回調函數追蹤被配置使能的服務的狀態。狀態回調函數有兩級,第一個回調由NETTOOLS服務生成,當服務狀態改變時它調用配置服務提供者。然後配置服務提供者增加它自己的狀態到報告中,並且調用應用程序回調函數。當應用程序增加服務到系統配置中時,一個指嚮應用程序回調的指針被提供。
void StatusCallback( uint Item, uint Status, uint Code, HANDLE hCfgEntry )
 Item         Item value of entry changed被更改的入口的項目值
 Status       New status新狀態
 Code         Report code (if any)報告代碼
 hCfgEntry    Non-Referenced HANDLE to entry with status change不引用
實例:
//
// Service Status Reports
//
static char *TaskName[] = { "Telnet","HTTP","NAT","DHCPS","DHCPC","DNS" };  //不能改變,在netcfg.h中定義
static char *ReportStr[] = { "","Running","Updated","Complete","Fault" };  //不能改變,在nettools.h中定義
static char *StatusStr[] = { "Disabled", "Waiting", "IPTerm", "Failed", "Enabled" }
static void ServiceReport( uint Item, uint Status, uint Report, HANDLE h )
{
 printf( "Service Status: %-9s: %-9s: %-9s: %03d\n",
  TaskName[Item-1], StatusStr[Status],
  ReportStr[Report/256], Report&0xFF );
}
以上函數列印的最後一個值是答應通過Report傳遞的低8位的值,這個值是固定的,大部分情況下這個值不需要。通常,如果服務成功,它報告Complete,失敗,他報告Fault。對於那些不會結束的服務(例如,當IP分配啟動時,DHCP客戶端會持續運行),Report的高位位元組意味著Running,而服務特定的低位元組必須被用來指定當前狀態。
For example, the status codes returned in the 8 least significant bits of Report when using the DHCP
client service are:
DHCPCODE_IPADD           Client has added an IP address
DHCPCODE_IPREMOVE        IP address removed and CFG erased
DHCPCODE_IPRENEW         IP renewed, DHCP config space reset
大部分情況下不必去核對這些狀態報告代碼,除非以下情況:
當使用DHCP客戶端來配置協議棧,DHCP客戶端控制CFGTAG_SYSINFO標籤空間的前256個入口。這些入口與這256個DHCP操作標籤通信。應用程序可以檢查DHCPCODE_IPADD或者DHCPCODE_IPRENEW返回代碼以便它能夠讀或者改變通過DHCP客戶端獲得的信息。
8、不使用DHCP client時,手動配置DNS的IP地址方法如下:
IPN IPTmp;
// Manually add the DNS server "128.114.12.2"
IPTmp = inet_addr("128.114.12.2");
CfgAddEntry( hCfg, CFGTAG_SYSINFO, CFGITEM_DHCP_DOMAINNAMESERVER,
  0, sizeof(IPTmp), (UINT8 *)&IPTmp, 0 );
如果以上代碼被加到使用DHCP的應用程序中,當DHCP執行狀態更新時這個入口將會被清除。
9、使用DHCP client時,手動配置DNS的IP地址方法如下:必須在DHCP配置完成以後再手動增加DNS服務。
//
// Service Status Reports
//
static char *TaskName[] = { "Telnet","HTTP","NAT","DHCPS","DHCPC","DNS" };
static char *ReportStr[] = { "","Running","Updated","Complete","Fault" };
static char *StatusStr[] = { "Disabled","Waiting","IPTerm", "Failed","Enabled" };
static void ServiceReport( uint Item, uint Status, uint Report, HANDLE h )
{
 printf( "Service Status: %-9s: %-9s: %-9s: %03d\n",
  TaskName[Item-1], StatusStr[Status],
  ReportStr[Report/256], Report&0xFF );
 // Example of adding to the DHCP configuration space
 //
 // When using the DHCP client, the client has full control over access
 // to the first 256 entries in the CFGTAG_SYSINFO space. Here, we want
 // to manually add a DNS server to the configuration, but we can only
 // do it once DHCP has finished its programming.
 //
 if( Item == CFGITEM_SERVICE_DHCPCLIENT &&
  Status == CIS_SRV_STATUS_ENABLED &&
  (Report == (NETTOOLS_STAT_RUNNING|DHCPCODE_IPADD) ||
  Report == (NETTOOLS_STAT_RUNNING|DHCPCODE_IPRENEW)) )
 {
  IPN IPTmp;
  // Manually add the DNS server when specified. If the address
  // string reads "0.0.0.0", IPTmp will be set to zero.
  IPTmp = inet_addr(DNSServer);
  
  if( IPTmp )
   CfgAddEntry( 0, CFGTAG_SYSINFO, CFGITEM_DHCP_DOMAINNAMESERVER,
     0, sizeof(IPTmp), (UINT8 *)&IPTmp, 0 );
 }
}
 
四、操作系統配置結構體和NDK配置結構體
以上兩個結構體的值可以直接賦值,但是有兩個原因說明增加這個參數給系統配置是有用的:
第一,它為所有的網路配置提供了固定的API。
第二,如果使用了配置載入和保存功能,這些配置參數都被保存除了系統配置的其餘部分。
以下代碼可以改變答應輸出的調試信息的級別,例如,不列印出警告信息,而可以列印出調試信息:
// We do not want to see debug messages less than WARNINGS
 rc = DBG_WARN;
 CfgAddEntry( hCfg, CFGTAG_OS, CFGITEM_OS_DBGPRINTLEVEL,
   CFG_ADDMODE_UNIQUE, sizeof(uint), (UINT8 *)&rc, 0 );
 
五、存儲和載入配置
1、配置設置好後,存儲在非易失性存儲器中。
int CfgSave(HANDLE hCfg, int *pSize, UINT8 *pData);
返回值:正確返回被寫的位元組數,size錯誤返回0,操作錯誤返回小於1。
描述:該函數將由hCfg指定的配置內容存儲到pData指定的內存塊。
      數據緩衝區的大小最初由pSize指定,如果這個指針指向的size值為0(pSize本身不能為NULL指針),這個函數不會試圖存儲配置,相反地,會計算需要的大小並且將這個值寫到由pSize指定的位置。事實上,在任何時候pSize處的值都比存儲配置所需的值要小,函數返回0值並且pSize處的值被用來設置存儲數據所需的大小。參數pData指向接收配置信息的數據緩衝區。
int SaveConfig( HANDLE hCfg )
{
 UINT8 *pBuf;
 int size;
 // Get the required size to save the configuration
 CfgSave( hCfg, &size, 0 );   //計算存儲所需的大小並存儲到pSize
 if( size && (pBuf = malloc(size) ) )
 {
  CfgSave( hCfg, &size, pBuf );
  MyMemorySave( pBuf, size );  //假設這個函數是將線性緩衝區存儲到非易失性存儲器
  Free( pBuf );
  return(1);
 }
 return(0);
}
2、載入配置
實例如下:假設兩個函數
MyMemorySize()返回線性buffer中的配置的存儲大小
MyMemoryLoad()從flash中載入線性buffer
int NetworkTest()
{
 int rc;
 HANDLE hCfg;
 UINT8 *pBuf;
 Int size;
 //
 // 在應用程序中,這絕對是第一個必須被完成的!
 //
 rc = NC_SystemOpen( NC_PRIORITY_LOW, NC_OPMODE_INTERRUPT );
 if( rc )
 {
  printf("NC_SystemOpen Failed (%d)\n",rc);
  for(;;);
 }
 //
 // 首先載入裝有配置信息的線性存儲塊
 //
// 分配一個buffer用來裝載配置信息
 size = MyMemorySize();
 if( !size )
  goto main_exit;
pBuf = malloc( size );
 if( !pBuf )
  goto main_exit;
// 將配置信息從flash裝載到buffer中
 MyMemoryLoad( pBuf, size );
//
 // 創建新配置並且載入配置信息
 //
// 創建一個新配置
 hCfg = CfgNew();
if( !hCfg )
 {
  printf("Unable to create configuration\n");
  free( pBuf );
  goto main_exit;
 }
// 載入配置信息(然後我們可以釋放buffer)
 CfgLoad( hCfg, size, pBuf );
Free( pBuf );
//
 // 用這個配置來啟動這個系統
 //
 // We keep booting until the function returns less than 1. This allows
 // us to have a "reboot" command.
 //
 do
 {
  rc = NC_NetStart( hCfg, NetworkStart, NetworkStop, NetworkIPAddr );
 } while( rc > 0 );
// 刪除配置
 CfgFree( hCfg );
// 關閉操作系統
    main_exit:
 NC_SystemmClose();
 return(0);
}
 
六、ping NDK目標系統,以下代碼例子配置IP重組最大的尺寸為65500個位元組。
uint tmp = 65500;
CfgAddEntry(hCfg, CFGTAG_IP, CFGITEM_IP_IPREASMMAXSIZE,
  CFG_ADDMODE_UNIQUE, sizeof(uint), (UINT8*) &tmp, 0);
 
七、發送和接收UDP數據包超過最大傳輸單元尺寸的方法:
1、NDK配置操作:
 CFGITEM_IP_SOCKUDPRXLIMIT
 CFGITEM_IP_IPREASMMAXSIZE
2、socket操作:
 SO_SNDBUF
 SO_RCVBUF
3、操作系統適配層定義:
 MMALLOC_MAXSIZE
 MMALLOC_MAXSIZE
例如:為了配置發送和接收的UDP數據包能達到65500位元組的大小,一下代碼必須被執行
1、uint tmp = 65500;
   CfgAddEntry(hCfg, CFGTAG_IP, CFGITEM_IP_IPREASMMAXSIZE,
  CFG_ADDMODE_UNIQUE, sizeof(uint), (UINT8*) &tmp, 0);
   CfgAddEntry(hCfg, CFGTAG_IP, CFGITEM_IP_SOCKUDPRXLIMIT,
  CFG_ADDMODE_UNIQUE, sizeof(uint), (UINT8*) &tmp, 0);
2、在"pbm.c"文件中修改MMALLOC_MAXSIZE參數,在"mem.c"文件中修改RAW_PAGE_SIZE參數,並且重新建立OSAL庫。
3、uint tmp = 65500;
   setsockopt(s, SOL_SOCKET, SO_RCVBUF, &tmp, sizeof(int) );
   setsockopt(s, SOL_SOCKET, SO_SNDBUF, &tmp, sizeof(int) );
 
八、UDP數據報有效載荷時間戳
NDK允許應用程序更新UDP數據包的有效載荷。常用的方法是更新數據包的時間戳信息。這樣,發送端和接收端能更精確地調整依賴於改變系統特有的運行時間的傳遞延時。
1、在傳輸端:
 在每個socket上,通過使用"setsockopt"函數,應用程序可以註冊一個喚起函數。
 將數據包插入驅動的傳輸隊列之前,協議棧調用這個喚起函數。
 在頭部,喚起函數要更新UDP校驗和信息。
以下代碼示意了怎樣控制它:
void myTxTimestampFxn(UINT8 *pIpHdr) {
     ...
         }
setsockopt(s, SOL_SOCKET, SO_TXTIMESTAMP, (void*) myTxTimestampFxn, sizeof(void*));
2、在接收端:
 在每個介面基礎上,通過使用"EtherConfig"函數,應用程序可以註冊一個喚起函數。EtherConfig函數在"netctrl.c"文件中的NC_NetStart()函數中設置。
 這個喚起函數僅僅在處理包之前協議棧調度器調用。
 在頭部,喚起函數要更新UDP校驗和信息。
以下代碼示意了怎樣控制它:
void myRcvTimestampFxn(UINT8 *pIpHdr) {
     ...
          }
EtherConfig( hEther[i], 1518, 14, 0, 6, 12, 4, myRcvTimestampFxn);
 
九、調試信息
包括DBG_INFO,DBG_WARN,DBG_ERROR。使用這些等級有兩個目的:
1,決定調試信息是否會被列印。
2,決定調試信息是否會引起NDK關閉。
DBG_ERROR這一層的信息會引起棧的關閉。可以通過系統配置和使用操作系統配置結構來調整這個行為。
 
十、存儲器出錯
當診斷NDK調試信息時,字存儲器出錯會頻繁發生。這是因為對於緩衝設備很容易造成存儲器出錯。包含在NDK中的大部分示常式序都是用全L2緩衝模式。在這種模式下,任何對CPU內部存儲邊界的讀寫訪問都會引起緩衝區出錯,從而引起存儲器出錯。因為內部存儲(L2)邊界從地址0x00000000開始,當使用全緩衝時,一個空指針會導致問題。
當L2使用cache+RAM模式時,對於地址0x00000000的讀寫不會引起緩衝錯誤。
 
十一、程序死鎖
大部分程序死鎖都是由於任務堆棧空間不足引起的。例如,當我們寫一個HTTP CGI函數時,CGI函數的任務線程可能總共只需要5000位元組的任務堆棧空間。因此,使用過大的堆棧是不被推薦的。
一般來說,不使用下面的源碼:
myTask()
{
 char TempBuffer[2000];
 myFun( TempBuffer );
}
而是這樣使用:
myTask()
{
 char *pTempBuf;
 pTempBuf = MEM_alloc( 0, 2000, 0);
 if( pTempBuf != MEM_ILLEGAL )
 {
  myFun( pTempBuf );
  MEM_free( pTempBuf, 2000 );
 }
}
如果調用一個內存分配函數速度太快,可以考慮使用外部buffer。這僅僅是個例子,幾乎不要事先考慮就能排除所有可能的堆棧溢出情況,並且消除可能的程序死鎖。
 
十二、內存管理報告
mmAlloc()和mmFree():分配/釋放小的內存塊
mmBulkAlloc()和mmBulkFree():分配/釋放較大(不受限制的,通常在3000bytes以上)內存塊
48:48 ( 75%)   18:96 ( 56%)   8:128 ( 33%)   28:256 ( 77%)   1:512 ( 16%)   0:1536   0:3072
(21504/46080 mmAlloc: 61347036/0/61346947, mmBulk: 25/0/17)
18:96 ( 56%):內存管理器的頁的大小是3072 bytes,至多被分成18塊*96位元組,使用了一頁的56%。
mmAlloc: 61347036/0/61346947  :調用了mmAlloc()函數61347036次,失敗了0次,調用mmFree()函數61346947次。在任何時候,調用mmAlloc()的次數 + 失敗的次數 = 調用mmFree()的次數 + 應該分配而未分配的次數。假如在最終的報告中有 mmAlloc:n1/n2/n3,n1+n2應該等於n3,如果不等,就有內存泄露。
十三、NC_NetStart()函數流程
NC_NetStart()
{
 設備初始化;
 創建配置啟動線程;
網路調度器(NetScheduler(););
關閉配置;
 關閉設備;
}

抱歉!評論已關閉.