多线程编程之重点--使用DSP/BIOS时选择线程类型的参考方法
了解以下这些,对在DSP/BIOS上设计多线程应用程序就是轻而易举的事件。
DSP/BIOS支持多种类型线程,每种类型线程且有不同执行及抢先点,下表列出了这些线程的一些特点。
线程选择的一些原则
对线程选择基于对下面这个问题的回答:在应用程序中,这个线程是否具有实时性(线程必须在严格的时间段内结束,以及不允许被抢先)?该线程的执行时间相对其它部分来说是否少很多?
1. 严格的实时性
如果线程的执行需要严格的实时性,而线程执行需要的时间又很少时,可以使用硬件中断或时钟函数来完成。
CLK时钟函数也是在硬件中断中执行的。
DSP芯片一般都有一个或多个片上定时器外设,并能产生周期性的等间隔硬件中断。
DSP/BIOS内核通常会使用其中的一个定时器作为自己的系统时钟。在大多数
TMS320系列的
DSP中,
DSP/BIOS内核的
CLK模块都可以提供接近一个机器周期的时钟。
CLK函数将由片上的定时器中断触发,并在该中断的中断服务子程序(
ISR)中调用执行。
硬件中断线程由外部的同步事件(或
AD转换器的中断信号)触发而执行。
HWI函数或中断服务子程序(
ISR)将在中断发生后执行。由于硬件中断具有最高优先级,所以
HWI函数具有严格的实时性。在
DSP/BIOS的应用程序中,
HWI中断函数能够处理频率为
200KHZ的中断(具体瑾因工作频率不同或
ISR的复杂度而可能有差异)。所以当处理时间间隔在
2-
5US之间时,可以使用硬件中断线程。还有一个原因,就是硬件中断线程有极小的中断潜伏期。所谓中断潜伏期,指的是从中断触发到中断服务子程序的第一条指令开始执行之间的时间。当然,中断潜伏期越长,表示中断的响应越不及时,这一点对处理的实时性来讲相当重要。硬件中断线程只可以用汇编语言或
C语言和汇编语言的混合体,但通常我推荐使用汇编语言。
在硬件中断处理函数中,可以将
SWI软件中断对象,或
TSK任务对象放到执行队列中,但它们必须等到所有的硬件中断线程结束后才能允许。所以,应该尽量减小
HWI硬件中断函数,以便中断服务序尽快结束。另外,
DSP在响应硬件中断后会自动屏蔽所有的中断,因此可以通过设置调用
HWI_enter的参数开放一些中断,允许这些中断抢先执行。在系统存在多个硬件中断线程,而其中一个的中断服务程序稍长时,这种方法非常有效。
当硬件中断函数调用某些
PIP管道模块的
API函数时,如
PIP_alloc,PIP_free,PIP_get,PIP_put等,读写通知函数也将在中断响应中调用执行。
2.部分实时性
有时会遇到这种情况,只有一部分线程的执行要求有严格的实时性,其余部分执行时间较长而且对处理时间没有太多的要求。显然,这时仅仅使用硬件中断线程是不理想的。
一般情况下,要求严格实时性的处理都是由硬件中断触发,所以我们显然会使用硬件中断服务程序来响应这个中断,以便满足对实时性的要求。更为能用的做法是,可以增加
SWI软件中断或
TSK任务线程来完成一些非实时性的处理任务。这样可以减少中断的潜伏期,提高响应实时性请求的能力。
所以,将代码分为多个线程,只要求有严格实时性的任务放到
HWI硬件中断中完成,而其余处理都放到优先级较低的
SWI或
TSK中完成。这些
SWI或
TSK线程有如下两个特点:
* 能完成实时处理任务,但允许处理时间相对较长;
* 允许被其他线程抢先。
那么这些处理函数究竟该选择使用
SWI软件中断线程还是
TSK任务线程呢?请先看下面的几种情况:
* 处理函数需要等待某些资源(例如数据、上位机的信号等),以便继续运行。
* 处理函数与其他线程之间有复杂的联系或数据共享要求。
* 希望处理函数有自己的堆栈空间而不是系统共用的堆栈空间。
* 处理函数中用到
LCK,
MBX或
SEM这几个内核模块。
* 希望在线程创建、删除、退出、就绪、切换时调用钩子函数。
当上面的情况有任何一种存在时,建议使用
TSK任务线程;相反,当没有上面的一种情况出现时,建议选用
SWI软件中断。任务线程与软件中断线程有以下几点区别:
* 任务在执行时可以被挂起(
suspended,可以理解为暂停),直到必需的条件(如数据准备好、信号同步等)得到满足,才可以继续运行。在任务被挂起时,即任务处于暂停或阻塞(
blocked)状态,其他任务或线程得以执行,而软件中断做不到这点。
*
DSP/BIOS内核提供了一组用于任务之间通信和同步的数据结构,包括旗语、邮箱和锁。这些数据结构无法用于软件中断之间的通信和同步。
* 每个任务都有自己的堆栈区,而软件中断使用共享的系统堆栈。
* 当任务被创建、删除、退出或切换时,都可以调用特殊函数(钩子函数)。这些钩子函数可以用于保存任务的环境面不仅仅是
CPU的寄存器。
* 任务线程的优先级比软件中断线程低,而比后台
IDL线程高。任务线程内部又被划分为
16个优先等级。用户任务的优先级可以是从
1-
15级,最低的
0级内核保留给
LOOP循环使用。
软件中断线程往往伴随着硬件中断的发生。每当有硬件中断发生时,会触发
HWI硬件中断服务函数,而软件中断的触发是靠
SWI软件中断
API函数调用实现的。例如,在硬件中断服务函数中调用
SWI_post函数,可以触发一个
SWI软件中断。
软件中断线程适合于处理一些发生速率较低的任务,或对实时性要求不苛刻的任务。
SWI软件中断可以帮助硬件中断函数将一些非严格实时性的处理放到低优先级的线程中,以减少硬件中断的响应时间。这点非常重要,因为在硬件中断响应过程中往往处于关中断状态。
与硬件中断线程一样,软件中断线程总会一直执行下去,直到结束。当程序将软件中断放到
CPU的执行队列中排队等待时,软件中断函数需要的所有数据应该是准备好了的。
SWI软件中断对象中的邮箱提供了一种判断资源准备情况的方法。在其他线程运行时,一个软件中断线程不能够等待资源变为有效。
软件中断的优先级比任务线程高,但比硬件中断低。软件中断线程比任务线程使用更少的存储器,因为所有的软件中断线程都使用系统的堆栈空间。任务线程提供了额外的能力,如暂停(阻塞)和切换,而软件中断线程不支持这些。
3.周期性的服务
虽然有时线程的执行有实时性限制,但是处理任务却有足够长的时间执行而且能够允许其他线程抢先,即被其他线程中断去处理更紧急的任务。这种情况可以参照上面处理部分实时性线程的原则进行处理。但其中有一个较特殊的应用,就是需要周期性地或在固定的时间间隔内完成处理任务,而一般情况下,时间间隔比处理任务所需的时间要长得多。对于这种要求,我建议使用
DSP/BIOS内核提供的
PRD周期性函数来完成。
周期函数以系统时钟为基础,默认情况下,系统时钟由片上定时器驱动,也可以配置为其他事件来驱动,如
IO事件等。周期性函数属于
SWI软件中断中的
PRD_swi对象,通常该对象的优先级较低,可以通过
DSP/BIOS的配置工具提高它的优先级。
所有的
PRD周期函数具有同样的
SWI软件中断优先级,因此一个周期函数是不能抢先其他周期函数的。当存在一些处理时间较长的其他
SWI或
TSK线程时,为了保证所有
PRD周期函数正常执行,除了尽量缩短周期函数本身的处理时间外,还需要提高整个
PRD周期函数的优先级,以保证周期函数被允许在系统时钟触发时能够抢先那些低优先级的线程。
如果多个周期函数被同一个系统时钟触发,它们将按照创建时的顺序执行。另外,若周期函数需要被挂起或暂停时,应该使用
TSK线程来完成同样的服务。
4.不需要实时性
有时线程只需要在后台进行一些无关紧要的处理,比如收集统计数据、与自己交换检测数据等。这时,我们建议使用
IDL线程。
IDL等待循环是
DSP/BIOS内核中最低优先级的线程。当应用程序的主函数返回后,
DSP/BIOS内核调用该应用程序所用到的
DSP/BIOS模块的初始化启动代码。在这些启动代码结束后,便进入这个
IDL等待循环。该循环是一个无限循环,它不停地调用
IDL后台对象中的所有函数。每个函数在前一个函数结束后依次运行。这些
IDL函数不断被运行,直到被更高优先级的线程抢先。由于
IDL函数的优先级最低,运行的时间得不到保证,所以
IDL函数不适合于有实时性要求的任何处理任务。它通常用在一些不需要中断(当然也不能有中断)的非实时设备查询中,或用于监视系统状态,或其他一些后台活动。
DSP目标系统与主机的
DSP/BIOS分析工具之间的通信是在后台
IDL循环中完成的。这可以保证
DSP/BIOS的分析工具不会干扰应用程序的运行。如果目标板上的
DSP太忙,以致无法运行后台
IDL线程时,
DSP/BIOS分析工具会停止接收目标板信息。所以我们经常会看到,当目标板
DSP的处理任务太繁重或因错误落入一个死循环时,
CCS的所有操作都会变得迟缓起来。