TI的TCP/IP協議棧--NDK .

zzsfqiuyigui發表於2011-10-27
 
這是之前用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(););
關閉配置;
 關閉裝置;
}

相關文章