最近在TI的处理器上做软件开发,项目需要网络通讯功能,而在TI的处理器上做网络编程只能使用TI自家的NDK,除非是非常专业的选手,否则用户几乎没有其他选择。
本文假设设计者熟悉TI的集成开发环境Code Composer Studio v6,因此一些基本的工程、项目、软件包的配置说明将被略过,若有需要了解请移步TI官网查询。
本人项目中使用的处理器是TI C6457 DSP,软件包有NDK v2.24.03.35,SYS/BIOS v6.42.02.29。其中NDK需要SYS/BIOS的支持,因此SYS/BIOS是必选项。 一.NDK的配置
1.系统配置
NDK使用SYS/BIOS的Clock模块对NDK内部的工作进行计时驱动,这一模块的实例在NDK中被设计为动态创建得到,虽然NDK自带一套不依赖于系统动态内存管理功能的内存管理单元,并且有自己的堆内存,但Clock的实例却不能在其中创建,只能在系统的全局对中创建。因此SYS/BIOS中的内存堆必须分配足够的大小,且必须将SYS/BIOS的动态内存分配功能使能,否则编译工具将无法产生正确的Clock动态创建代码,而程序也无法正常启动NDK服务。 2.模块配置
按项目需求,需要使用芯片的网络接口设备,因此NDK配置选择了EMAC模块;又因为主要使用UDP协议通信,所以添加了IP模块;其他的TELNET、HTTP、FTP等上层服务一概不选。 3.底层驱动配置
底层驱动分别需要EMAC驱动和NIMU Ethernet驱动,另外CSL库是必须的,它们都可以在针对C64PLUS芯片的MCSDK中找到。这个软件包集合可以安装CSL、PDK、MCSDK三个包,CSL包不必说了,PDK中可以找到EMAC驱动,NIMU Ethernet驱动在MCSDK包中,后两个最好用CCS打开工程自己编译一下,然后需要手动将它们(包括CSL包)的lib文件及include路径添加到自己的工程中。其他的配置略过。 二.基于NDK的部分UDP编程经验
1.基于NDK的socket编程代码必须在Task中执行,原因有:
(1).NDK的启动、初始化以及后台守护任务都在Task中执行;
(2).socket编程需要文件描述符的支持,SYS/BIOS实现了一个简化版的文件描述符,但必须在Task中打开(用fdOpenSession)和关闭(用fdCloseSession)。 2.使用socket编程的用户Task不可一直占用CPU的执行时间,必须以合适的时间间隔自动阻塞,原因有:
(1).SYS/BIOS的Tasks并不是以分片时间自动调度执行,而是按优先级自动重入执行,对相同优先级的Tasks仅在当前执行的Task通过结束任务或者阻塞来主动放弃执行时间后,等待执行的Task才有机会获得执行时间。
(2).NDK的守护任务在完成初始化后即会自动降低Task优先级至最低的IDLE Task级,若用户Task执行在高于IDLE的优先级,且一直不通过阻塞来主动放弃执行时间的话,则NDK的数据报队列状态将一直得不到更新,从而导致用户Task的socket函数无法正常接收和发送数据报。 3.setsockopt应谨慎改变NDK的自定义属性SOL_BLOCKING,否则socket API recv和recvfrom无法正常接收数据报,原因目前未明。
4.recv、recvfrom、send、sendto函数的查询模式实现
这些函数虽然自带阻塞功能,但在一些需要使用查询的应用中并不合适,而多Task的方案又较为复杂,增加了程序设计的难度。实际上可以为它们的flag参数传递MSG_DONTWAIT来防止函数进入阻塞状态,此时这些函数将返回工作失败状态,用fdError()查询可得错误状态码为EWOULDBLOCK。另外在查询模式下,需要编程者手动调整Task的执行时间,或者按后面的方法对NDK进行一点小的改进。 5.在连接以Windows作为操作系统的PC机的网络接口时sendto函数有时会返回错误状态
这是因为Windows系统服务在网络接口连接后会断断续续的发送一些报文,这些报文会暂时占据NDK的数据报队列,导致查询模式下的sendto函数失败,此时fdError()会返回ENOBUFS状态。这种情况下,将Task阻塞一小段时间,再sendto就会成功了。 三.适应执行时间紧张应用场景的NDK小改进
正如上文所说,在查询模式下需要编程者手动调整Task的执行时间,以预留足够的时间给NDK守护任务用于刷新数据报队列,这在应用执行时间比较紧张的情况下将带来额外的调试任务和难度。
为解决这个问题,可考虑将NDK的守护任务执行函数在用户主Task中按合适的时间频度调用,但这可能引入更多的Task间的互斥同步编程,且可能与守护任务的执行上下文产生冲突,因此有必要采用更安全的做法。
我的做法是:在调用socket通信函数(recv、recvfrom、send、sendto等)发现NDK资源不足时,临时提高一次NDK守护任务的优先级,在守护任务执行完一次刷新任务后,又将自身的优先级降回IDLE。这需要对NDK的源代码做一点小更改,并重新编译。
涉及的NDK源码更改为: - /* (NDK安装目录)/packages/ti/ndk/netctrl/netctrl.c中的NetScheduler函数前添加自定义的API函数 */
- static int netStartTaskPri = 0;
- static HANDLE hTaskNetService = 0;
- void NC_tmpRaiseNetServicePri(void)
- {
- if (hTaskNetService) {
- TaskSetPri( hTaskNetService, netStartTaskPri );
- }
- }
- /* 更改NetScheduler函数的实现代码 */
- static void NetScheduler( uint const SerilCnt, uint const EherCnt)
- {
- register int fEvents;
- /* Set the Scheduler priority */
- register HANDLE hTaskSelf = hTaskNetService = TaskSelf();/* 获取和保存守护Task的句柄 */
- netStartTaskPri = TaskSetPri( hTaskSelf, SchedulerPriority );/* 设置守护Task优先级为SchedulerPriority(默认为IDLE级),并保存原来的优先级 */
- /*
- * 说明:NetScheduler所在的Task在执行NetScheduler之前执行的是NDK的初始化任务,默认情况下该Task的
- * 优先级高于用户Task的优先级,直到初始化任务完毕后,在NetScheduler函数中才将自身优先级降为IDLE。
- * 因此上面的代码中将原来的优先级和Task句柄一并保存,在自定义的API函数NC_tmpRaiseNetServicePri中
- * 即可恢复守护Task的优先级,从而使守护Task从用户Task中重入并得以执行。
- */
- /* Enter scheduling loop */
- while( !NetHaltFlag )
- {
- .../* 原循环代码省略 */
- /* 检查守护Task的优先级,并根据需要重设优先级至SchedulerPriority */
- if (TaskGetPri( hTaskSelf ) != SchedulerPriority) {
- TaskSetPri( hTaskSelf, SchedulerPriority );
- }
- }
- }
- /* (NDK安装目录)/packages/ti/ndk/inc/netctrl/netctrl.h中添加自定义API函数的声明 */
- _extern void NC_tmpRaiseNetServicePri(void);
复制代码 用法示例:
SOCKET s;
struct sock_addr_in from;
int fromLen;
...
int ret = recvfrom(s, recvBuff, recvBytes, MSG_DONTWAIT, (struct sock_addr*)&from, &fromLen);
if (ret == INVALID_SOCKET) {
ret = fdError();
if (ret == EWOULDBLOCK || ret == ENOBUFS) {
NC_tmpRaiseNetServicePri();
}
}
...
|