一.互斥条件
多个任务共享一个资源时需要注意做到互斥,意即当一个任务在使用一个资源时,其它任务需停止使用该资源(挂起),直到该任务使用完该资源时,其它任务方可使用该资源。
常用的实现互斥的方法有:
1. 关中断
2. 信号量
3. 使用测试并置位指令
4. 禁止任务切换
下面分别来介绍各种方法(其中第3种方法在实时内核中不会用到,不做讨论):
(1)关中断
为互斥常用方法,UC/OS-II中中断的宏定义如下:
#define OS_CRITICAL_METHOD 2 |
|
#if OS_CRITICAL_METHOD == 1 |
#define OS_ENTER_CRITICAL() asm CLI // CLI为汇编中的关中断指令 |
#define OS_EXIT_CRITICAL() asm STI //STI为汇编中的开中断指令 |
#endif |
|
#if OS_CRITICAL_METHOD == 2 |
#define OS_ENTER_CRITICAL() asm {PUSHF; CLI} |
#define OS_EXIT_CRITICAL() asm POPF |
#endif |
(2) 信号量
信号量就像是一把钥匙,它能将共享资源锁定,每个任务在执行时若需要该共享资源,则必须先得到该资源的钥匙,否则无法使用。信号量通常有两种类型:二进制型和计数器型。二进制型的信号量只有两种状态0和1,1表示该资源可用,0表示该资源不可用。 计数器型的信号量表示有多个共享资源属于一组钥匙,计数器的计数范围通常由具体的硬件来决定,如0——255,0——65535,0——4294967295等。计数器的当前值为几就表示有几个信号量(资源)可用。每当一个资源使用完时,当前计数器就加1,一个任务得到一个信号量时,当前信号量计数器就减1,计数器为0时证明当前已没有可用资源,若此时又有任务需要信号量,则该任务便处于挂起状态,直到有一个资源的信号量被释放为止。
对信号量的操作只能有3种类型:创建,等待信号和给予信号。下面通过信号量管理缓冲区的方法来介绍下信号量的使用方法:
(3) 禁止任务切换
这种方法应该避免使用,因为内核的主要任务就是任务的调度,而用手动的方法将任务的切换禁止后,便有违内核调度的初衷了。同时应注意在禁止任务切换时,中断还是开着的,如果中断服务子程序有与当前任务共享的资源,则还要将中断关掉,而有些内核规定中断服务子程序中必须没有共享资源的使用,这样的话,完全有可能出现当前任务正在执行,突然有一个中断产生,于是又去执行中断服务子程序,执行完后,即便就绪队列中有比当前任务高的优先级的任务,但不执行它,而是执行完当前任务,待到当前任务执行允许任务切换的代码后,才会去执行那个就绪队列中的高优先级任务。下面就是基于这种方式的基本结构:
void Function (void) |
{ |
OSSchedLock(); |
. |
. /* You can access shared data in here (interrupts are recognized) */ |
. /*在这里处理共享数据(中断是开着的)*/ |
OSSchedUnlock(); |
} |
(4)置位并测试指令
此方法在实时内核中很少用到,暂且不讨论,其基本原理和二进制信号量很相似。
二.死锁(或称抱死)
如果两个或多个任务在执行时互相占有对方要使用的资源而导致程序无法执行,这种情况被称为死锁。如任务T1占有资源R1,任务T2占有资源R2,在某一时刻,任务T1在执行时要用到R2,而T2在某一时刻要用到R1,此时任务T1和T2都将无法执行。防止死锁的方法较多,最简单的方法便是让任务先得到自己执行时所需要的所有资源再进入执行态。
三.同步
可以使用信号量来达到两个任务的同步执行,这种同步被称为双向同步,在WINDOWS程序设计中有很多这样的多线程应用,其基本结构如下所示:
Task1() |
{ |
for (;;) { |
Perform operation; |
Signal task #2; (1) |
Wait for signal from task #2; (2) |
Continue operation; |
} |
} |
|
Task2() |
{ |
for (;;) { |
Perform operation; |
Signal task #1; (3) |
Wait for signal from task #1; (4) |
Continue operation; |
} |
} |
四.事件标志
当一个任务要与多个事件同步时,要使用事件标志。若任务要与事件任务之一发生同步,可称为独立型同步(逻辑或型同步),在WINDOWS程序设计中使用事件互斥实现线程同步时,若事件为一个事件组,则此时就为这种独立型同步。而若任务要与所有事件都发生同步,则称为关联性同步(逻辑与型同步)。
UC/OS-II目前不支持事件标志。
五.任务间的通讯
很多时候,多个任务需要协作来共同完成一件事务,此时多个任务可能需要通讯。任务间的通讯通常有两种方式:共享全局变量和发送消息。
共享全局变量的话,就要注意必须通过一定的互斥方式来实现共享变量的排他性(互斥性)。而发送消息通常有两种方式:消息邮箱和消息队列(见下一条要点)。
六.消息邮箱和消息队列
顾名思义,消息邮箱就是内核提供个类似邮箱的东西,一个任务给其投递消息,另外一个任务从中获取消息。若另外一个任务在获取消息时邮箱中没有消息,则此任务被挂起,直至邮箱中有消息为止。一般情况下,为避免任务被挂起的时间太长,通常都会为任务设定一个挂起时间限制,如果在限制时间内邮箱中没有消息,则该任务便处于就绪态并准备执行,返回错误信息。
消息队列,是一个FIFO机制的队列,里面存放着各个消息的地址。同时也有个等待消息队列来等待从队列中获取消息。获得消息的方式随内核不同而不同,有些是基于FIFO型的严格队列类型的,而有些是基于任务的最高优先级机制类型的(即获取消息的任务总是等待消息队列中优先级最高的那个任务)。
七.中断
对于中断的理解很重要,由于其设计的内容较多,在此不做讨论,个人认为理解其最好的方式是研究单片机的工作原理,最简单易学的为8051单片机。