μC/OS- -III对信号量的改进

2013-08-14 08:03黄土琛宫辉邵贝贝
单片机与嵌入式系统应用 2013年2期
关键词:链表内核列表

黄土琛,宫辉,邵贝贝

(1.清华大学 工程物理系,北京100084;2.清华大学 粒子技术与辐射成像教育部重点实验室)

引 言

μC/OS是一个基于优先级调度的可剥夺型实时多任务内核。在多任务的实时内核中,信号量是常用的机制,可以用来实现对共享资源的访问、任务之间的通信和同步,以及任务和中断的同步等功能。μC/OS -II中提供了等待和释放信号量等最基本的服务,而在μC/OS -III中,对信号量的使用增加了一些可选的模式,如非阻塞等待、释放但不进行任务调度等,提高了使用的灵活性。更重要的是,在μC/OS -III中还新增了任务内嵌的信号量,用户程序无需建立信号量便可和任务直接通信,比普通信号量更加简单高效。本文将分析对比μC/OS -II和μC/OSIII中信号量内部结构的差异以及μC/OS -III新增的特性。

1 μC/OS -II中信号量内部结构

在μC/OS -II中,信号量直接使用内核的数据结构OS_EVENT,其内部结构如下:

其中,和信号量相关的最重要的就是OSEventCnt、OSEventGrp和OSEventTbl[]。OSEventCnt记录的是信号量的有效值。OSEventTbl[]是一个位映射表,以64级优先级为例,OSEventTbl[]将是一个8×8的位映射表,如果某优先级下有任务在等待该事件,则OSEventTbl[]中对应的位将被置1。为了加快查询过程,又将64级优先级分为8组,用一个8位的整型OSEventGrp来记录每一组的状态。可见,OSEventGrp和OSEventTbl[]跟就绪表中的OSRdyGrp和OSRdyTbl[]结构是一模一样的,区别仅仅在于前者记录的是等待该事件的任务的状态,而后者记录的是系统中就绪的任务的状态。而两者的查找过程是一样的,都是通过“掩码表”来快速得到列表中优先级最高的任务。

μC/OS-II提供的信号量相关的最常用的几个API函数如下:

在使用信号量前必须先新建一个信号量,并指定其初始值。当信号量用于对共享资源的访问时,该值应初始化为实际可用的共享资源数;当信号量用来实现任务的同步,则初始值应设为0。调用等待信号量的OSSemPend()函数时可以指定超时选项timeout,在指定的时间内如果没有获得信号量则任务会超时返回。释放信号量时,如果有任务在等待,内核会通过查找OSEventGrp和OSEvent-Tbl[]获得等待任务中优先级最高的任务,该任务将获得信号量从而转入就绪态,内核会进行任务调度。如果获得信号量的任务比正在执行的任务优先级还高,则会进行任务切换。

2 μC/OS -III中信号量内部结构

在μC/OS -III中,信号量类型的结构有所变化,并没有和μC/OS -II一样继续采用和“就绪表”类似的结构,而是采用一个“等待列表”的数据结构来记录等待信号量的任务。其数据结构如下:

从上述结构可以看出,μC/OS -III的信号量结构中新增了一个时间戳TS,用来记录最近一次释放信号量(或者是取消等待、删除信号量)的时间。而等待信号量的任务列表则通过一个新的数据结构OS_PEND_LIST来记录,如图1所示。

