NDK开发详细讲解--转载自c101zxg的博客_赤松子耶_新浪博客
基于DSP/BIOS和NDK的嵌入式网络操作系统设计方案
=============================================================================
随着嵌入式应用的普及,嵌入式操作系统的问题日益引人关注。由于DSP代表了一类重要的嵌入式应用,以DSP为核心的嵌入式操作系统也正在成为人们研究的热点。为了对自己的DSP产品提供软件支持,TI公司发行了DSP/BIOS程序包。但是关于DSP/BIOS的定位却存在分歧。一方面,在TI的正式资料和文档中均回避把DSP/BIOS称为实时操作系统,另一方面,为了满足在DSP应用中对操作系统环境的迫切需求,TI及其产品的用户均将DSP/BIOS视为一个“简单的”实时操作系统。
事实上DSP/BIOS并不是真正的实时操作系统,而只是用于帮助程序员开发实时操作系统的软件包。而且,它不包含网络功能。所以,在用DSP/BIOS设计网络操作系统时,需要从两方面入手:一,利用DSP/BIOS提供的资源进行裁减和整合;二,补充DSP/BIOS中未能提供的网络功能。
1 DSP/BIOS的构成
一般认为DSP/BIOS由三部分内容组成,即
1)DSP/BIOS实时库和API
2)DSP/BIOS配置工具
3)DSP/BIOS插件其中配置工具用于提供可视化的编程环境,而插件用于支持调试过程。所以,实时库和API才是DSP/BIOS的核心。DSP/BIOS实时库可以为最终生成的嵌入式实时操作系统提供运行时的基本服务,包括线程调度,中断管理等功能。DSP/BIOS
API由十几个模块组成,如DEV设备驱动接口,MEM存贮段管理器等。程序员通过调用API来使用DSP/BIOS。DSP/BIOS
API由十几个模块组成,每个模块又由相关的数据结构和函数组成。在有关DSP/BIOS的资料中混杂使用了模块(module)、对象(object)等术语,不过,DSP/BIOS与面向对象技术并没有直接的关联。所谓模块,是指一个逻辑上的概念,表示一组数据结构和以此为基础的一组函数。对象则是特指按模块中的数据结构所创建的变量(与C++中的类与对象不是同一个概念)。如果程序员定义了某个对象,他将可以使用模块中的相应函数,并用这些函数代码来组成自己的嵌入式操作系统。
在程序员开发自己的嵌入式操作系统时,一般不会同时需要DSP/BIOS的全部模块。所以,DSP/BIOS中的内容是按需使用的。但是,开发平台会自动选择部分核心模块,并为它们创建对象。例如,开发平台会自动选择任务管理器(
Multi-Task Manager)模块,以支持空闲处理功能:TSK_idle (),即:
TSK_Obj TSK_idle (iFXN, iARG0, iARG1, iARG2, iARG3, iARG4, iARG5,
iARG6, iARG7, iAUTOSTK, iMANSTK, iSTKSZ, iSTKSEG, iPRI, iENV,
iEXITFLAG, iUSETSKNAME, iSTATREG)由DSP/BIOS实时库生成的代码和程序员新编制的代码链接到一起就构成了DSP的嵌入式应用系统。这些代码之间的执行关系由boot.c文件规定,该文件的缺省执行顺序为:
一,初始化DSP:由复位中断向量将程序引导到c_int00,开始初始化寄存器等硬件资源;二,初始化DSP/BIOS模块:通过调用BIOS_init完成,BIOS_init由开发平台中的配置工具自动生成;三,初始化用户应用环境:通过调用main例程实现;四,启动DSP/BIOS:由BIOS_start启动由DSP/BIOS实时库中的相关例程,BIOS_start也是由开发平台中的配置工具自动生成的。但是,通过调整boot.c可以改变系统的执行顺序。
2 NDK开发环境
为了加速其高档DSP的网络化进程,TI结合其C6000系列推出了TCP/IP NDK (Network
Developer’s
Kit)。该开发包采用紧凑的设计方法,实现了用较少的资源耗费支持TCP/IP。从实用效果看,NDK仅用200-250K程序空间和95K数据空间即可支持常规的TCP/IP服务,包括应用层的telnet,DHCP,HTTP等。所以,NDK很适合目前嵌入式系统的硬件环境,是实现DSP上网的重要支撑工具。与常规的TCP/IP应用环境不同,为了最大限度地减少资源消耗,TI为其NDK采用了许多特殊技巧,例如:低层驱动程序与协议栈之间通过指针传递数据,不对包进行复制考贝。因为在嵌入式系统中,低层驱动程序和应用程序一样均需要开发者自行设计。也就是说,在以NDK为基础的开发中,开发人员需要分别设计低层驱动程序和应用程序,这两部分程序通过NDK提供的TCP/IP包发生关联。程序的执行过程是:应用程序调用TCP/IP包,TCP/IP包再调用低层驱动程序。
在NDK中对低层驱动程序与TCP/IP包之间的接口作了明确规室,换言之低层驱动程序必须符合接口约定。以PPP为例,其要点是:
1)由低层驱动程序调用TCP/IP包函数创建PPP连接实例,在连接实例中,以回调函数的形式将用于处理数据发送的函数名传递给TCP/IP包;
2)当TCP/IP包有数据需要发送时,直接调用PPP创建时由低层驱动程序传递来的函数名;
3)当低层驱动程序接收到网络数据时,调用TCP/IP包函数发送到IP层。低层驱动程序直接面向硬件,为了适应硬件的多样性,在NDK中也提供了多种实现低层连接的方法,也为用户设计符合自己硬件特点的低层连接提供了接口规范。
3 利用NDK增加网络功能
如何在嵌入式系统中增添DSP/BIOS实时库中没有的功能是设计基于DSP/BIOS的实时操作系统中最主要的问题之一。解决这个问题一般需要两个步骤:一,程序员定制开发代码;二,与系统中的其它功能绑定在一起。现在,我们介绍如何利用NDK为其增加网络功能。
与常规的TCP/IP开发不同,在开发嵌入式应用时,开发人员必须对网络环境和应用需求作更细致的设置。在以NDK为基础的开发中,程序员需要完成的主要工作有:
1) 通过DSP/BIOS.cdb或DSP/BIOS
API调用NETCTRL任务线程。该线程不是直正的网络任务线程,它以初始化线程的形式出现,起TCP/IP协议栈的事件调度线程的作用。2) 调用初始化函数NC_SystemOpen()。该函数完成对协议栈其及所需要的内存的初始化。
3)创建系统配置。该系统配置用于对协议栈的控制和管理,可用CfgNew()和CfgLoad()等函数操作。
4)调用NETCTRL函数NC_NewStart()启动网络。NC_NewStart()函数的参数中包含三个回调函数指针,分别处理“Start”,“Stop”和“IP
Address Event”事件,其中“Start”和“Stop”只执行一次,“IP Address
Event”则响应每次IP地址的变化。由于NDK已经提供了完整的TCP/IP库函数,程序员开发的代码只须按需要进行配置即可。下面是将嵌入式设备配置为车间局域网节点的核心代码:
char *LocalIPAddr ="128.247.117.12";
char *LocalIPMask ="255.255.254.0";
char *GatewayIP ="128.247.116.1";
char *DomainName ="demo.net";
int NetworkConfig()
{
int rc;
CI_IPNET NA;
CI_ROUTE RT;
HANDLE hCfg;
NC_SystemOpen();
// Create a new configuration TCP/IP Stack Initialization and
Configuration
hCfg = CfgNew();
if( !hCfg )
{ goto main_exit; }
// Manually configure our local IP address
bzero( &NA, sizeof(NA) );
NA.IPAddr = inet_addr(LocalIPAddr);
NA.IPMask = inet_addr(LocalIPMask);
strcpy( NA.Domain, DomainName );
NA.NetType = 0;
// Add the address to interface 1
CfgAddEntry( hCfg, CFGTAG_IPNET, 1, 0,
sizeof(CI_IPNET), (UINT8 *)&NA, 0 );
// Add the default gateway.
bzero( &RT, sizeof(RT) );
RT.IPDestAddr = 0;
RT.IPDestMask = 0;
RT.IPGateAddr = inet_addr(GatewayIP);
// Add the route
CfgAddEntry( hCfg, CFGTAG_ROUTE, 0, 0,
sizeof(CI_ROUTE), (UINT8 *)&RT, 0 );
do
{
rc = NC_NetStart( hCfg, NetworkStart, NetworkStop, NetworkIPAddr
);
} while( rc > 0 );
// Delete Configuration
CfgFree( hCfg );
// Close the OS
main_exit:
NC_SystemClose();
return(0);
}为了让NetworkConfig与系统中的其它功能绑定在一起,可以通过开发平台创建一个TSK任务管理器对象,并将其定义为一个独立的线程任务。这样,TCPStackStart就加入到嵌入式系统中了。
----------------------------------
5 NDK的基本执行过程
The basic process of stack initialization is as follows:
* 用初始化函数对操作系统环境进行初始化。NC_SystemOpen()
* 创建一个新配置:CfgNew()
* 通过配置函数API调用来构建新配置或加载一个原来存在的配置CfgLoad() 。
* 启动带配置的堆栈NC_NetStart( hCfg, pfnStart, pfnStop, pfnNetIP )
* 一些主要的初始化后NC_NetStart() 产生一个新的线程(调用用户的回调函数),产生网络要求的任务线程。
* 正常工作下,网络不会关闭(直到调用NC_NetStop() ),当在某点调用NC_NetStop()
时,原先NC_NetStart() thread 调用用户回调函数来停止,* 运用程序可以在再次调用NC_NetStart() 时立即重新启动,而不需要重载一个新的配置。
* 当NC_NetStart() 返回 and the session is over 时,调用CfgFree()
释放配置句柄。* 所有资源被释放后,调用NC_SystemClose() 来完成系统关闭。
6 NDK关键函数说明
-----------------------------------
1、int CfgAddEntry(HANDLE hCfg, uint Tag, uint Item, uint Mode,
uint Size, UINT8 *pData,HANDLE *phCfgEntry);
功能:为配置创建一个新的配置入口。
参数:
hCfg- Handle to configuration配置句柄
Tag -Tag value of new entry 新入口的标签值
Item -Item value of new entry 新入口的项目值
Mode- Mode flags for how to add entry 如何增加入口的模式标志
Size- Size of entry data pointed to by pData 入口数据大小
pData -Pointer to entry data 入口数据指针
phCfgEntry- Pointer to where to write handle of new
configuration entry返回值:(四种情况)
1-Returns 1 on success with successful processing by a service
callback function。0-Returns 0 on success with no processing performed by a service
callback functionCFGERROR_SERVICE<返回值<0
的情况有三种如下:CFGERROR_BADHANDLE Invalid hCfg handle
CFGERROR_BADPARAM Invalid function parameter
CFGERROR_RESOURCES Memory allocation error while adding
entry小于CFGERROR_SERVICE
备注:To modify the default behavior, one or more of the following
flags can be set:CFG_ADDMODE_UNIQUE :Replace all previous entry instances with
this single entry.CFG_ADDMODE_DUPLICATE :Allow full duplicate entry (duplicate
Tag, Item, and entry data). Requests to add duplicates are normally
ignored.CFG_ADDMODE_NOSAVE :Do not include this entry in the linear
buffer in CfgSave().2、void CfgFree(HANDLE hCfg);
参数:hCfg Handle to configuration
描述:Destroys a configuration. Unloads and frees all configuration
entries and frees theconfiguration handle. After this call, the
configuration handle hCfg is invalid.3、int CfgLoad(HANDLE hCfg, int Size, UINT8 *pData);
功能:把一线性存储块转化为一个配置。(通过配置入口来实现)
参数:hCfg -Handle to configuration
Size -Size of memory block to load
pData -Pointer to memory block to load
返回值:Returns the number of bytes loaded, or less than 0 on an
error. CFGERROR_BADHANDLE :Invalid hCfg handleCFGERROR_BADPARAM :Invalid function parameter
4、HANDLE CfgNew();
功能:创建一个新配置。这个配置可以被配置函数作为参数,
返回值:Returns handle to a new configuration or NULL on memory
allocation error.5、int CfgRemoveEntry(HANDLE hCfg, HANDLE hCfgEntry);
功能:从配置中通过句柄来移除配置入口
返回值:成功返回0,失败返回下面错误代码:
CFGERROR_BADHANDLE- Invalid hCfg handle
CFGERROR_BADPARAM -Invalid function parameter
=====================================================
这是之前用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:
d\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:
d\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(););
关闭配置;
关闭设备;
}