线程设计

程序的运行上下文

采用RT-Thread的实时系统,程序运行状态只有几种类型,但这几种类型构成了程序运行的上下文状态,当程序员清晰的知道自己编写的程序处于何种状态时,对于程序中应该注意什么要点将非常清晰了然。

RT-Thread中程序运行的上下文包括:

  • 中断服务例程;
  • 普通线程;
  • 空闲线程;
    空闲线程

空闲线程是RT-Thread系统中没有其他工作进行时自动进入的系统线程。开发者可以通过idle线程钩子方式,在idle线程上钩入自己的功能函数。通常这个空闲线程钩子能够完成一些额外的特殊功能,例如系统运行状态的指示,系统省电模式等。

除了空闲线程钩子,RT-Thread系统还把idle线程用于一些其他的功能,比如当系统删除一个线程或一个动态线程运行结束时,会先行更改线程状态为非调度状态,然后挂入一个僵尸队列中,真正的系统资源回收工作在idle线程完成。所以,对于空闲线程钩子上挂接的程序,它应该:

  • 不会挂起idle线程;
  • 不应该陷入死循环,需要留出部分时间用于系统处理僵尸线程的系统资源回收。
    中断服务例程

中断服务例程是一种需要特别注意的上下文环境,它运行在非线程的执行环境下(一般为芯片的一种特殊运行模式(特权模式)),在这个上下文环境中不能使用挂起当前线程的操作,因为当前线程并不存在,执行相关的操作会有类似:

  1. Function[abc_func] shall not used in ISR

的打印信息(其中abc_func就是你不应该在中断服务例程中调用的函数)。另外需要注意的是,中断服务程序最好保持精简短小,因为中断服务是一种高于任何线程的存在。

普通线程

普通线程看似没有什么限制程序执行的因素,似乎所有的操作都可以执行。但是做为一个实时系统,一个优先级明确的实时系统,如果一个线程中的程序执行了死循环操作,那么比它优先级低的线程都将不能够得到执行,当然也包括了idle线程。这个是在实时操作系统中必须注意的一点。

线程设计要点

在实时系统的章节中提到过,实时系统多数是一种被动的系统,被动的响应外部事件,当外部事件触发后执行设定的工作内容。所以在对系统进行线程设计时,需要考虑到:

上下文环境

对于工作内容,首先需要思考它的执行环境是什么。工作内容与工作内容间是否有重叠的部分,是否能够合并到一起进行处理,或者单独划分开进行处理。例如对键盘事件的处理:在正常情况下,键盘可以采用中断服务例程直接产生RT-Thread/GUI所要求的键盘事件,然后在中断服务例程中把它发送给应用线程;但是在STM32 Radio中,由于硬件的限制,系统需要自行查询按键的状态,即不能够在中断服务的上下文中执行,所以应单独开辟一个key线程来处理按键。

线程的状态跃迁

这里说的状态跃迁指的是线程运行中状态的变化,从就绪态过渡到挂起态。实时系统一般被设计成一种优先级的系统,如果一个线程只有就绪态而无阻塞态,势必会影响到其他低优先级线程的执行。所以在进行线程设计时,就应该保证线程在不活跃的时候,必须让出处理器,即线程能够主动让出处理器资源,进入到阻塞状态。这需要设计者在设计线程的时候就明确的知道什么情况下需要让线程从就绪态跃迁到阻塞态。

线程运行时间长度

线程运行时间长度被定义为,在线程所关心的一种事件或多种事件触发状态下,线程由阻塞态跃迁为就绪态执行设定的工作,再从就绪态跃迁为阻塞态所需要的时间(一般还应加上这段时间内,这个线程不会被其它线程所抢占的先决条件)。线程运行时间长度将和线程的优先级设计密切相关,同时也决定着设计的系统是否能够满足预计的实时响应的指标。

例如,对于事件A对应的服务线程Ta,系统要求的实时响应指标是1ms,而Ta的最大运行时间是500us。此时,系统中还存在着以50ms为周期的另一线程Tb,它每次运行的最大时间长度是100us。在这种情况下,即使把线程Tb的优先级抬到比Ta更高的位置,对系统的实时性指标也没什么影响(因为即使在Ta的运行过程中,Tb抢占了Ta的资源,但在规定的时间内(1ms),Ta也能够完成对事件A的响应)。