OS_PEND_LIST包括3个数据域:NbrEntries用来记录等待列表中的条目数,也就是等待的任务数目;HeadPtr和TailPtr构成一个双向链表,指向的是OS_PEND_DATA类型的结构体。OS_PEND_DATA是μC/OS -III内部的一个数据类型,每当任务因等待信号量而被挂起时,内核就会新建一个对应的OS_PEND_DATA类型的数据块并插入到信号量的等待列表OS_PEND_LIST所包含的双向链表中。OS_PEND_DATA结构体包含指向等待任务的OS_TCB的指针以及其他数据域。在这里,最重要的细节是,μC/OS -III是按照任务优先级从高到低的顺序来排列双向链表中的OS_PEND_DATA数据块的。也就是说,每当有一个新的OS_PEND_DATA数据块需要插入到双向链表时(也就是任务因等待信号量而被挂起时),内核会从链表头部开始扫描各个OS_PEND_DATA数据块所对应的等待任务的优先级(通过OS_PEND_DATA数据块内部的TCBPtr指针可以从任务控制块内部获得任务的优先级),直到找到比当前需要插入的任务的优先级低的任务,然后把新的OS_PEND_DATA数据块插入到该位置前。如果链表中已有和需要插入的任务优先级相同的任务,则新插入的任务放到优先级相同的任务后。道理很简单,优先级相同,晚到的任务没有任何理由比早到的任务先获得信号量。基于上述排列方法,位于双向链表头部的任务总是等待的任务中优先级最高的。因此,当用户释放信号量时,总是双向链表头部的任务获得信号量,而不必再执行“查找最高优先级”的过程了。

图1 信号量内部的OS_PEND_LIST结构

μC/OS-III提供的信号量相关的最常用的几个API函数如下:

OSSemCreate()函数和μC/OS -II中的类似,需要指定信号量的初始值,还需额外指定信号量的名称以便于调试。

OSSemPend()函数多了两个参数:opt和p_ts。p_ts是指向时间戳的指针,当任务获得信号量(或者任务取消等待或信号量被删除)返回时,内核会把释放信号量(或者任务取消等待或信号量被删除)时刻的时间戳保存到该指针指向的变量中,该时间戳用户可以计算从信号量被释放到实际获得信号量的时间。opt参数用来指定该等待操作是否是阻塞的。在μC/OS -II中,当用户对信号量执行Pend操作而信号量无效时任务会被挂起,而μC/OS -III通过opt参数支持以“非阻塞”的方式调用。这种情况下,即使等待的信号量无效,任务也会返回,而不是被挂起,内核会通过返回代码告诉用户此时信号量无效。“非阻塞”方式可以应用于对共享资源的访问,比如当某资源不可用时用户可能并不希望任务被挂起,而是执行其他操作,等待一段时间后再次查询资源。但如果要实现任务间的同步,则必须用“阻塞”方式。这里顺便提一下,μC/OS -II中提供了一个信号量查询函数OSSemQuery(),可以用来获得信号量内部的计数值和等待列表,用户可使用“查询信号量”的办法来实现类似“非阻塞”的等待方式。而在μC/OS III中,由于OSSemPend()函数本身就支持“非阻塞”模式,因此并没有再提供查询信号量的函数,这也比“查询信号量”的办法更加高效。

OSSemPost()同样增加了一个opt参数,除了普通的Post操作外,还允许“广播模式”和“不调度模式”。“广播模式”是指所有在等待该信号量的任务都将获得信号量而转入就绪态;而“不调度模式”是指该次Post操作后不进行任务调度,当用户连续执行多个Post操作,只需在最后一次Post完成后才进行任务调度。前面提到,信号量的等待列表中的任务已经按照优先级从高到低的顺序排序了,因此当执行OSSemPost()操作时如果有任务在等待信号量,则位于等待列表首部的任务会获得信号量从而转入就绪态。当然,如果是“广播模式”则所有任务都被唤醒。

3 μC/OS -III中任务内嵌的信号量

在很多应用中,信号量被用作任务和中断程序同步的手段。举一个常见的例子,有一个串口设备,通过串口接收来自主机的命令并执行相应的任务。串口每当收到数据就会产生一个接收中断,当收到回车符时表示主机端的用户已输入一串命令,这时串口中断服务例程会给另外一个串口服务任务发信号量,由该任务来处理接收到的命令并实现相应功能。在这种情况下,等待该信号量的只有一个任务,而且串口中断服务例程也清楚地知道向哪个任务发信号量。这种应用对信号量的功能需求实际被简化了,如果使用普通的信号量来实现该应用,从功能上是完全可以的,但是在μC/OS -III中针对这种情况有更加高效的方法,那就是任务内嵌的信号量。

