目录
本文仅为翻译手册,留以自己查看,若需要深入交流,可以在个人分类中查找解析与实践内容(可能未发布),或与作者联系
NETTOOL Services and Support Functions
NDK Software Directory Structure
Windows and Linux Test Utilities
Network Application Development
Configuring the NDK with C Code
Adding NDK Hooks Using ACD Support
Initializing the File Descriptor Table
Application Debug and Troubleshooting
Introduction to NETCTRL Source
Task Thread Abstraction: TASK.C
TaskCreate(), TaskExit(), and TaskDestroy()
Choosing the llEnter()/llExit() Exclusion Method
Packet Buffer Allocation Method
Memory Allocation System: MEM.C
Jumbo Packet Buffer Manager (Jumbo PBM)
Preface
About this manual
本文档介绍了NDK应用程序编程以及如何在用户系统和应用程序中构建和使用NDK。它不是一个API参考。
How to use this manual
本文档中提供的信息分为以下章节:
•第1章:概述介绍了堆栈和开发网络应用程序。
•第2章:网络应用程序开发描述了NDK软件,以及如何立即开始开发网络应用程序。
•第3章:网络控制函数描述网络控制层(NETCTRL)的内部工作方式。
•第4章:OS适配层描述了控制NDK如何使用RTOS资源的OS适配层。这包括任务,信号量,内存和打印。NDK旨在与各种RTOS系统配合使用 - 包括TI-RTOS内核(以前称为SYS / BIOS)和FreeRTOS。NDK使用POSIX pthread调用和DPL函数调用来支持各种操作系统。
•附录A:修订历史记录描述了自上一版本以来对本文档所做的更改。
Overview
本章通过简要概述NDK的用途和构造,以及NDK部署环境中的硬件和软件环境细节,介绍了网络开发人员工具包(NDK)。此网络开发人员工具包(NDK)软件用户指南是NDK和开发网络应用程序的简介。
Introduction
网络开发者工具包(NDK)是一个用于在TI嵌入式处理器上开发和演示网络应用的平台。此NDK版本中包含的代码是通用C代码,可在各种TI器件上运行。
在SimpleLink SDK中,网络服务SlNetSock模块将NDK配置为有线以太网通信的网络堆栈。
NDK堆栈用作网络和数据包处理应用程序开发的快速原型开发平台。它可用于为现有应用程序添加网络连接,以进行通信,配置和控制。使用NDK中提供的组件,开发人员可以快速从开发概念转移到连接到网络的工作实现。
在图1-1中,用户应用程序可以使用标准BSD套接字API进行调用,也可以直接调用SlNetSock层与网络连接进行通信。SlNetSock层是用户应用程序和特定于服务的堆栈之间的堆栈独立层。
在SimpleLink SDK中,网络服务SlNetSock模块将NDK配置为有线以太网通信的网络堆栈。“SlNetIfNDK”是NDK的SlNetSock接口的实现。
可以通过调用NDK的Cfg *()函数在运行时配置NDK堆栈的设置。
NDK旨在提供独立于平台,与设备无关且与RTOS无关的API接口,供应用程序使用。本文档和TI网络开发人员工具包(NDK)API参考指南(SPRU524)中描述的许多API模块很少被应用程序使用。相反,它们可供那些正在编写设备驱动程序和网络堆栈的人使用。
用户应用程序通常使用NDK提供的以下API:
•Cfg *()函数向配置数据库添加设置,以确定应用程序可以使用哪些网络服务。有关详细信息,请参阅TI网络开发人员套件(NDK)API参考指南(SPRU524)。
•NC _ *()函数可以初始化,启动和停止网络服务系统。有关详细信息,请参阅TI网络开发人员套件(NDK)API参考指南(SPRU524)。
•TaskCreate()或pthread *()函数使用POSIX处理应用程序线程。在内部,POSIX可以使用SimpleLink SDK为目标设备支持的任何RTOS。对于大多数目标,这包括TI-RTOS内核和FreeRTOS。有关详细信息,请参见第2.3节。
•NDK套接字API,用于执行套接字操作,如接受,发送和接收。对于纯NDK应用程序,这些是类似BSD的NDK _ *()函数。
对于SimpleLink SDK应用程序,您可以使用通过SlNetSock提供的标准BSD API。有关详细信息,请参阅TI网络开发人员套件(NDK)API参考指南(SPRU524)。
Rebuilding NDK Libraries
NDK安装包括所有源文件以及对重建其库的完全支持。要重建NDK库,请参阅Texas Instruments Wiki中使用gmake重建NDK核心主题中的说明。
您可以定义以下宏以导致重建的NDK库的行为发生变化:
•_INCLUDE_ACD_SUPPORT - 如果已定义,如果ARP请求来自与NDK主机不同的子网上的计算机,则不会添加地址解析协议(ARP)条目。缺省情况下,为所有ARP请求添加ARP表项。
•_INCLUDE_IPv6_CODE - 如果已定义,则启用IPv6支持。
•_INCLUDE_JUMBOFRAME_SUPPORT - 如果已定义,则支持大于1500字节的数据包大小。
•_INCLUDE_NAT_CODE - 如果已定义,则堆栈提供网络地址转换(NAT)支持。
•_INCLUDE_PPP_CODE - 如果已定义,则包括点对点协议(PPP)模块。
•_INCLUDE_PPPOE_CODE - 如果已定义,则启用PPP over Ethernet(PPPoE)客户端。
•_STRONG_CHECKING - 如果已定义,则对所有句柄执行错误检查。
NDK Stack Library Design
NDK旨在提供完整的TCP / IP功能环境,无论是否具有路由,都可在较小的内存空间内完成。
Design Philosophy
NDK通过抽象编程接口与本机OS和低级硬件隔离。本机OS由操作系统适配层(OS)抽象,并且通过硬件适配层(HAL)库支持定制硬件。这些库用于将堆栈连接到RTOS和系统外围设备。
NDK的功能包括:
•虚拟LAN(VLAN)支持。
VLAN支持使堆栈能够接收,处理和传输VLAN标记的数据包。
•原始以太网套接字支持。
原始以太网套接字(与原始IPv4 / IPv6套接字不同)使任何使用NDK堆栈的应用程序都能够使用自定义第2层(L2)协议类型发送/接收以太网数据包,即除了任何数据包之外的数据包的以太网报头中的协议类型 众所周知的标准协议类型,如IP(0x800),IPv6(0x806),VLAN(0x8100),PPPoE控制(0x8863)或PPPoE数据(0x8864)。
•IPv6堆栈支持。
该堆栈可用于IPv6和IPv4。
•巨型帧支持。
巨型帧的数据包大小超过1500字节。通过链接与为Jumbo帧支持编译的库,可以将巨型帧支持内置到应用程序中。必须使用添加的以下预处理器定义重新编译库和应用程序:_INCLUDE_JUMBOFRAME_SUPPORT。
有关VLAN,IPv6,原始以太网套接字和Jumbo帧支持的更多详细信息,请参阅TI网络开发人员套件(NDK)API参考指南(SPRU524)。
Control Flow
图1-2显示了如何根据函数调用控制流来组织堆栈包的概念图。显示了构成NDK的五个主要库。这些是STACK,NETTOOL,OS,HAL和NETCTRL。这些库在以下章节中进行了总结。NIMU相关的更改也在受影响的库(STACK,NETCTRL和NETTOOL)中进行了讨论。
Library Directory Structure
为构成/ ti / ndk目录树中的NDK的每个库提供了预构建的可链接库和源代码。预构建的库位于每个库的目录的lib子目录中。
有关目录树的更多信息,请参见第1.5节。
•提供了IPv4和IPv6库。不包含“ipv4”的文件名是针对IPv6编译的。但是,在/ ti / ndk / stack / lib目录中,为IPv4编译不包含“6”的文件名。
•NETCTRL库有“min”,常规和“full”版本。例如,netctrl_min,netctrl和netctrl_full。详细信息请参见1.3.8节。
•OS库包含TI-RTOS内核(“os”和“os_sem”)和FreeRTOS(“os_freertos”和“os_sem_freertos”)版本。
•NDK安装中不包含支持Jumbo Frame的库(大小超过1500字节的数据包)。如果您希望启用了Jumbo Frame支持的NDK库,则需要#define _INCLUDE_JUMBOFRAME_SUPPORT预处理器定义并重建库,如TI嵌入式处理器Wiki中的重建NDK核心主题中所述。
NDK提供的库与平台无关。也就是说,为所有平台提供了这些库的版本。任何仅针对某些平台存在的依赖于硬件的库都分布在相应的NDK支持包(NSP)中,您可以从NDK单独下载该包。
NDK安装包括所有源文件以及对重建其库的完全支持。要重建NDK库,请参阅TI嵌入式处理器Wiki中的重建NDK核心主题中的说明。
The STACK Library
STACK库是主要的TCP / IP网络堆栈。它包含从顶部的套接字层到底部的以太网和点对点协议(PPP)层的所有内容。编译库以使用RTOS,并且在从一个平台移动到另一个平台时不需要移植。NDK中包含了几个库的版本。
STACK库在/ ti / ndk / stack / lib目录中提供。以下版本的库包括或排除PPP,以太网PPP(PPPoE)和网络地址转换(NAT)等功能。
NETTOOL Libraries
网络工具(NETTOOL)函数库包含随NDK提供的所有基于套接字的网络服务,以及一些旨在帮助开发网络应用程序的其他工具。NETTOOL库中最常用的组件是基于标记的配置系统。配置系统几乎控制堆栈及其服务的每个方面。配置可以存储在非易失性RAM中,以便在BOOT时自动加载。
NETTOOL库在/ ti / ndk / nettools / lib目录中提供。
NETTOOL库中提供的工具直接使用NIMU IOCTL调用来检索与设备相关的信息。
有关更多信息,请参见第1.4.3节。
OS Library
这些库形成一个向上适配层,将一些抽象的OS函数调用映射到POSIX和DPL函数调用。这包括任务线程管理,内存分配,数据包缓冲区管理,打印,日志记录,关键部分和巨型数据包缓冲区管理。
OS库位于/ ti / ndk / os / lib目录中。“os”库是具有优先级排除的OS Adaptation Layer库。“os_sem”库使用信号量排除。有关更多信息,请参见第1.4.1节。
HAL Library
HAL库包含将硬件外围设备连接到NDK的文件。其中包括定时器,LED指示灯,以太网设备和串行端口。/ ti / ndk / hal目录中包含的驱动程序如下:
有关HAL API的信息,请参见第1.4.5节。HAL还在TI网络开发人员套件(NDK)API参考指南(SPRU524)和NDK支持包以太网驱动程序设计指南(SPRUFP2)中进行了讨论。
NETCTRL Libraries
NETCTRL或网络控制库可以被视为堆栈的中心。它控制TCP / IP与外部世界之间的交互。在所有堆栈模块中,它对NDK的操作最重要。其职责包括:
•初始化NDK和低级设备驱动程序
•通过配置服务提供程序回调函数引导和维护系统配置
•与低级设备驱动程序的接口和调用驱动程序事件以调用NDK
•在退出时卸载系统配置和驱动程序清理
•在堆栈启动期间初始化NIMU核心,然后初始化并启动在NIMU核心注册的所有设备驱动程序。初始化NDK核心堆栈中的VLAN模块。
•在堆栈关闭期间取消初始化NIMU内核,然后在所有已注册的设备驱动程序中循环并关闭它们。
•定期轮询所有已注册的设备,以便允许它们执行任何例行维护活动,例如链接管理。此外,检查来自任何已注册设备的任何事件,如数据包接收。
•如果在堆栈启动期间内置,则初始化IPv6堆栈。
NETCTRL库旨在支持用户在其应用程序中期望的“潜在”堆栈功能(例如DHCP服务器)。但是,这样做的缺点是,即使应用程序从不使用这些功能,这些功能的代码也将包含在可执行文件中。这导致比通常需要的更大的占地面积。
为了最大限度地减少此问题,/ ti / ndk / netctrl / lib目录中提供了以下不同版本的NETCTRL库:
•netctrl_min。此最小库仅启用DHCP客户端。当需要最小的占地面积时,应该使用它。
•netctrl。NETCTRL库的这个“标准”版本支持以下功能并具有中等占用空间: - Telnet服务器 - HTTP服务器 - DHCP客户端•netctrl_full。这个“完整”库支持所有支持的NETCTRL功能,包括: - Telnet服务器 - HTTP服务器 - NAT服务器 - DHCP客户端 - DHCP服务器 - DNS服务器
所有版本的NETCTRL都支持NIMU,VLAN和Raw以太网套接字。这些NETCTRL库版本中的每一个都是为纯IPv4和IPv6构建的。
如果使用XGCONF配置工具在Code Composer Studio(CCS)中配置NDK,则会根据您启用的模块自动选择相应的NETCTRL库。
您可以重建NETCTRL库以仅包含要使用的功能。为此,请编辑/ ti / ndk / netctrl目录中的package.bld文件,然后重新定义以下任何选项。有关重建NDK库的信息,请参阅TI嵌入式处理器Wiki中的重建NDK核心主题。
NDK Programming APIs
如前所述,堆栈设计用于最佳隔离,因此可以无缝地插入到不同的运行时环境中。因此,您可能有机会使用几种不同的编程接口。它们按相关性递减顺序列在此处。以下所有内容均在TI网络开发人员套件(NDK)API参考指南(SPRU524)中详细介绍。
Operating System Abstraction
操作系统抽象提供了支持移植到其他操作系统的服务。在大多数情况下,这些是NDK堆栈的内部,应用程序开发人员不会使用这些API。但是,有时应用程序开发人员可能需要使用Task模块。有关这些详细信息,请参阅TI网络开发人员套件(NDK)API参考指南(SPRU524)。
Sockets and Stream IO API
套接字API主要由类似BSD的套接字服务API组成,但包含一些其他有用的调用。这些函数是可重入且线程安全的。它们显示为操作系统随附的标准IO的扩展,不应与任何本机文件支持功能冲突。
NETTOOL Services and Support Functions
NETTOOL库包括网络服务和基本网络支持功能。支持函数的API类似于Berkeley Unix的API,为自定义功能提供了一些附加功能。
NETTOOL服务包括将堆栈作为网络服务器或路由器运行所需的大多数网络协议服务器。服务的API在所有支持的服务中是标准化和统一的,并且还可以使用配置系统调用服务,完全绕过NETTOOL API。
Internal Stack API
您几乎从不使用内部堆栈API(可以被认为是内核级API)。但是,某些类型的堆栈维护需要它,并且它由一些示例源代码调用。
HAL API
您很可能永远不会直接调用HAL API,但在将堆栈移动到备用硬件平台时需要它。TI网络开发人员套件(NDK)API参考指南(SPRU524)和网络开发人员套件(NDK)支持包以太网驱动程序(SPRUFP2)中更详细地描述了HAL。
NDK Software Directory Structure
NDK文件位于/ ti / ndk中,并组织到以下子目录中。(任何其他目录仅供内部使用。)对于每个库,都提供源文件和预构建库。
NDK Include File Directory
包含文件目录(/ ti / ndk / inc)包含可由网络应用程序引用的所有包含文件。必须在软件工具默认搜索路径或CCStudio项目文件的搜索路径中包含此目录。后一种方法用于示例程序中。主要包含文件如下:
在HAL,NETCTRL,NETTOOLS,OS,STACK和TOOLS库的子目录中提供了其他包含文件。
TOOL Programs
NDK提供了多种用于各种目的的工具。它们位于/ ti / ndk / tools目录中。
Windows and Linux Test Utilities
WINAPPS目录包含四个非常简单的测试应用程序,可用于验证Console示例程序的操作。这些测试应用程序充当TCP发送,接收和回送以及UDP回送操作的网络客户端。大多数NDK示例包含可与这些测试应用程序通信的网络数据服务器。SEND,RECV,ECHOC和TESTUDP应用程序在这些示例的描述中引用,可以在这些示例中找到。
为Windows和Linux提供了这些测试程序的可执行版本。
您可以使用提供的makefile使用Microsoft Visual Studio或MinGW编译器工具重建这些工具。
Example Programs
SDK中提供了与NDK一起使用的示例,具体取决于SDK目标。
Configuring NDK Modules
要配置NDK及其组件,NDK允许您使用C代码调用Cfg *()函数或CCStudio中的XGCONF配置工具。选择一种方法或另一种方法来配置您的应用程序。
•C代码。
通过编写调用CfgNew()的C代码来创建配置数据库和其他Cfg *()函数以将各种设置添加到该配置数据库来配置应用程序。
在链接器命令文件中完成了一些其他配置。有关详细信息,请参阅第2.1节。建议使用此配置方法,因为它可以与SDK支持的任何RTOS一起使用。
•XGCONF。
使用CCStudio中的图形显示来启用和设置属性。XGCONF还可用于配置TI-RTOS内核使用的对象。有关详细信息,请参阅第2.2节。仅当您的RTOS是TI-RTOS内核时,才能使用此配置方法。
Network Application Development
本章介绍如何开始开发网络应用程序。它讨论了使用NDK库开发网络应用程序所涉及的问题和指南。
Configuring the NDK with C Code
要配置应用程序对NDK及其组件的使用,可以使用CC代码中的C代码或XGCONF配置工具。选择一种方法或另一种方法来配置您的应用程序。
C代码。通过编写调用CfgNew()的C代码来创建配置数据库和其他Cfg *()函数以将各种设置添加到该配置数据库来配置应用程序。在链接器命令文件中完成了一些其他配置。建议使用此配置方法,因为它可以与SDK支持的任何RTOS一起使用。如果您使用的是此配置方法,请参见第2.1.1节及后面的小节。
XGCONF。使用CCStudio中的图形显示来启用和设置属性。XGCONF还可用于配置TI-RTOS内核使用的对象。仅当您的RTOS是TI-RTOS内核并且您正在使用CCStudio开发应用程序时,才能使用此配置方法。如果您使用XGCONF进行配置,请参见第2.2节。
不要混用配置方法。如果项目使用两种方法,则两种配置之间将存在冲突。
本文使用C code方法进行配置
Required RTOS Objects
NDK使用OS适配层访问RTOS(TI-RTOS内核或FreeRTOS)和HAL层以访问硬件。这些层需要创建以下RTOS对象才能使NDK正常工作。只能通过修改OS和HAL层的代码来更改此要求。
•计时器对象。HAL中的计时器驱动程序要求创建RTOS Timer对象以驱动其主计时器。必须将Timer配置为每100mS触发一次,并调用定时器驱动程序函数llTimerTick()。有关创建和启动计时器对象的ndk.c中的示例,请参见第2.1.4.1.1节。
(如果使用XGCONF配置NDK,则会自动创建此对象。)
Include Files
如果您使用Cfg *()函数将设置添加到配置数据库,则您负责指向正确的包含文件目录。NDK安装中的include目录在1.5.1节中描述。您应该在项目构建选项中包含基本NDK包含目录。例如,项目应设置为使用包含文件路径:/ ti / ndk / inc。
(如果使用XGCONF配置NDK,则CCStudio项目会自动引用正确的包含文件目录。)
Library Files
如果您使用Cfg *()函数进行配置,则您负责将正确的库链接到项目中。如果您使用CCStudio来管理应用程序,则可以将所需的库文件直接添加到CCStudio项目中。这样,链接器就会知道在哪里找到它们。
(如果使用XGCONF配置NDK,则正确的库会自动与应用程序链接。)
System Configuration
如果使用Cfg *()函数进行配置,则必须创建系统配置才能使用NETCTRL API。配置是一个基于句柄的对象,它包含大量系统参数。这些参数控制堆栈的操作。典型配置参数包括:
•网络主机名
•IP地址和子网掩码
•默认路由的IP地址
•要执行的服务(DHCP,DNS,HTTP等)
•名称服务器的IP地址
•堆栈属性(IP路由,套接字缓冲区大小,ARP超时等)
创建配置的过程总是从调用CfgNew()开始创建配置句柄。创建配置句柄后,可以批量将配置信息加载到句柄中,也可以一次构造一个条目。
批量加载配置需要先前构建的配置已保存到非易失性存储中。配置在内存中后,可以通过调用CfgLoad()将信息加载到配置句柄中。另一种选择是手动将各个项目添加到配置中以获得各种所需的属性。这是通过为要添加的每个条目调用CfgAddEntry()来完成的。
堆栈配置API的确切规范显示在TI网络开发人员套件(NDK)API参考指南(SPRU524)的初始化和配置部分。本文档的第2.1.4.1节和NDK示例程序中提供了一些其他示例。
Configuration Examples
本节包含一些使用Cfg *()函数构造配置的示例代码。
Constructing a Configuration for a Static IP and Gateway(原文档中附代码)
此示例中的ndkStackThread()函数由堆栈的主初始化线程组成。它创建一个新配置,配置IP,TCP和UDP,然后启动堆栈。
该示例来自几个NDK示例中的ndk.c文件。它执行以下操作:
1.通过调用POSIX timer_create()和timer_settime()函数,创建并启动NDK将使用的计时器对象。在内部,这些POSIX函数可以使用任何支持的RTOS,例如TI RTOS Kernel或FreeRTOS。
2.通过调用NC_SystemOpen()启动系统会话。
3.通过调用CfgNew()创建新配置。
4.配置IP,TCP和UDP的堆栈设置。有关如何通过调用CfgAddEntry()配置这些设置的示例,请参阅ndk.c中的initIp(),initTcp()和initUdp()函数。
5.通过调用CfgAddEntry()配置用于低,正常和高优先级任务的堆栈大小。
6.通过调用NC_NetStart()使用此配置引导系统。
7.在系统关闭时释放配置(当NC_NetStart()返回时)并调用CfgFree()和NC_SystemClose()。
8.调用timer_delete()删除计时器对象。
static void *ndkStackThread(void *threadArgs)
{
void *hCfg;
int rc;
timer_t ndkHeartBeat;
struct sigevent sev;
struct itimerspec its;
struct itimerspec oldIts;
int ndkHeartBeatCount = 0;
/* create the NDK timer tick */
sev.sigev_notify = SIGEV_SIGNAL;
sev.sigev_value.sival_ptr = &ndkHeartBeatCount;
sev.sigev_notify_attributes = NULL;
sev.sigev_notify_function = &llTimerTick;
rc = timer_create(CLOCK_MONOTONIC, &sev, &ndkHeartBeat);
if (rc != 0) {
DbgPrintf(DBG_INFO, "ndkStackThread: failed to create timer (%d)\n");
}
/* start the NDK 100ms timer */
its.it_interval.tv_sec = 0;
its.it_interval.tv_nsec = 100000000;
its.it_value.tv_sec = 0;
its.it_value.tv_nsec = 100000000;
rc = timer_settime(ndkHeartBeat, 0, &its, NULL);
if (rc != 0) {
DbgPrintf(DBG_INFO, "ndkStackThread: failed to set time (%d)\n");
}
rc = NC_SystemOpen(NC_PRIORITY_LOW, NC_OPMODE_INTERRUPT);
if (rc) {
DbgPrintf(DBG_ERROR, "NC_SystemOpen Failed (%d)\n");
}
/* create and build the system configuration from scratch. */
hCfg = CfgNew();
if (!hCfg) {
DbgPrintf(DBG_INFO, "Unable to create configuration\n");
goto main_exit;
}
/* IP, TCP, and UDP config */
initIp(hCfg);
initTcp(hCfg);
initUdp(hCfg);
/* config low priority tasks stack size */
rc = 2048;
CfgAddEntry(hCfg, CFGTAG_OS, CFGITEM_OS_TASKSTKLOW, CFG_ADDMODE_UNIQUE,
sizeof(uint32_t), (unsigned char *)&rc, NULL);
/* config norm priority tasks stack size */
rc = 2048;
CfgAddEntry(hCfg, CFGTAG_OS, CFGITEM_OS_TASKSTKNORM, CFG_ADDMODE_UNIQUE,
sizeof(uint32_t), (unsigned char *)&rc, NULL);
/* config high priority tasks stack size */
rc = 2048;
CfgAddEntry(hCfg, CFGTAG_OS, CFGITEM_OS_TASKSTKHIGH, CFG_ADDMODE_UNIQUE,
sizeof(uint32_t), (unsigned char *)&rc, NULL);
do {
rc = NC_NetStart(hCfg, networkOpen, networkClose, networkIPAddr);
} while(rc > 0);
/* Shut down the stack */
CfgFree(hCfg);
main_exit:
NC_SystemClose();
/* stop and delete the NDK heartbeat */
its.it_value.tv_sec = 0;
its.it_value.tv_nsec = 0;
rc = timer_settime(ndkHeartBeat, 0, &its, &oldIts);
rc = timer_delete(ndkHeartBeat);
DbgPrintf(DBG_INFO, "ndkStackThread: exiting ...\n");
return (NULL);
}
Constructing a Configuration using the DHCP Client Service(原文档中附代码)
本节检查上一节中调用的ndk.c中的initIp()函数。该函数告诉堆栈使用动态主机配置协议(DHCP)客户端服务来执行其IP地址配置。
由于DHCP提供IP地址,路由,域和域名服务器,因此您只需提供主机名。有关使用DHCP的更多详细信息,请参阅TI网络开发人员工具包(NDK)API参考指南(SPRU524)。
下面的代码执行以下操作:1。使用hostName为本地主机名添加配置条目,该名称声明为static char * hostName =“tisoc”;
2.设置dhcpc的元素,其结构类型为CI_SERVICE_DHCPC。TI网络开发人员套件(NDK)API参考指南(SPRU524)中描述了此结构。
3.添加指定要使用的DHCP客户端服务的配置条目。
static void initIp(void *hCfg)
{
CI_SERVICE_DHCPC dhcpc;
unsigned char DHCP_OPTIONS[] = { DHCPOPT_SUBNET_MASK };
/* Add global hostname to hCfg (to be claimed in all connected domains) */
CfgAddEntry(hCfg, CFGTAG_SYSINFO, CFGITEM_DHCP_HOSTNAME, 0,
strlen(hostName), (unsigned char *)hostName, NULL);
/* Use DHCP to obtain IP address on interface 1 */
/* Specify DHCP Service on IF specified by "IfIdx" */
memset(&dhcpc, 0, sizeof(dhcpc));
dhcpc.cisargs.Mode = CIS_FLG_IFIDXVALID;
dhcpc.cisargs.IfIdx = 1;
dhcpc.cisargs.pCbSrv = &serviceReport;
dhcpc.param.pOptions = DHCP_OPTIONS;
dhcpc.param.len = 1;
CfgAddEntry(hCfg, CFGTAG_SERVICE, CFGITEM_SERVICE_DHCPCLIENT, 0,
sizeof(dhcpc), (unsigned char *)&dhcpc, NULL);
}
Using a Statically Defined DNS Server (原文档中附代码)
DHCP客户端使用的配置系统区域可能很困难。当DHCP客户端正在使用时,它可以完全控制配置系统的系统信息部分中的前256个条目。在极少数情况下,与DHCP共享此空间可能很有用。
例如,假设网络应用程序需要手动将域名系统(DNS)服务器的IP地址添加到系统配置中。当不使用DHCP时,此代码很简单。要添加128.114.12.2的DNS服务器,将在配置构建过程中添加以下代码(在调用NC_NetStart()之前)。
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), (unsigned char *)&IPTmp, 0 );
请注意,示例应用程序中的CLIENT示例程序使用此代码的形式。现在,当使用DHCP客户端时,它会清除并重置其控制的配置部分的内容。这包括DNS服务器地址。因此,如果将上述代码添加到使用DHCP的应用程序中,则只要DHCP执行状态更新,就会清除该条目。
要与DHCP共享此配置空间(或读取DHCP配置的结果),必须使用DHCP状态回调报告代码。状态回调函数在第2.1.5.5节中介绍。当DHCP报告状态更改时,应用程序知道系统配置的DHCP部分已重置。
以下代码也出现在CLIENT示例程序中。当DHCP客户端正在使用时,此代码手动添加DNS服务器地址。请注意,此代码是标准服务回调函数的一部分,该函数在指定DHCP客户端服务时提供给配置。
Controlling NDK and OS Options via the Configuration
除了指定IP地址,路由和服务外,配置系统还允许您直接操作OS适配层和NDK的配置结构。OS网络开发人员工具包(NDK)API参考指南(SPRU524)的操作系统配置部分讨论了OS配置结构,NDK配置结构将在附录的配置堆栈部分中讨论。这些内部结构的配置界面将合并到“初始化和配置”部分中指定的单个配置API中。
尽管可以直接修改这两种配置结构中的值,但将参数添加到系统配置中有两个原因。首先,它为所有网络配置提供一致的API,其次,如果使用配置加载和保存功能,这些配置参数将与系统配置的其余部分一起保存。
作为设置OS配置选项的快速示例,以下代码对调试报告机制进行了更改。默认情况下,NDK生成的所有调试消息都将输出到CCStudio输出窗口。但是,可以调整OS配置以仅打印更高严重性级别的消息,或者完全禁用调试消息。
NDK附带的大多数示例应用程序都会将调试消息的打印阈值从INFO级别提高到警告级别。以下是它在源代码中的显示方式:
// 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(uint32_t), (unsigned char *)&rc, 0 );
Shutdown
堆栈可以通过两种方式关闭。第一种是在应用程序调用NC_NetStop()时发生的手动关闭。这里,函数的调用参数作为NC_NetStart()的返回值返回给NETCTRL线程。因此,对于示例代码,调用NC_NetStop(1)重新启动网络堆栈,而调用NC_NetStop(0)则关闭网络堆栈。
堆栈可以关闭的第二种方式是堆栈代码检测到致命错误。致命错误是高于配置中设置的致命阈值的错误。这种类型的错误通常表明堆栈继续运行是不安全的。发生这种情况时,堆栈代码调用NC_NetStop(-1)。然后由您决定接下来应该做什么。编码NC_NetStart()循环的方式决定了系统是否将关闭(如示例中所示),或者只是重新启动。
请注意,也可以禁用关闭的临界阈值。可以将以下代码添加到配置中以禁用与错误相关的关闭:
// We do not want the stack to abort on any error
uint32_t rc = DBG_NONE;
CfgAddEntry( hCfg, CFGTAG_OS, CFGITEM_OS_DBGABORTLEVEL,CFG_ADDMODE_UNIQUE, sizeof(uint32_t), (unsigned char *)&rc, 0 )
Saving the Configuration
构建配置后,应用程序可以将其保存到非易失性RAM中,以便在下次冷启动时重新加载。这在嵌入式系统中尤其有用,在嵌入式系统中,可以使用串行电缆,Telnet或HTTP浏览器在运行时修改配置。
如果使用XGCONF进行配置,XGCONF不会自动支持保存和重新加载配置。但是,在内部,构建* .cfg文件时,将填充Cfg *()C函数使用的相同配置数据库。您可能希望使用以下小节中的函数作为挂钩函数来保存使用XGCONF创建的配置,并在启动时从非易失性内存中重新加载。
要保存配置,请将其转换为线性缓冲区,然后将线性缓冲区保存到存储区。以下是配置保存操作的快速示例。请注意,假设MyMemorySave()函数将线性缓冲区保存到非易失性存储中。
int SaveConfig( void *hCfg ) {
unsigned char *pBuf;
int size;
// Get the required size to save the configuration
CfgSave( hCfg, &size, 0 );
if( size && (pBuf = malloc(size) ) )
{
CfgSave( hCfg, &size, pBuf );
MyMemorySave( pBuf, size );
Free( pBuf );
return(1);
}
return(0);
}
保存配置后,可以在启动时从非易失性存储器加载。对于最终的NetworkTest()示例,假设另一个Task在先前的执行中创建,编辑或保存了有效配置到某些存储介质。在此网络初始化例程中,所需的只是从存储加载配置并使用当前配置引导NDK。
对于此示例,假设函数MyMemorySize()返回存储的线性缓冲区中的配置大小,并且MyMemoryLoad()从非易失性存储中加载线性缓冲区。
int NetworkTest()
{
int rc;
void *hCfg;
unsigned char *pBuf;
Int size;
//
// THIS MUST BE THE ABSOLUTE 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(;;);
}
//
// First load the linear memory block holding the configuration
//
// Allocate a buffer to hold the information
size = MyMemorySize();
if( !size )
goto main_exit;
pBuf = malloc( size );
if( !pBuf )
goto main_exit;
// Load from non-volatile storage
MyMemoryLoad( pBuf, size );
//
// Now create the configuration and load it
//
// Create a new configuration
hCfg = CfgNew();
if( !hCfg ) {
printf("Unable to create configuration\n");
free( pBuf );
goto main_exit;
}
// Load the configuration (and then we can free the buffer)
CfgLoad( hCfg, size, pBuf );
mmFree( pBuf );
//
// Boot the system using this configuration
//
// 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 );
// Delete Configuration
CfgFree( hCfg );
// Close the OS
main_exit:
NC_SystemClose();
return(0);
}
NDK Initialization
在可以执行类似于示例的套接字应用程序之前,必须正确配置和初始化堆栈。为了便于标准初始化过程,并允许自定义,NDK中包含网络控制模块(NETCTRL)的源代码。NETCTRL模块是堆栈初始化和事件调度的中心。对NETCTRL操作的充分理解对于构建可靠的网络应用程序至关重要。本节介绍如何在网络应用程序中使用NETCTRL。第3章提供了有关NETCTRL如何工作以及如何调整的解释。
TI网络开发人员套件(NDK)API参考指南(SPRU524)的第4章详细介绍了NDK的初始化过程。本节与该文档的NDK软件目录中描述的初始化过程密切相关。在这里,我们用更实际的倾斜描述信息。与此处提到的功能的确切API有关的编程人员应参考TI网络开发人员工具包(NDK)API参考指南(SPRU524)以获得更精确的描述。
The NETCTRL Task Thread
如果使用Cfg *()API调用进行配置,则必须创建一个包含对NC_NetStart()调用的Task线程,该调用依次运行网络调度程序函数。NSP示例应用程序,在其主C源文件中提供此线程。
如果使用XGCONF进行配置,则会自动生成此线程,并且调用NC_NetStart()的代码位于生成的C文件中(例如,evmOMAPL138客户端示例的client_p674.c)。
此Task线程(称为调度程序线程)是几乎所有NETCTRL活动都发生的线程。该线程充当程序的入口点并执行初始化操作。之后,它成为NETCTRL调度程序线程。因此,在关闭堆栈之前,不会将此线程的控制权返回给调用方。应用程序任务 - 面向网络或其他 - 不在此线程内执行。
Pre-Initialization
如果使用Cfg *()API调用进行配置,则应用程序必须在调用任何其他堆栈API函数之前调用主初始化函数NC_SystemOpen()。这初始化堆栈和所有堆栈组件使用的内存环境。两个调用参数Priority和OpMode指示调度程序应如何执行。例如,NSP中包含的示例应用程序包含以下代码:
rc = NC_SystemOpen( NC_PRIORITY_LOW, NC_OPMODE_INTERRUPT );
if( rc ) {
printf("NC_SystemOpen Failed (%d)\n",rc);
for(;;);
}
Invoking New Network Tasks and Services
可以在NDK配置中指定一些标准网络服务;它们由NETCTRL模块自动加载和卸载。其他服务,包括应用程序员编写的服务,应该从回调函数启动。
如果使用Cfg *()API调用进行配置,则可以使用提供给NC_NetStart()的Start回调函数来添加回调函数。作为网络启动回调的示例,下面的NetworkStart()函数通过调用open函数来打开用户SMTP服务器应用程序以创建主应用程序线程。
static SMTP_Handle hSMTP;
//
// NetworkStart
//
// This function is called after the configuration has booted
//
static void NetworkStart( ) {
// Create an SMTP server Task
hSMTP = SMTP_open( );
}
上面的代码启动了一个不需要进一步监视的自包含应用程序,但是在系统关闭时必须关闭应用程序。这是通过NetworkStop()回调函数完成的。因此,NetworkStop()函数必须撤消在NetworkStart()中完成的操作。
//
// NetworkStop
//
// This function is called when the network is shutting down
//
static void NetworkStop()
{
// Close our SMTP server Task
SMTP_close( hSMTP );
}
上面的示例假定无论堆栈是否具有本地IP地址,都可以启动网络调度程序任务。对于侦听通配符地址为0.0.0.0的服务器,情况也是如此。在极少数情况下,任务初始化可能需要IP地址,或者可能需要某种设备类型的IP地址。在这些情况下,NetworkIPAddr()回调函数会向应用程序发出可以安全启动的信号。
以下示例说明了对NetworkIPAddr()回调的调用参数。请注意,可以调用IFIndexGetHandle()和IFGetType()函数来获取添加或删除新IP地址的设备类型(HTYPE_ETH或HTYPE_PPP)。此示例仅打印一条消息。此回调函数的最常见用途是同步需要在执行之前安装本地IP地址的网络任务。
//
// NetworkIPAddr
// This function is called whenever an IP address binding is
// added or removed from the system.
//
static void NetworkIPAddr( IPN IPAddr, uint32_t IfIdx, uint32_t 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, (unsigned char)(IPTmp>>24)&0xFF,
(unsigned char)(IPTmp>>16)&0xFF, (unsigned char)(IPTmp>>8)&0xFF, (unsigned char)IPTmp&0xFF );
}
Network Startup
如果使用Cfg *()API调用进行配置,则应用程序必须在加载配置后调用NETCTRL函数NC_NetStart()以调用网络调度程序。除了配置句柄外,该函数还需要三个额外的回调指针参数;指向Start回调函数,Stop函数和IP Address Event函数的指针。
前两个回调函数只调用一次。系统初始化并准备好执行网络应用程序时,将调用Start回调(请注意,可能尚未安装本地IP网络地址)。系统关闭时会调用Stop回调,表示堆栈很快就无法执行网络应用程序。第三个回调可以多次调用。在系统中添加或删除本地IP地址时调用它。这可用于检测新的DHCP或PPP地址事件,或仅用于记录本地IP地址以供本地网络应用程序使用。在系统关闭之前,对NC_NetStart()的调用不会返回,然后它会返回一个关闭代码作为其返回值。系统如何关闭对于确定是否应重新启动堆栈可能很重要。例如,可能需要重新启动以加载新配置。NC_NetStart()的返回码可用于确定是否应再次调用NC_NetStart()(从而执行重启)。
举一个简单的例子,如果堆栈以大于零的返回码关闭,则以下代码使用当前配置句柄不断重新启动堆栈。通过调用NC_NetStop()关闭堆栈时设置返回码。
//
// 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 );
Adding Status Report Services
配置系统还可用于调用NETTOOLS库中的标准网络服务。使用NDK的网络应用程序可用的服务将在TI网络开发人员工具包(NDK)API参考指南(SPRU524)的第4章中详细讨论。本节总结了该章中描述的服务。
使用NETTOOLS库时,会引入NETTOOLS状态回调函数。此回调函数跟踪通过配置启用的服务的状态。状态回调函数有两个级别。第一个回调是由NETTOOLS服务完成的。它在服务状态发生变化时调用配置服务提供程序。然后,配置服务提供程序将自己的状态添加到信息中,并回调应用程序的回调函数。当应用程序将服务添加到系统配置时,将提供指向应用程序回调的指针。
如果使用Cfg *()API调用进行配置,则所有示例中使用的基本状态回调函数如下所示:
//
// 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( uint32_t Item, uint32_t Status, uint32_t Report, void *h ) {
printf( "Service Status: %-9s: %-9s: %-9s: %03d\n",
TaskName[Item-1], StatusStr[Status], ReportStr[Report/256], Report&0xFF );
}
请注意,各个服务的名称列在TaskName []数组中。此顺序由配置系统中服务项的定义指定,并且是常量。请参阅文件INC / NETTOOLS / NETCFG.H以获取物理声明。
请注意,定义主报告代码的字符串列在ReportStr []数组中。此顺序由NETTOOLS标准报告机制指定并且是常量。请参阅文件INC / NETTOOLS / NETTOOLS.H以获取物理声明。
请注意,定义Task状态的字符串在StatusStr []数组中定义。此顺序由配置系统中标准服务结构的定义指定。请参阅文件INC / NETTOOLS / NETCFG.H以获取物理声明。
此回调函数打印的最后一个值是Report中传递的值的最低有效8位。此值特定于相关服务。对于大多数服务,此值是多余的。通常,如果服务成功,则报告“完成”,如果服务失败,则报告“故障”。对于从未完成的服务(例如,在IP租约处于活动状态时继续运行的DHCP客户端),报告的高字节表示正在运行,并且必须使用特定于服务的低字节来确定当前状态。
例如,使用DHCP客户端服务时,报告的8个最低有效位中返回的状态代码为:
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客户端特定的报告代码在INC / NETTOOLS / INC / DHCPIF.H中定义。在大多数情况下,除了以下情况外,您不必检查状态报告代码,直到这个详细程度。使用DHCP客户端配置堆栈时,DHCP客户端控制CFGTAG_SYSINFO标记空间的前256个条目。这些条目对应于256个DHCP选项标记。应用程序可以检查DHCPCODE_IPADD或DHCPCODE_IPRENEW返回代码,以便它可以读取或更改DHCP客户端获取的信息。这将在第2.1.4.1.2节中进一步讨论。
Adding NDK Hooks Using ACD Support
使用定义的_INCLUDE_ACD_SUPPORT宏重建NDK可以支持一些可选的钩子函数,这些函数可以让用户在堆栈的IP和ARP层中获得更多控制。这些钩子支持那些希望将地址冲突检测协议(ACD,RFC5227)添加到NDK的人。
但是,钩子也可以用于其他目的。这些挂钩可用于执行以下操作:
•在IP地址绑定到堆栈之前,对IP地址执行运行时验证,这些IP地址可以静态设置或由DHCP配置。
•监控传入的地址解析协议(ARP)数据包。
使用_INCLUDE_ACD_SUPPORT重建NDK时,它会在IP层和ARP层的关键位置调用用户定义的挂钩函数(如果它们是非NULL)。您可以按照以下小节中的说明定义此类功能并启用它们。
Defining an IP Address Validation Function
要使用NDK的钩子函数调用来验证潜在的IP地址,请创建并定义一个接受IP地址的钩子函数。如果IP地址可以使用,该函数必须返回成功,如果不能使用,则返回-1。该函数的签名定义如下:
/* Hook function to check IP address per protocols like ACD RFC5227 */
typedef int(*NS_ValidateAddress)(IPN ipAddr);
Registering the IP Address Validation Function
一旦写入IP地址验证功能,就可以将其传递给以下API,以便将其注册到堆栈:
extern void NS_setAddrHook(NS_ValidateAddress fxn);
然后,IP层在绑定潜在IP地址之前调用验证函数“fxn”(如果它不是NULL)。如果验证函数返回成功,则堆栈绑定IP地址。如果不是,则绑定失败。
必须在堆栈初始化代码的早期在适当的位置调用NS_setAddrHook()函数。特别是,应该在调用NC_NetStart()之前在启动代码中调用它。为了在堆栈初始化期间进行此调用,有必要修改NDK堆栈线程的代码以添加调用。
rc = NC_SystemOpen(NC_PRIORITY_LOW, NC_OPMODE_INTERRUPT);
if (rc) {
System_abort("NC_SystemOpen Failed (%d)\n");
}
/* Create and build the system configuration from scratch. */
hCfg = CfgNew();
if (!hCfg) {
System_printf("Unable to create configuration\n");
goto main_exit;
}
/* ... */
/* call NS_setAddrHook here */
do {
rc = NC_NetStart(hCfg, ti_ndk_config_Global_NetworkOpen,
ti_ndk_config_Global_NetworkClose,
ti_ndk_config_Global_NetworkIPAddr);
} while (rc > 0 || rc == UAT_LINKDOWN);
Defining an ARP Packet Monitoring Function
类似地,还可以定义和注册钩子函数以监视接收的ARP分组。要使用NDK的挂钩函数调用来监视收到的ARP数据包,请定义一个接受ARP数据包的挂钩函数。在驱动程序Rx处理期间将调用钩子函数。该函数可以执行此钩子函数中所需的任何操作。例如,它可以扫描ARP数据包的数据或对数据包进行排队(使用SB队列)以供以后处理。
该函数的定义如下:
/* Hook function to give access to received ARP packets */
typedef void(*LLI_ReportARP)(void * data);
Registering the ARP Packet Monitoring Function
一旦编写了ARP数据包监控功能,就可以将其传递给以下API,以便将其注册到堆栈:
extern void LLI_setARPHook(LLI_ReportARP fxn);
然后,ARP层将为堆栈接收的ARP数据包调用验证函数“fxn”(如果它不为NULL)。
调用LLI_setARPHook()函数的步骤与第2.1.6.2节中的步骤类似。
Creating a Task
使用NDK的应用程序可以通过以下任一方式创建任务线程:
•按照TI网络开发人员工具包(NDK)API参考指南(SPRU524)中的说明调用TaskCreate()。
•按照TI处理器wiki的POSIX线程(pthread)支持页面中的说明调用POSIX pthread API。请注意,如果直接使用pthreads,则代码需要通过在开头调用fdOpenSession()并在结尾调用fdCloseSession()来初始化文件描述符表(参见第2.3.1节)。
在内部,TaskCreate()使用POSIX pthreads,因此您的应用程序实际上使用POSIX去进行任务创建方法。
基于优先级的排除使您的应用程序仅使用已配置的NDK优先级范围内的优先级(OS_TASKPRILOW到OS_TASKPRIHIGH)非常重要。将线程设置为比NDK的高优先级线程级别更高的优先级可能会破坏系统并在线程调用任何与堆栈相关的函数时导致不可预测的行为。
以下示例使用TaskCreate()创建具有普通优先级的Task:
void *taskHandle = NULL;
taskHandle = TaskCreate( entrypoint, "TaskName", OS_TASKPRINORM, stacksize, arg1, arg2, arg3 );
以下示例使用pthread API创建供NDK使用的任务。该线程具有普通优先级,堆栈大小为2048
#include <pthread.h>
#include <ti/ndk/inc/netmain.h>
void mySocketThreadFxn()
{
fdOpenSession((void *)pthread_self());
/* do socket calls */
fdCloseSession((void *)pthread_self());
}
void createNdkThread()
{
int status;
pthread_attr_t pthreadAttrs;
struct sched_param schedParams;
pthread_t task;
pthread_attr_init(&pthreadAttrs);
schedParams.sched_priority = OS_TASKPRINORM;
status = pthread_attr_setschedparam(&pthreadAttrs, &schedParams);
if (status != 0) {
/* error */
}
status = pthread_attr_setstacksize(&pthreadAttrs, 2048);
if (status != 0) {
/* error */
}
status = pthread_attr_setdetachstate(&pthreadAttrs, PTHREAD_CREATE_DETACHED);
if (status != 0) {
/* error */
}
status = pthread_create(&task, &pthreadAttrs, mySocketThreadFxn, NULL);
if (status != 0) {
/* error */
}
pthread_attr_destroy(&pthreadAttrs);
if(status!= 0)
{
/ * error * /
}
}
Initializing the File Descriptor Table
使用套接字或文件API的每个Task线程必须分配文件描述符表并将该表与Task句柄相关联。TI网络开发人员套件(NDK)API参考指南(SPRU524)中详细介绍了此过程。要实现这一点,必须在使用任何面向文件描述符的函数之前执行对fdOpenSession()的调用,然后在不再需要这些函数时调用fdCloseSession()。
void *mySocketsFunction(void *arg)
{
fdOpenSession((void *)pthread_self());
/* do socket calls */
fdCloseSession((void *)pthread_self());
return (NULL);
}
Application Debug and Troubleshooting
虽然没有即时或简单的方法来调试NDK应用程序,但以下部分提供了一些潜在问题区域的快速描述。其中一些主题也在文档的其他地方讨论。
解决常见问题
NDK最常见的支持请求之一是处理无法发送或接收网络数据包的问题。这也可能采取丢包或一般性能不佳的形式。这种行为有很多原因。有关潜在的调度问题,请参见第2.2.7.2节。还建议应用程序员完全理解NETCTRL模块的工作原理。为此,请参阅第3章。
这是一个快速列表。如果使用XGCONF进行配置,则不会发生许多潜在的配置问题。
All socket calls return “error” (-1)
•确保在使用套接字之前在Task中调用fdOpenSession(),并在Task终止时调用fdCloseSession()。
No link indication, or will not re-link when cable is disconnected and reconnected.
•确保配置中有一个Timer对象,每100 ms调用一次驱动程序函数llTimerTick()。
Not receiving any packets – ever
•通过以非阻塞方式调用NDK_recv(),fdPoll()或fdSelect()来轮询数据时,请确保没有任何调度问题。当NETCTRL调度程序以低优先级运行时,不允许网络应用程序在不阻塞的情况下进行轮询。尝试以高优先级运行调度程序(通过NC_SystemOpen())。
•NDK假定存在一些L2缓存。如果DSP或ARM配置为内部存储器而没有任何剩余的L2缓存,则NDK驱动程序将无法正常工作。
Performance is sluggish. Very slow ping response.
•确保配置中有一个Timer对象,每100 ms调用一次驱动程序函数llTimerTick()。
•如果在中断模式下移植以太网驱动程序并运行NETCTRL,请确保您的设备正确检测到中断。确保中断极性正确。
UDP application drops packets on NDK_send() calls.
•如果发送到新IP地址,则第一次发送可能会在ARP层中保留,而堆栈将确定数据包目标的MAC地址。在此模式下,后续发送将被丢弃。
•使用UDP并一次发送多个数据包时,请确保有足够的数据包缓冲区可用(参见第4.3.1节)。
•确认您没有任何计划问题。尝试以高优先级运行调度程序(通过NC_SystemOpen())。
UDP application drops packets on NDK_recv() calls.
•确保有足够的数据包缓冲区(参见第4.3.1节)。
•确保UDP的数据包阈值足够高,以保存在NDK_recv()调用之间接收的所有UDP数据(请参阅NDK程序员参考指南中的CFGITEM_IP_SOCKUDPRXLIMIT)。
•确认您没有任何计划问题。尝试以高优先级运行调度程序(通过NC_SystemOpen())。
•以太网设备驱动程序可能会丢弃数据包。某些设备驱动程序具有可调整的RX队列深度,而其他驱动程序则没有有关更多详细信息,请参阅以太网设备驱动程序的源代码(硬件平台的NDK支持包中提供了设备驱动程序源代码)。
Pings to NDK target Fails Beyond 3012 Size
NDK的默认配置允许重新组装最大为“3012”字节的数据包。为了能够ping更大的大小,需要重新配置堆栈,如下所示:
•更改“pbm.c”文件中的“MMALLOC_MAXSIZE”定义。(即#define MMALLOC_MAXSIZE 65500)并重建库。
•在全局配置的“缓冲区”选项卡中增加“内存管理器缓冲区页面大小”。
•增加IP模块配置的最大IP重组大小属性。
Sending and Receiving UDP Datagrams over MTU Size
发送和接收UDP数据报的大小取决于以下NDK配置选项,套接字选项和OS适配层定义:
•NDK配置选项:
- 增加IP模块套接字配置的最小发送大小属性。请参阅TI网络开发人员套件(NDK)API参考指南(SPRU524)。
- 增加IP模块套接字配置的最小读取大小属性。
- 如果使用Cfg *()API调用进行配置,则可以使用以下C代码配置这些IP模块属性:
uint32_t tmp = 65500;
// configure NDK
CfgAddEntry(hCfg, CFGTAG_IP, CFGITEM_IP_IPREASMMAXSIZE,
CFG_ADDMODE_UNIQUE, sizeof(uint32_t), (unsigned char*) &tmp, 0);
CfgAddEntry(hCfg, CFGTAG_IP, CFGITEM_IP_SOCKUDPRXLIMIT,
CFG_ADDMODE_UNIQUE, sizeof(uint32_t), (unsigned char*) &tmp, 0);
// set socket options
NDK_setsockopt(s, SOL_SOCKET, SO_RCVBUF, &tmp, sizeof(int) );
NDK_setsockopt(s, SOL_SOCKET, SO_SNDBUF, &tmp, sizeof(int) );
•套接字选项:
- SO_SNDBUF:参见TI网络开发人员套件(NDK)API参考指南(SPRU524)
- SO_RCVBUF - 请参阅TI网络开发人员套件(NDK)API参考指南(SPRU524)
•OS适配层定义:
- 更改“pbm.c”文件中的“MMALLOC_MAXSIZE”定义。(即#define MMALLOC_MAXSIZE 65500)并重建库
- 在Global配置的Buffers选项卡中增加Memory Manager Buffer Page Size。
- 如果使用Cfg *()API调用进行配置,则可以编辑pbm.c文件中的MMALLOC_MAXSIZE定义和mem.c文件中的RAW_PAGE_SIZE定义。然后在/ ti / ndk / os / lib中重建相应的OS Adaptation Layer库。
Timestamping UDP Datagram Payloads
NDK允许应用程序更新UDP数据报的有效负载。这种情况的典型用法是更新数据报的时间戳信息。这样,发送和接收端可以根据系统的运行时间特性的变化更准确地调整传送延迟。
在发送端:
•应用程序可以使用NDK_setsockopt()函数为每个插槽注册一个调出功能。
•在将数据报插入驱动程序的传输队列之前,堆栈会调用call-out函数。
•呼出功能负责更新标头中的UDP校验和信息。
•以下代码部分是如何控制它的示例:
void myTxTimestampFxn(unsigned char *pIpHdr) {
...
}
NDK_setsockopt(s, SOL_SOCKET, SO_TXTIMESTAMP, (void*) myTxTimestampFxn, sizeof(void*));
在接收端:
•应用程序可以使用EtherConfig()函数在每个接口的基础上注册调出功能。它在“netctrl.c”的NC_NetStart()函数中设置。
•在处理数据包之前,堆栈调度程序会调用call-out函数。
•呼出功能负责更新标头中的UDP校验和信息。
•以下代码部分是如何控制它的示例:
void myRcvTimestampFxn(unsigned char *pIpHdr) {
...
}
EtherConfig( hEther[i], 1518, 14, 0, 6, 12, 4, myRcvTimestampFxn);
一般来说
•请勿尝试调整定时器功能频率。确保它每100毫秒调用llTimerTick()。
•注意内存不足的情况。这些可以通过某些功能的返回来检测,但也会在启用消息时打印出警告消息。这些消息包含内存不足的首字母缩写OOM。(内存不足可能是由许多因素引起的,但NDK中最常见的原因是在不使用SO_LINGER套接字选项的情况下非常快速地创建和关闭TCP套接字。这会使许多套接字处于TCP timewait状态,从而耗尽了暂存内存解决方案是使用SO_LINGER套接字选项。)
Debug Messages
TI-RTOS内核的调试消息使用System_printf()API处理,该API由XDCtools提供,供TI-RTOS使用。
目前不支持FreeRTOS的调试输出,因此后面的小节不适用于使用FreeRTOS的应用程序。您可以修改OS Adaptation Layer源代码以根据需要添加其他类型的程序输出。
Controlling Debug Messages
TI-RTOS内核的调试消息还包括关联的严重性级别。这些级别是DBG_INFO,DBG_WARN和DBG_ERROR。严重性级别用于两个目的。首先,它确定是否将打印调试消息,其次,它确定调试消息是否将导致NDK关闭。
默认情况下,将打印所有调试消息,并且级别为DBG_ERROR的消息会导致堆栈关闭。可以在2.2.10节中描述的配置中修改此行为。或者,您可以通过第2.1.4.2节和第2.1.4.3节中所述的系统配置对其进行修改。另请参阅TI网络开发人员套件(NDK)API参考指南(SPRU524)。
Interpreting Debug Messages
以下是堆栈操作期间可能发生的一些TI-RTOS内核调试消息的列表,以及最常见的相关原因。
TCP: Retransmit Timeout: Level DBG_INFO
此消息由TCP在向网络对等方发送数据包时生成,并且对等方未在预期的时间内回复。这可以是任何事情;对等体已经关闭,网络繁忙,网络数据包被丢弃或损坏,等等。
FunctionName:Buffer OOM:Level DBG_WARN
当出现意外的内存不足情况时,某些模块会生成此消息。堆栈有一个内部资源恢复例程来帮助处理这些情况;但是,大量的这些消息也可能表明没有足够的大块内存可用,或者存在内存泄漏。有关更多详细信息,请参阅本节中有关内存管理器报告的说明。
mmFree: Double Free: Level DBG_WARN
在未标记为已分配的内存块上调用mmFree()函数时,会出现双重释放消息。这可能是由于为同一内存物理调用两次mmFree()引起的,但更常见的是由内存损坏引起的。有关可能的原因,请参阅第2.4.3节。
FunctionName: HTYPE nnnn: Level DBG_ERROR
此消息仅由堆栈的强检查版本生成。将句柄传递给不是正确句柄类型的函数时会导致它。由于堆栈的面向对象特性对网络应用程序编写器是隐藏的,因此不应发生此错误。如果它不是由尝试调用内部堆栈函数引起的,那么很可能是内存损坏的结果。有关可能的原因,请参阅本节中有关内存损坏的说明。
mmAlloc: PIT ???? Sync: Level DBG_ERROR
该消息由暂存存储器分配系统生成。PIT是页面信息表的首字母缩写。表同步错误只能由内存损坏引起。有关可能的原因,请参阅第2.4.3节。
PBM_enq: Invalid Packet: Level DBG_ERROR
此消息由OS适配层中的数据包缓冲区管理器(PBM)模块驱动程序生成。
当PBM模块最初分配其数据包缓冲池时,它会使用幻数标记每个数据包缓冲区。在正常操作期间,数据包被推入和弹出各种队列。在每次按下操作时,将检查数据包的幻数。当幻数无效时,将显示此消息。使用非复制套接字API扩展时,可能会将无效数据包引入系统,但更常见的原因是内存损坏。有关可能的原因,请参阅本节中有关内存损坏的说明。
Memory Corruption
NDK调试消息可能会发生内存损坏错误。这是因为很容易破坏缓存设备上的内存。NDK中包含的大多数示例程序都使用完整的L2缓存运行。在此模式下,对CPU内部存储器范围的任何读或写访问都可能导致高速缓存损坏,从而导致内存损坏。由于内部存储器范围从地址0x00000000开始,因此使用完全缓存时,NULL指针可能会导致问题。
要检查是否由NULL指针引起损坏,请更改缓存模式以使用较少的缓存。当有一些内部存储器可用时,对地址0x0的读取或写入不会导致高速缓存损坏(应用程序仍可能无法正常工作,但错误消息应该停止)。
追踪任何类型的缓存损坏的另一种方法是中断CPU读取或写入整个缓存范围。Code Composer Studio能够捕获对一系列内存的读取或写入,但两者都无法同时检查。因此,可能需要进行几次试验。
当然,内存损坏可能与堆栈无关。它可能是一个狂野的指针。但是,由于破坏缓存可能会破坏整个系统的内存,因此缓存是第一个启动的地方。
Program Lockups
大多数锁定条件是由任务堆栈大小不足引起的。例如,在编写HTTP CGI函数时,CGI函数Task线程只有大约5000字节的总任务堆栈。因此,不建议使用大量堆栈。通常,请勿使用以下代码:
myTask()
{
char TempBuffer[2000];
myFun( TempBuffer );
}
但相反,请使用以下内容:
myTask()
{
char *pTempBuf;
pTempBuf = Memory_alloc( NULL, 2000, 0, &eb )
if (pTempBuf != NULL)
{
myFun( pTempBuf );
Memory_free( NULL, pTempBuf, 2000 );
}
}
如果调用内存分配函数的速度开销过高,请考虑使用外部缓冲区。
这只是一个例子,稍加考虑你可以消除所有可能的堆栈溢出条件,并消除程序锁定的可能性。
Memory Management Reports
管理NDK中暂存内存的内存管理器具有内置报告系统。它跟踪临时存储器的使用(调用mmAlloc()和mmFree()),并跟踪对分配的大块存储器的调用(调用mmBulkAlloc()和mmBulkFree())。请注意,批量分配函数只需调用malloc()和free()。可以通过调整内存管理器来更改此行为。
内存报告如下所示。它列出了每个大小桶分配的最大块数,对malloc和free的调用次数,以及已分配内存的列表。示例报告如下所示:
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)
1 blocks alloced in 512 byte page
38 blocks alloced in 48 byte page
18 blocks alloced in 96 byte page
8 blocks alloced in 128 byte page
12 blocks alloced in 256 byte page
12 blocks alloced in 256 byte page
这里,条目18:96(56%)表示最多在96字节桶中分配了18个块。内存管理器上的页面大小为3072,因此使用了56%的页面。条目21504/46080表示最多分配了21,504个字节,总共有46,080个字节可用。
条目mmAlloc:61347036/0/61346947表示对mmAlloc()进行了61,347,036次调用,其中0次失败,并且对mmFree()进行了61,346,947次调用。请注意,在任何时候,对mmAlloc的调用加上失败必须等于对mmFree的调用加上任何未完成的分配。因此,在报告为mmAlloc:n1 / n2 / n3的最终报告中,n1 + n2应等于n3。如果没有,则存在内存泄漏。
使用大多数示例应用程序附带的telnet控制台程序时,有几种方法可以获取内存报告。控制台'mem'命令打印出当前报告,但更重要的是,控制台'shutdown'命令关闭堆栈并打印出最终报告。如果根据本文档中的规范创建和销毁所有网络应用程序,则最终报告中不应检测到内存泄漏。调用以获取内存报告的函数定义如下。
mmCheck – Generate Memory Manager Report
Network Control Functions
Introduction to NETCTRL Source
History
NETCTRL模块最初是推荐的初始化和调度方法来执行NDK。虽然大多数都很简单,但这个代码成了标最终,它被分离到NETCTRL库中。
NETCTRL模块是NDK的中心,因为它将HAL和OS适配层连接到NDK。它控制初始化以及如何在堆栈内调度事件。了解NETCTRL模块的工作原理有助于您调整DSP或ARM网络应用程序以获得理想的性能。
NETCTRL Source Files
NETCTRL库的源代码由位于/ ti / ndk / netctrl目录下的两个C文件组成:
NETCTRL.C网络控制(初始化和调度)模块
NETSRV.C配置服务模块(系统配置服务提供者)
有两个包含文件与/ INC / NETCTRL目录中的NETCTRL相关联:
NETCTRL.H NETCTRL的接口规范
NETSRV.H NETSRV的接口规范
Main Functions
NETCTRL.C源模块包含具有NC_前缀的所有函数的源代码。NETCTRL模块的功能有三个基本部分。
NETCTRL.C的第一个功能是在调用任何其他堆栈函数之前执行必要的系统初始化和关闭。这些函数声明为NC_SystemOpen()和NC_SystemClose()。
NETCTRL.C的第二个功能是执行启动堆栈功能所需的驱动程序环境初始化和配置引导程序。此启动函数及其关闭对应项声明为NC_NetStart()和NC_NetStop()。
从调用者隐藏的NETCTRL.C的最后一个功能是实现堆栈的事件调度,它是堆栈操作的中心。
NETSRV.C模块包含引导堆栈上所有服务的代码。此代码获取存储在堆栈配置中的内容,并实现必要的堆栈功能以使配置保持最新。当配置中的活动项更改时,NETSRV模块中的代码将在NDK中执行该更改。
Additional Functions
TI网络开发人员套件(NDK)API参考指南(SPRU524)中未记录一些其他NETCTRL功能。这些函数是NC_BootComplete()和NC_IPUpdate()。
它们都是从NETSRV模块调用的。
NC_NetStart()函数通过创建一个入口点为NS_BootTask()(来自NETSRV.C)的引导线程来启动配置引导过程。配置引导完成后,配置引导线程将调用NC_BootComplete()函数。它向NETCTRL发出信号,它现在可以调用由应用程序传递给NC_NetStart()的NetworkStart()应用程序回调。从NC_BootComplete()返回时,引导线程终止。因此,应用程序员可以控制NetworkStart()回调线程,但不建议这样做。
当向系统添加地址或从系统中删除地址时,NETSRV会调用IP地址更新功能。然后,此函数调用最初传递给NC_NetStart()的NetworkIPAddr()应用程序回调。
Booting and Scheduling
第2.1.5节讨论了使用网络控制(NETCTRL)模块。本节介绍主NETCTRL模块的内部源代码和事件调度程序的操作。
堆栈事件调度程序是调用堆栈以处理数据包和计时器事件的例程。调度程序从NC_NetStart()内部调用,直到堆栈关闭才返回,这解释了为什么NC_NetStart()函数在系统关闭且调度程序终止之前不会返回应用程序。
NC_NetStart()的基本流程如下:
NC_NetStart()
{
Initialize_Devices();
CreateConfigurationBootThread() ;
NetScheduler();
CloseConfiguration();
CloseDevices();
}
在上面列出的NC_NetStart()的功能阶段中,最关心的两个是创建引导线程和网络事件调度程序的实现。
引导线程由名为NETSRV.C的NETCTRL库中的第二个C模块处理。此名称是Network Service Manager的缩写。NETSRV模块作为配置服务提供程序挂接到配置系统。配置系统模块只是一个活动数据库。相反,网络服务模块将配置条目转换为实际的NDK对象。可以改变服务模块以适应特定需要。这可能涉及为配置系统创建自定义配置标记。但是,完全理解NETSRV中的代码需要基本了解TI网络开发人员工具包(NDK)API参考指南(SPRU524)中讨论的几乎所有API函数。
您应该最关心NetScheduler()函数,因为此调度程序运行NDK。它查找需要由NDK处理的事件,并执行开始处理所需的工作。
NETCTRL Scheduler
Scheduler Overview
NETCTRL调度程序代码是一个名为NetScheduler()的无限循环函数,出现在源文件NETCTRL.C的末尾。它从低级设备驱动程序中查找活动事件,并在检测到事件时执行操作。当通过外部调用NC_NetStop()设置静态变量时,循环终止。
尽管NDK提供了一个可重入的环境,但堆栈的核心并不是可重入的。必须保护部分代码不被重入调用访问。该软件不是使用阻止所有其他任务执行的关键部分,而是定义称为内核模式的操作模式。定义内核模式,使得在任何给定时间只有一个Task可以处于内核模式。它不会阻止运行不使用NDK的任务。这为堆栈提供了保护,而不会影响不相关代码的执行。定义了两个函数来进入和退出内核模式,llEnter()和llExit()。它们是OS适配层的一部分,将在4.2.2节中详细讨论。简而言之,必须在调用堆栈之前调用llEnter(),并且在完成调用堆栈函数时必须调用llExit()。
调度程序循环的基本流程可以通过以下伪代码汇总:
static void NetScheduler()
{
SetSchedulingPriority();
while( !NetHaltFlag )
{
WaitOrPollForEvents();
ServiceDeviceDrivers();
// Process current events in Kernel Mode
if( StackEvents )
{
// Enter Kernel Mode
llEnter();
ServiceStackEvents();
// Exit Kernel Mode llExit();
}
}
}
以下各节依次介绍每个突出显示的功能。请注意,代码将继续运行,直到设置NetHaltFlag。当应用程序调用NC_NetStop()函数时,将设置此标志。
Scheduling Options
运行调度程序有三种基本方法。它们可以被视为三种操作模式:
1.调度程序以低优先级运行,仅在有网络事件要处理时运行。
2.调度程序以低优先级连续运行,轮询设备驱动程序以查找事件。
3.调度程序运行高优先级,但仅在有网络事件要处理时才运行。
运行调度程序的最佳方式取决于应用程序和系统体系结构。
模式1是运行NDK的最有效方式。这里,调度程序循环以低优先级运行。这使得可能具有实时要求的应用程序优先于网络,其中实时限制更加宽松。此外,调度循环仅在存在网络相关活动时运行;因此,也可以使用空闲循环。
当阻止设备驱动程序使用中断时,使用模式2。这对于实时任务最佳,但对网络性能最差。由于调度程序线程连续运行,因此它也可以防止使用空闲循环。这是NETCTRL在使用需要轮询的设备驱动程序时必须使用的模式。
模式3是最像Unix的环境。这里,网络调度程序任务的运行优先级高于系统中的任何其他网络任务。只要检测到新的网络相关事件,就会运行堆栈,从而可能会使用堆栈抢占其他任务。这是保持网络环境最新的最佳方法,而不会限制网络应用程序的编写方式。
当应用程序首次调用NC_SystemOpen()时,将设置优先级和轮询或中断驱动的调度。这将在第2.1.5.2节和NDK程序员参考指南中进一步讨论。
Scheduler Thread Priority
NetScheduler()实际实现的第一行包括以下代码:
// Set the scheduler priority
TaskSetPri(TaskSelf(), SchedulerPriority);
此代码更改调用NC_NetStart()的Task线程的优先级,以便有一个控制点来设置调度程序优先级。使用的优先级是传递给NC_SystemOpen()函数的优先级。这将在第2.1.5.2节和NDK程序员参考指南中进一步讨论。
调度程序优先级(相对于网络应用程序线程优先级)会影响网络应用程序的编程方式。例如,当以低优先级运行调度程序时,网络应用程序无法通过以非阻塞方式连续调用NDK_recv()来轮询数据。这是因为如果应用程序线程永远不会阻塞,则网络调度程序线程永远不会运行,并且NDK永远不会处理传入的数据包。
Tracking Events with STKEVENT
如前所述,NETCTRL模块是堆栈与HAL层中的设备驱动程序之间的接口。在旧版本的NDK中,设备驱动程序通过全局信号量向NETCTRL模块发出信号。为了稍微改进这个过程,简单的信号量被封装到一个名为STKEVENT的对象中。
从设备驱动程序的角度来看,此事件对象是一个传递给名为STKEVENT_signal()的函数的句柄。实际上,这个函数只是一个在STKEVENT类型结构上运行的MACRO。NETCTRL模块直接在此结构上运行。STKEVENT结构定义如下:
// Stack Event Object
typedef struct _stkevent {
void *hSemEvent;
uint32_t EventCodes[STKEVENT_NUMEVENTS];
} STKEVENT;
#define STKEVENT_NUMEVENTS 5
#define STKEVENT_TIMER 0
#define STKEVENT_ETHERNET 1
#define STKEVENT_SERIAL 2
#define STKEVENT_LINKUP 3
#define STKEVENT_LINKDOWN 4
该结构有两个部分,一个信号量句柄和一组事件。每个驱动程序通过在EventCode []数组中为其事件类型设置一个标志来发出事件信号,然后可选地用信号通知事件信号量。只有当驱动程序检测到中断条件时才会发出信号。如果在驱动程序轮询期间检测到事件(定期轮询或仅轮询驱动程序时为常量),则设置事件,但不发信号通知信号量。
当驱动程序发出STKEVENT_LINKUP或STKEVENT_LINKDOWN事件信号时,您可以提供挂钩功能,这意味着链接已经出现或已经关闭。请注意,只有在驱动程序具有调用STKEVENT_signal({STKEVENT_LINKUP})和STKEVENT_signal({STKEVENT_LINKDOWN})的代码时,才会调用此类挂钩函数。
钩子函数应该接受单个int状态参数。如果函数收到0,则链接现在已关闭(例如,因为电缆断开连接)。如果函数收到1,则链接现在已启动。要注册钩子函数,请按如下方式调用NC_setLinkHook():
NC_setLinkHook( void (*LinkHook)(int) );
NETCTRL模块创建STKEVENT结构的私有实例,它作为STKEVENT_Handle类型的句柄传递给设备驱动程序。由NETCTRL直接操作的私有实例声明为:
// Static Event Object
static STKEVENT stkEvent;
在随后的NetScheduler()的完整源代码中,STKEVENT结构由其实例stkEvent引用。
Scheduler Loop Source Code
NDK中包含的示例调度程序实现的代码如下所示。此实现使用本节中描述的方法和对象来充实第3.2.1节中所示的伪代码。在此代码中,串行端口设备和以太网设备的数量作为调用参数传入。当设备驱动程序要求枚举其物理设备时,将从设备驱动程序获取此设备数。
#define FLAG_EVENT_TIMER 1
#define FLAG_EVENT_ETHERNET 2
#define FLAG_EVENT_SERIAL 4
#define FLAG_EVENT_LINKUP 8
#define FLAG_EVENT_LINKDOWN 16
static void NetScheduler( uint32_t const SerialCnt, uint32_t const EtherCnt )
{
register int fEvents;
/* Set the scheduler priority */
TaskSetPri( TaskSelf(), SchedulerPriority );
/* Enter scheduling loop */
while( !NetHaltFlag )
{
if( stkEvent.hSemEvent )
{
SemPend( stkEvent.hSemEvent, SEM_FOREVER );
}
/* Clear our event flags */
fEvents = 0;
/* First we do driver polling. This is done from outside */
/* kernel mode since pure "polling" drivers cannot spend */
/* 100% of their time in kernel mode. */
/* Check for a timer event and flag it. EventCodes[STKEVENT_TIMER] */
/* is set as a result of llTimerTick() (NDK heartbeat) */
if( stkEvent.EventCodes[STKEVENT_TIMER] )
{
stkEvent.EventCodes[STKEVENT_TIMER] = 0;
fEvents |= FLAG_EVENT_TIMER;
}
/* Poll only once every timer event for ISR based drivers, */
/* and continuously for polling drivers. Note that "fEvents" */
/* can only be set to FLAG_EVENT_TIMER at this point. */
if( fEvents || !stkEvent.hSemEvent )
{
NIMUPacketServiceCheck (fEvents);
/* Poll Serial Port Devices */
if( SerialCnt )
_llSerialServiceCheck( fEvents );
}
/* Note we check for Ethernet and Serial events after */
/* polling since the ServiceCheck() functions may */
/* have passively set them. */
/* Was an Ethernet event signaled? */
if(stkEvent.EventCodes[STKEVENT_ETHERNET])
{
/* We call service check on an event to allow the */
/* driver to do any processing outside of kernel */
/* mode that it requires, but don't call it if we */
/* already called it due to a timer event or by polling */
if (!(fEvents & FLAG_EVENT_TIMER) && stkEvent.hSemEvent)
NIMUPacketServiceCheck (0);
/* Clear the event and record it in our flags */
stkEvent.EventCodes[STKEVENT_ETHERNET] = 0;
fEvents |= FLAG_EVENT_ETHERNET;
}
/* Check for a Serial event and flag it */
if(SerialCnt && stkEvent.EventCodes[STKEVENT_SERIAL] )
{
/* We call service check on an event to allow the */
/* driver to do any processing outside of kernel */
/* mode that it requires, but don't call it if we */
/* already called it due to a timer event or by polling */
if( !(fEvents & FLAG_EVENT_TIMER) && stkEvent.hSemEvent )
_llSerialServiceCheck( 0 );
/* Clear the event and record it in our flags */
stkEvent.EventCodes[STKEVENT_SERIAL] = 0;
fEvents |= FLAG_EVENT_SERIAL;
}
/* Check if link went up */
if(stkEvent.EventCodes[STKEVENT_LINKUP])
{
/* Clear the event and record it in our flags */
stkEvent.EventCodes[STKEVENT_LINKUP] = 0;
fEvents |= FLAG_EVENT_LINKUP;
}
/* Check if link went down */
if(stkEvent.EventCodes[STKEVENT_LINKDOWN])
{
/* Clear the event and record it in our flags */
stkEvent.EventCodes[STKEVENT_LINKDOWN] = 0;
fEvents |= FLAG_EVENT_LINKDOWN;
}
/* Process current events in Kernel Mode */
if( fEvents )
{
/* Enter Kernel Mode */
llEnter();
/* Check for timer event. Timer event flag is set as a result of */
/* llTimerTick() (NDK heartbeat) */
if( fEvents & FLAG_EVENT_TIMER )
ExecTimer();
/* Check for packet event */
if( fEvents & FLAG_EVENT_ETHERNET )
NIMUPacketService();
/* Check for serial port event */
if( fEvents & FLAG_EVENT_SERIAL )
llSerialService();
/* Exit Kernel Mode */
llExit();
/* Check for a change in link status. Do this outside of above */
/* llEnter/llExit pair as to avoid illegal reentrant calls to */
/* kernel mode by user's callback. */
/* Check for link up status */
if( fEvents & FLAG_EVENT_LINKUP )
{
/* Call the link status callback, if user registered one */
if (NetLinkHook)
{
/* Pass callback function a link status of "up" */
(*NetLinkHook)(1);
}
}
/* Check for link down status */
if( fEvents & FLAG_EVENT_LINKDOWN )
{
/* Call the link status callback, if user registered one */
if (NetLinkHook)
{
/* Pass callback function a link status of "down" */
(*NetLinkHook)(0);
}
}
}
}
}
Disabling On-Demand Services
NETCTRL库被设计为支持用户在应用程序(例如DHCP服务器)内期望的“潜在”堆栈特征。但是,这样做的缺点是,即使应用程序从不使用这些功能,这些功能的代码也将包含在可执行文件中。这导致比通常需要的更大的占地面积。为了最大限度地减少此问题,可以使用以下不同版本的NETCTRL库:
•netctrl_min。此最小库仅启用DHCP客户端。当需要最小的占地面积时,应该使用它。
•netctrl。NETCTRL库的这个“标准”版本支持以下功能并具有中等占用空间: - Telnet服务器 - HTTP服务器 - DHCP客户端•netctrl_full。这个“完整”库支持所有支持的NETCTRL功能,包括: - Telnet服务器 - HTTP服务器 - NAT服务器 - DHCP客户端 - DHCP服务器 - DNS服务器
这些NETCTRL库版本中的大部分版本都是为纯IPv4和IPv6构建的。
如果使用XGCONF配置工具在CCStudio中配置NDK,则会根据您启用的模块自动选择相应的NETCTRL库。
如果您需要更多地控制应用程序使用的NETCTRL库中可用的功能,可以在/ti/ndk/inc/netctrl/netsrv.h中定义以下常量,这些常量控制带入NETCTRL库的功能如果未定义“_NDK_EXTERN_CONFIG”。
#define NETSRV_ENABLE_TELNET 1
#define NETSRV_ENABLE_HTTP 1
#define NETSRV_ENABLE_NAT 0
#define NETSRV_ENABLE_DHCPCLIENT 1
#define NETSRV_ENABLE_DHCPSERVER 1
#define NETSRV_ENABLE_DNSSERVER 1
通过将上述任何一项设置为0并重建相应的NETCTRL库,可以从可执行文件中清除各个服务。
OS Adaptation Layer
OS适配层控制NDK如何使用RTOS资源。这包括使用任务,信号量和内存。在大多数情况下,本章中描述的NDK应用程序将调用的唯一API是TaskCreate()。
About OS Adaptation
NDK使用OS适配层,以便应用程序可以使用任何支持的RTOS。目前,NDK应用支持TI-RTOS内核和FreeRTOS。这种可移植性是通过转换为POSIX pthread调用的OS适配API实现的。反过来,POSIX层可以在内部使用TI RTOS内核或FreeRTOS。
本章介绍执行OS适配的API。
在大多数情况下,您的用户应用程序代码将仅调用TaskCreate()。但是,您可以根据需要修改适配层的源代码。OS适配层的源代码在/ ti / ndk / os目录中提供:
Source Files
OS库的源代码由以下文件组成,这些文件位于/ ti / ndk / os目录中:
efs.c嵌入式(基于RAM)文件系统
mem.c内存分配和内存复制函数
mem_data.c内存分配定义和#pragmas
oem.c OEM缓存和系统函数
ossys.c额外的OS支持(调试日志,stricmp()函数)
semaphore.c信号量抽象
task.c任务线程抽象
两个额外的包含文件位于/ ti / ndk /inc / os目录:
osif.h适配库的接口规范
oskern.h由NETCTRL等函数使用的半私有声明
Task Thread Abstraction: TASK.C
task.c模块包含TI网络开发人员工具包(NDK)API参考指南(SPRU524)中记录的任务抽象API的子集。它还包含堆栈排除方法函数的源代码:llEnter()和llExit()。后者在本文件的第4.2.2节中讨论。
在大多数情况下,您的用户应用程序代码将仅调用TaskCreate()。
TI网络开发人员工具包(NDK)API参考指南(SPRU524)中定义的大多数任务和信号量函数都是调用RTOS的宏。宏在inc / os / osif.h中定义。
可能需要特殊处理的功能将在后面的小节中介绍。
TaskCreate(), TaskExit(), and TaskDestroy()
create,exit和destroy函数都调用它们的POSIX pthread对应函数。
如果您使用TI-RTOS内核,TaskExit()和TaskDestroy()按预期运行,则Task.deleteTerminatedTasks配置参数必须设置为“true”。此参数设置指示任务模块删除空闲任务中已完成的任务。部分清理涉及释放任务堆栈内存。
NDK配置自动将Task.deleteTerminatedTasks设置为“true”。如果您的应用程序不使用NDK Global模块进行配置,则必须手动设置此参数。例如:
var Task = xdc.useModule('ti.sysbios.knl.Task');
Task.deleteTerminatedTasks = true;
Choosing the llEnter()/llExit() Exclusion Method
尽管NDK提供了一个可重入的环境,但堆栈的核心并不是可重入的。必须保护部分代码不被重入调用访问。该软件不是使用阻止所有其他任务执行的关键部分,而是定义称为内核模式的操作模式。定义内核模式,使得在任何给定时间只有一个Task可以处于内核模式。它不会阻止运行不使用NDK的任务。这为堆栈软件提供了保护,而不会影响不相关代码的执行。
llEnter()和llExit()函数在整个堆栈代码中用于进入和退出内核模式,并提供代码排除而不使用临界分段。它们相当于splhigh()/ splx()Unix函数及其多个衍生函数。
NDK中包含llEnter()和llExit()函数的两个示例实现。示例实现通过任务优先级或使用信号量提供排除。两个实现的源代码包含在Task抽象源文件中:src / os / task.c
一种排除方法是优先级方法。这里,调用llEnter()的Task被提升到OS_TASKPRIKERN的优先级,这保证它不会被抢占,因为另一个Task不可能运行(所有可能调用堆栈的任务都有一个较低的优先级)。对堆栈进行编码,以便内核模式优先级的Task永远不会阻塞。调用llExit()时,将恢复任务的原始优先级。请注意,可以为时间关键任务分配高于OS_TASKPRIKERN的优先级,但不允许它们调用NDK。
基于优先级的排除使您的应用程序使用NDK定义的任务优先级之一变得非常重要。如果使用的优先级大于NDK的最高定义优先级(OS_TASKPRIHIGH),则基于优先级的排除可能会中断。如果线程调用任何与堆栈相关的函数,则将线程设置为比NDK的高优先级线程级别更高的优先级可能会中断系统并导致不可预测的行为。
enter和exit函数的替代实现使用初始计数为1的信号量。
当调用llEnter()时,任务调用信号量上的挂起操作。如果某个其他任务当前正在内核模式下执行,则新任务将挂起,直到原始任务调用llExit()。对llExit()的调用会导致一个post操作,释放一个Task进入堆栈。这种形式的函数对比优先级方法更安全,但也可能更慢。通常,信号量操作比任务优先级更改慢一点。但是,这种方法也有其优点。信号量方法的主要优点是可以更自由地为任务分配优先级。如果高优先级任务要调用NDK,则无需限制任务优先级或担心。
通过更改两个实现的#if语句,系统开发人员可以选择使用任一实现。
Packer Buffer Manager: PBM.C
数据包缓冲区管理器(PBM)负责管理系统中的所有数据包缓冲区。NDK和设备驱动程序使用数据包缓冲区来承载网络数据包数据。PBK编程抽象在NDK程序员参考指南中讨论。本节讨论NDK中提供的实现。
Packet Buffer Pool
PBM缓冲区在XGCONF中的全局模块配置的缓冲区选项卡中配置。您可以设置帧数(默认值= 192),帧缓冲区大小(默认值= 1536字节)以及存储缓冲区的内存部分。
请注意,当声明内存时,它将放置在缓存对齐的边界上。此外,每个数据包缓冲区的大小必须是偶数个高速缓存行,以便可以可靠地刷新它,而不会有与其他缓冲区冲突的风险。
Packet Buffer Allocation Method
缓冲区分配的基本方法是缓冲池。调用PBM_alloc()函数时会分配缓冲区。可以在中断时调用此函数,因此必须确保仅作为结果进行非阻塞调用。但是,只有设备驱动程序才能从ISR进行调用,设备驱动程序永远不会要求大于PKT_SIZE_FRAMEBUF的缓冲区。因此,用于分配较大缓冲区的回退方法在技术上可以进行阻塞调用,尽管NDK中包含的实现在任何情况下都不会进行阻塞调用。
分配的基本方法是检查大小。当大小小于或等于PKT_SIZE_FRAMEBUF时,则从空闲队列中获取数据包缓冲区。如果队列中没有空闲数据包缓冲区,则该函数返回NULL。请注意,可以修改PBM模块以增加空闲池或使用内存分配作为回退,但是由于大小小于或等于PKT_SIZE_FRAMEBUF的请求而提供的任何缓冲区必须遵守在上一节。
对于大于PKT_SIZE_FRAMEBUF的数据包缓冲区,可以使用标准内存。这些分配请求仅用于重新组装大型IP数据包。生成的数据包无法在未分段的情况下提交给硬件设备。因此,分组缓冲器不需要与硬件传输兼容。
Referenced Route Handles
PBM结构中的一个字段是用于将数据包路由到其最终目的地的路由的引用句柄。在释放数据包缓冲区或复制数据包缓冲区时,PBM模块必须知道此句柄。
当通过调用PBM_free()释放数据包缓冲区时,PBM模块必须检查数据包缓冲区持有的路由句柄,并取消引用句柄(如果存在)。例如:
if( pPkt->hRoute )
{
RtDeRef( pPkt->hRoute );
pPkt->hRoute = 0;
}
如PBM.C的源代码中所述,函数RtDeRef()只能从内核模式调用。但是,PBM模块不依赖于定义PBM_free()函数的两个版本,而是依赖于设备驱动程序永远不会被赋予包含路由的数据包缓冲区这一事实。因此,必须在内核模式下调用对缓冲区包含路由的PBM_free()的任何调用。因此,调用RtDeRef()是安全的。
使用PBM_copy()复制数据包缓冲区时,也会复制有关数据包的所有信息。该信息可以包括引用的路由句柄。如果在复制数据包缓冲区的过程中复制了路由句柄,则还必须通过调用RtRef()函数添加对该句柄的引用。PBM模块不需要担心内核模式,原因与PBM_free()没有相同。
Memory Allocation System: MEM.C
内存分配系统包括用于小块内存,大块以及初始化和复制内存块的分配函数。该模块中包含的文件的API定义在TI网络开发人员工具包(NDK)API参考指南(SPRU524)中定义。这些功能在整个堆栈中使用。提供源代码以便系统程序员可以调整存储器系统以适应特定需要。
可以配置此内存管理器缓冲区阵列的大小和位置。页面大小取决于各种堆栈实体,因此在更改它时应该小心。可以向上或向下调整使用的页数以增加或减少暂存器内存大小。
不应更改小内存块(mmAlloc()和mmFree())的分配函数。
NDK使用这些函数来分配和释放暂存器类型的内存。它们可以在中断时调用,不允许阻塞。内存当前是从静态数组中分配的。
内存操作函数mmZeroInit()和mmCopy()都用C编码。系统程序员可以在汇编中重新编码这些函数,或者使用EDMA通道来移动内存。
大内存块(mmBulkAlloc()和mmBulkFree())的分配函数目前定义为使用malloc()和free()。可以更改这些功能以使用任何选择的内存分配系统。它们不会在中断时被调用并被允许阻塞。
Embedded File System: EFS.C
EFS文件系统为HTTP服务器提供基于RAM的文件支持,并为应用程序编程人员提供任何CGI功能。此API在TI网络开发人员套件(NDK)API参考指南(SPRU524)中定义。提供源代码用于调整功能以支持物理存储介质。这允许HTTP服务器在不移植服务器的情况下在物理设备上工作。
General OS Support: OSSYS.C
对于在其他地方没有家的功能,OSSYS文件是一个通用的catch-all。目前,该模块包含DbgPrintf() - 一个调试日志功能和stricmp(),它不包含在RTS中。
Jumbo Packet Buffer Manager (Jumbo PBM)
巨型数据包缓冲区管理器负责处理大小大于MMALLOC_MAXSIZE(3068字节)的数据包缓冲区的内存分配和解除分配。当应用程序打算使用Jumbo帧时,该数据包缓冲区管理器很有用,即大小超过1500字节的数据包通常不能在中断上下文中使用PBM或RTOS API进行分配。
以下是Jumbo PBM的一些主要功能:
•Jumbo PBM实现大致类似于PBM实现本身,除了它可以处理的块大小大于PBM中的块大小,默认情况下介于3K和10K字节之间。
•Jumbo PBM不使用任何RTOS API或动态内存分配方法进行内存分配,因此可以在中断上下文中安全使用。它使用静态内存分配方法,即它在设备内存的“远”部分保留一块内存,并进一步使用它来分配所需的数据包缓冲区。
•Jumbo PBM从内存中的单独部分分配内存而不是PBM本身。PBM使用内存部分“NDK_PACKETMEM”,“NDK_MMBUFFER”进行内存分配。另一方面,Jumbo PBM定义并使用名为“NDK_JMMBUFFER”的部分进行内存分配。此部分的大小及其位置均可自定义。
•NDK OS AL中提供了Jumbo PBM的示例实现。客户需要根据应用程序需求和系统内存限制来定制此实现。内存部分大小,块大小和分配方法本身都可以进行自定义。
•“预计不会直接调用Jumbo PBM API。应用程序和驱动程序必须仅调用PBM_alloc()/ PBM_free()API。如果请求的内存大于PBM本身可以处理的内存,即3K字节,则这些API依次调用Jumbo PBM API来分配/清理内存。
有关Jumbo PBM的示例实现,请参阅/ ti / ndk / stack / pbm目录中的源文件JUMBO_PBM.C。