μC/OS―III对信号量的改进
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_PE ND_DATA数据块的。也就是说,每当有一个新的OS_PEND_DATA数据块需要插入到双向链表时(也就是任务因等待信号量而被挂起时),内核会从链表头部开始扫描各个OSPEND_DATA数据块所对应的等待任务的优先级(通过OS_PEND_DATA数据块内部的TCBPtr指针可以从任务控制块内部获得任务的优先级),直到找到比当前需要插入的任务的优先级低的任务,然后把新的OS PEND_DATA数据块插入到该位置前。如果链表中已有和需要插入的任务优先级相同的任务,则新插入的任务放到优先级相同的任务后。道理很简单,优先级相同,晚到的任务没有任何理由比早到的任务先获得信号量。基于上述排列方法,位于双向链表头部的任务总是等待的任务中优先级最高的。因此,当用户释放信号量时,总是双向链表头部的任务获得信号量,而不必再执行“查找最高优先级”的过程了。
μ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完成后才进行任务调度。前面提到,信号量的等待列表中的任务已经按照优先级从高到低的顺序排序了,因此当执行OSSem Post()操作时如果有任务在等待信号量,则位于等待列表首部的任务会获得信号量从而转入就绪态。当然,如果是“广播模式”则所有任务都被唤醒。
3 μC/OS-Ⅲ中任务内嵌的信号量
在很多应用中,信号量被用作任务和中断程序同步的手段。举一个常见的例子,有一个串口设备,通过串口接收来自主机的命令并执行相应的任务。串口每当收到数据就会产生一个接收中断,当收到回车符时表示主机端的用户已输入一串命令,这时串口中断服务例程会给另外一个串口服务任务发信号量,由该任务来处理接收到的命令并实现相应功能。在这种情况下,等待该信号量的只有一个任务,而且串口中断服务例程也清楚地知道向哪个任务发信号量。这种应用对信号量的功能需求实际被简化了,如果使用普通的信号量来实现该应用,从功能上是完全可以的,但是在μ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。当任务调用OSTaskSemPend()而被挂起时,也不需要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
2
关键词: &mu C/OS&mdash III 信号量 实时操作系统
加入微信
获取电子行业最新资讯
搜索微信公众号:EEPW
或用微信扫描左侧二维码