在μC/OS -III中每个任务都有内嵌的信号量,当任务被创建时,任务内嵌的信号量会被自动创建,且初始计数为零。在μC/OS -III中,任务内嵌信号量相关的服务函数都是以OSTaskSem???()的形式开头,以区别于普通的信号量。

任务内嵌的信号量相关的API函数如下:

和普通的信号量相比,当调用Pend操作时,无需指定等待的信号量,也无需指定等待的任务,因为默认要等待信号量的就是当前任务,而等待的就是其内嵌的信号量。而opt参数、p_ts参数和普通信号量的调用参数一样。前面提到,对于普通的信号量,任务调用OSSemPend()而被挂起时,内核会新建一个OS_PEND_DATA类型的数据块,然后填写相关的数据域,并根据等待任务的优先级将数据块插入到信号量的等待列表OS_PEND_LIST中对应的位置。任务内嵌的信号量不像普通的信号量那样拥有OS_SEM类型结构体的各个数据域,而是只有信号量计数值SemCtr变量。因为对于任务内嵌的信号量,只有该任务本身能对其进行等待操作,所以不需要普通信号量中的等待列表OS_PEND_LIST。当任务调用OSTaskS-emPend()而被挂起时,也不需要OS_PEND_DATA类型的数据块,内核要做的,除了把任务从就绪表中移除外,只需简单地把任务OS_TCB里的PendOn数据域置为OS_TASK_PEND_ON_TASK_SEM 就可以了。PendOn数据域用来指示任务在等待什么,如普通信号量、消息队列、事件标志组等,而OS_TASK_PEND_ON_TASK_SEM 表示任务等待的是任务内嵌的信号量。

OSTaskSemPost()需要传递一个指向OS_TCB的指针,表示对哪个任务的内嵌信号量进行Post操作。opt参数同样支持“不调度模式”,但与普通信号量的OSSemPost()相比,没有“广播模式”。原因很简单,任务内嵌的信号量最多只有1个任务(就是该任务本身)在等待,因此不存在“广播”的必要性。当别的任务或者中断服务程序调用OSTaskSemPost()对某个任务的内嵌信号量进行“发信号量”操作时,如果该任务在等待其内嵌的信号量,则内核会把其状态改为就绪,这比普通信号量的Post操作又进一步简化了。

结 语

μC/OS-III改进了信号量的使用,用户可以使用“非阻塞”方式等待信号量,而释放信号量则可以选择“广播模式”以及“不调度模式”,提高了使用的灵活性。除此之外,每个任务都有一个内部的信号量。和普通信号量相比,任务内部信号量的操作简化了,因此,在只有一个任务等待信号量的情况下使用任务内嵌的信号量,可以大大提高通信效率。

[1]Jean J Labrosse.μC/OS -II源码公开的实时嵌入式操作系统[M].邵贝贝,等译.北京:中国电力出版社,2001.

[2]Jean J Labrosse.嵌入式实时操作系统μC/OS -II[M].邵贝贝,等译.2版.北京:北京航空航天出版社,2003.

[3]Jean J Labrosse.μC/OS -III the Real Time Kernel for the Freescale Kinetis[EB/OL].[2012 09 -25].http://micrium.com/page/downloads/os -iii_projects.

猜你喜欢
链表内核列表
多内核操作系统综述①
强化『高新』内核 打造农业『硅谷』
学习运用列表法
扩列吧
基于二进制链表的粗糙集属性约简
跟麦咭学编程
基于嵌入式Linux内核的自恢复设计
Linux内核mmap保护机制研究
基于链表多分支路径树的云存储数据完整性验证机制
一种基于有序双端链表的高效排序算法