从Cfgdemo项目来分析协议栈的启动
项目中静态创建的任务有两个:一个是空闲任务,一个是StackTest任务,main函数是空的。任务StackTest的优先级(5)比空闲任务高。整个程序的初始化部分执行完之后,就会执行StackTest任务,从而执行StackTest()函数。
StackTest()函数首先调用了NC_SystemOpen()函数,来完成协议栈系统的初始化工作。必须注意的是:在使用协议栈之前必须最先调用该函数。接下来调用函数CfgNew()来创建一个配置(Configuration)并获得该配置的句柄,接下来的工作就是在配置中增添配置项(Configuration Entry),增添配置选项是通过调用CfgAddEntry()函数实现的。该项目中首先增添的配置项为Host name:
接着增添的配置项为Telnet服务,那么协议栈系统在启动之后会启动Telnet服务(创建了一个名为telnetd的任务):
接着通过调用efs_createfile()创建5个文件,其名字分别为:index.html、tibug.gif、cfgstart.cgi、cfgpass.cgi cfgdone.cgi;这5个文件中,前两个文件的数据分别存放在数组DEFAULT[]、TIBUG[]中,而后三个文件实际是cgi程序,这三个cgi程序分别完成来之客户端的命令请求:View configuration、Change password、Submit configuration,与之同时,它们动态修改并发送了两个网页并CONFIG、USERMSG。
接着增添的配置项为HTTP服务,那么协议栈启动之后会启动Http服务(创建一个名为http server的任务)
接着的增添的配置项为CFGITEM_OS_DBGPRINTLEVEL,来选择打印的信息内容:
接下来调用函数CfgSave()来获取配置的大小并讲配置数据存入一个缓存中去,并释放配置。
接下来调用函数NetBoot()来启动协议栈。这个函数是NDK的用户自己写的一个函数,在这个函数中,调用了协议栈启动函数NC_NetStart(),这个函数属于Network Control API,该函数的源代码可以在\ti\NDK\src\netctrl\netctrl.c中找到。其函数接口如下:
NetBoot()在调用NC_NetStart()之前调用CfgNew()来创建一个新的空的配置,然后再调用CfgLoad()来把之前存入缓冲区中的配置好的配置数据载入新的配置中去,并把它作为参数传入NC_NetStart()中去,接着就调用NC_NetStart()启动协议栈。
NC_NetStart()函数首先调用了4个硬件抽象层(HAL)的四个初始化函数,由它们来完成底层硬件的初始化(具体细节过程等待进一步研究):
接着该函数调用CfgSetDefault()把传入NC_NetStart()函数中构建好的配置设置为默认配置。由于编程方法上的需要,协议栈就使用配置是统一为一个配置句柄指向的配置。这样CfgSetDefault()的本质就是把该配置句柄指向传入NC_NetStart()函数中构建好的配置。
接着把传入NC_NetStart()函数的三个函数指针赋值给三个全局变量,以方便后面适当的时候调用:
接着调用协议栈的核心API函数ExecOpen()来初始化协议栈的executive(自己意会这个概念)。
接着动态创建一个名为ConfigBoot的任务,其优先级为15(最高),其执行的函数是NS_BootTask()。由于DSP/BIOS是占先式实时OS,所以一旦任务高优先级的任务创建,OS内核的调度模块就会自动切换到高优先级的任务执行。很显然,接下来执行的是函数NS_BootTask()。这个函数的源码在\ti\NDK\src\netctrl\ netsrv.c中可以找到。
NS_BootTask()函数首先调用CfgSetService()来Set Service CallBack Funtions for Every Configuration Tag,其意思是为每个Configuration Tag设置一个回调函数,其目的是为了在修改完配置之后能及时更新协议栈系统,也就是使协议栈系统随着配置的改变而实时地改变。 回调函数的接口定义如下:
在NDK的协议栈中,Configuration Tag共有如下8个:
其中需要配置回调函数的有如下几个:CFGTAG_OS、CFGTAG_IP、CFGTAG_SERVICE、CFGTAG_IPNET、CFGTAG_ROUTE,它们的回调函数分别为:SPConfig()、SPConfig()、SPService()、SPIpNet()、SPRoute(),这些回调函数的实现源代码都在\ti\NDK\src\netctrl\ netsrv.c可以找到。
下面分析以下SPConfig()函数是怎样实现实时更新系统的:SPConfig()函数是作为CFGTAG_OS、CFGTAG_IP的回调函数的,所以它必须负责处理增添CFGTAG_OS、CFGTAG_IP两种类型的Configuration Entry时的系统实时更新工作。系统在调CfgAddEntry函数来增添一个CFGTAG_OS、CFGTAG_IP类型的配置项后(注意:CfgAddEntry只把配置数据添加到配置中去),会调用与该种配置类型捆绑的的回调函数SPConfig(CfgSetService函数来完成捆绑工作的),SPConfig函数调用CfgEntryInfo来获取该配置项的数据缓冲区的指针并存放在变量pi中,接着更具Configuration Tag的类型来获取具体需要修改的系统配置参数结构体;CFGTAG_OSè OSENVCFG _oscfg、oscfgcopy / CFGTAG_IP èIPCONFIG _ipcfg、ipcfgcopy,这两个都是全局变量,它们的数据结构类型如下:
// Configuration Structure
typedef struct _ipconfig {
uint IcmpDoRedirect; // Update RtTable on ICMP redirect (1=Yes)
uint IcmpTtl; // TTL for ICMP messages RFC1700 says 64
uint IcmpTtlEcho; // TTL for ICMP echo RFC1700 says 64
uint IpIndex; // IP Start Index
uint IpForwarding; // IP Forwarding (1 = Enabled)
uint IpNatEnable; // IP NAT Enable (1 = Yes)
uint IpFilterEnable; // IP Filtering Enable (1 = Yes)
uint IpReasmMaxTime; // Max reassembly time in seconds
uint IpReasmMaxSize; // Max reassembly packet size
uint IpDirectedBCast; // Look for directed BCast IP addresses
uint TcpReasmMaxPkt; // Max reasm pkts held by TCP socket
uint RtcEnableDebug; //
uint RtcAdvTime; // Time in sec to send RtAdv (0=don't)
uint RtcAdvLife; // Litetime of route in RtAdv
int RtcAdvPref; // Preference Level (signed) in RtAdv
uint RtArpDownTime; // Time 5 failed ARPs keep Rt down (sec)
uint RtKeepaliveTime; // VALIDATED route timeout (sec)
uint RtCloneTimeout; // INITIAL route timeout (sec)
uint RtDefaultMTU; // Default MTU for internal routes
uint SockTtlDefault; // Default Packet TTL
uint SockTosDefault; // Default Packet TOS
int SockMaxConnect; // Max Socket Connections
uint SockTimeConnect; // Max time to connect (sec)
uint SockTimeIo; // Default Socket IO timeout (sec)
int SockBufMax; // Absolute max Socket buffer size
int SockBufMinTx; //
int SockBufMinRx; // Min Rx data for "able to read"
uint PipeTimeIo; // Default Pipe IO timeout (sec)
int PipeBufSize; // Pipe internal buffer size
int PipeBufMinTx; //
int PipeBufMinRx; // Min Rx data for "able to read"
} IPCONFIG;
大家应该注意到所有的成员都是32位的数据类型,所以这里的pi和pDst指针都定义为指向32位类型数据的指针。在做完必要性的检测之后,就会把pi指向数据缓冲中的数据直接拷贝到pDst+Item指向的数据缓冲中去。两个细节性的问题:
1)为什么只拷一个32位?因为这两种类型的Configuration的Configuration Entry都是32位类型的数据。2)为什么Item要事先减一?因为Tag = CFGTAG_OS / CFGTAG_IP, Item的值都是从1开始的。
//----------------------------------------------------------------------------------------
// SPConfig() - CFGTAG_IP and CFGTAG_OS Service Provider
//----------------------------------------------------------------------------------------
static int SPConfig(HANDLE hCfg, uint Tag, uint Item, uint Op, HANDLE hCfgEntry)
{
uint *pi,*pdst,*pdef;
(void)hCfg;
// Get the information
if( CfgEntryInfo( hCfgEntry, 0, (UINT8 **)(&pi) ) < 0 )
return( -1 );
// Setup to handle IP or OS
if( Tag == CFGTAG_IP ){
// Bound the value of Item
if( Item > CFGITEM_IP_MAX )
return( -1 );
pdst = (uint *)&_ipcfg;
pdef = (uint *)&ipcfgcopy;
}else if( Tag == CFGTAG_OS ){
// Bound the value of Item
if( Item > CFGITEM_OS_MAX )
return( -1 );
pdst = (uint *)&_oscfg;
pdef = (uint *)&oscfgcopy;
} else{
return( -1 );
}
// Verify Item
if( !Item ) return( -1 );
Item - -;
// If this is an "add", add the entry
if( Op == CFGOP_ADD ){
*(pdst+Item) = *pi;
// Else if "remove", restore the default
}else if( Op == CFGOP_REMOVE ){
*(pdst+Item) = *(pdef+Item);
}
// Return success
return(1);
}
到这里大家可能觉察到一个问题:我们的CfgAddEntry函数在StackTest任务的开始就被调用,而我们的回调函数是在后来才安装上去的,那么这些添加的配置项是不是没有被更新到系统配置中去呢?没错,的确没有!那我们怎么办呢?由于一开始我们添加了多个配置项,那么这些配置项更新到系统应该有一个先后顺序(可能它们之间有什么依赖关系吧),因此,首先要调用函数CfgSetExecuteOrder()来设置,需要注意的是这个函数不仅设定配置项更新到系统配置中的顺序,同时也设定了这些配置项从系统配置中删除的顺序。做好这些准备工作之后,调用CfgExecute()(其中fExecute参数值为1)来使配置项可以更新到系统配置中去,同也使能以后添加的配置项也能实时更新到系统配置中去。注意:这个函数必须调用,否则你用CfgAddEntry函数添加的配置项都不能更新到系统配置中去,即使你在安装好回调函数后调用CfgAddEntry也不能。至此,TCP/IP协议栈系统已经启动,应用程序可以调用协议栈的API函数来实现网络通信等应用。
接着调用函数NC_BootComplete(),该函数除了设置相应的标志外,主要是来执行一个用户程序。大家应该还记得NC_NetStart函数的接口吧,它有三个函数指针作为参数传入,其中第一个就在NC_BootComplete()中调用:*NetStart,该函数指针所指向的函数是由用户来实现,从而向用户提供一个机会,由用户自己决定协议栈启动之后做什么工作。接着NS_BootTask中调用TaskExit()来结束该任务(任务的状态由RunningèTerminated),那么接下来通过DSP/BIOS内核的调度,使得StackTest任务继续运行。
StackTest任务接着调用函数NetScheduler,它是协议栈的调度器,用来检测并处理所有与网络相关的事件。该函数是一个无限循环,因此任务StackTest最终就成为了网络事件调度任务,也就是说它的角色发生了改变,因此其优先级需要做适当的调整。有关网络事件调度任务的具体细节后面再论。
至此,整个系统就跑起来了,但是我们的应用程序怎样添加到这个系统中去并使用这个协议栈呢?大家应该还记得NC_BootComplete函数调用的NetStart函数指针指向的函数吧,它是由用户实现的一个函数,在协议栈启动之后调用。因此用户的应用程序(与网络相关的应用)可以在该函数中动态创建(应用程序作为任务的形式添加到系统中),CfgDemo项目就是这么做的。
CfgDemo项目中NetStart函数指针指向的函数为NetworkOpen()代码如下:
由此可见该函数动态创建了五个任务,其优先级相同,均为OS_TASKPRINORM(5),而且这五个任务的状态都为处于Ready状态。但是由于随后网络事件调度任务调整了自己的优先级,此四个任务都从Ready状态转为Blocked状态。一旦网络事件调度任务检测到网络事件就会通知相应的任务使之进入Running状态,处理网络事务。
联系客服