最近在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源码更改为:
static int netStartTaskPri = 0;
static HANDLE hTaskNetService = 0;
void NC_tmpRaiseNetServicePri(void)
{
if (hTaskNetService) {
TaskSetPri( hTaskNetService, netStartTaskPri );
}
}
static void NetScheduler( uint const SerilCnt, uint const EherCnt)
{
register int fEvents;
register HANDLE hTaskSelf = hTaskNetService = TaskSelf();
netStartTaskPri = TaskSetPri( hTaskSelf, SchedulerPriority );
while( !NetHaltFlag )
{
...
if (TaskGetPri( hTaskSelf ) != SchedulerPriority) {
TaskSetPri( hTaskSelf, SchedulerPriority );
}
}
}
_extern void NC_tmpRaiseNetServicePri(void);
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
用法示例:
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();
}
}
...