RTOS之UCOS(三)---任务间同步与通信

本文深入探讨了RTOS(实时操作系统)中UCOS的临界区保护、事件控制块(ECB)以及任务间通信机制,包括信号量、互斥量、消息邮箱和消息队列。临界区通过OS_ENTER_CRITICAL()和OS_EXIT_CRITICAL()宏来实现同步保护,而事件控制块用于管理任务间通信的信号和数据。文章详细介绍了各种通信工具的工作原理,如信号量的计数器和任务等待表,以及互斥量如何处理任务优先级反转问题。此外,消息邮箱和消息队列提供了多任务间的数据传递方式。
摘要由CSDN通过智能技术生成

一、临界区保护

之前谈到C++多线程并发编程时就强调过线程同步的问题,现在操作系统进行多任务调度时自然也少不了任务同步的问题,前面谈线程同步时已经讲清楚了如何防止共享资源的并发访问,任务同步的情况与此类似,就不再赘述了。

线程同步中防止共享资源并发访问的方法主要是使用锁机制或原子操作,操作系统任务同步中主要也是加锁和原子操作,但多出了一个临界区的概念,所谓临界区(也称为临界段),就是访问和操作共享数据的代码段。为了避免在临界区中并发访问,编程者必须保证这些代码原子的执行,操作在执行结束前不可被打断,就如同整个临界区是一个不可分割的指令一样。理解了临界区,就明白要保证任务同步,需要对临界区进行加锁。任务的打断或切换主要是通过中断来实现的,所以在MCU中可以通过关闭/打开中断功能实现对临界区的加锁保护。

以UCOS为例,它是通过两个宏OS_ENTER_CRITICAL()和OS_EXIT_CRITICAL()实现对临界区的同步保护的,UCOS给出了三种实现方式,常用的是第三种,实现代码如下:

#if OS_CRITICAL_METHOD == 1
#define OS_ENTER_CRITICAL() __asm__("cli")
#define OS_EXIT_CRITICAL() __asm__("sti")
#endif
 
#if OS_CRITICAL_METHOD == 2
#define OS_ENTER_CRITICAL() __asm__("pushf \n\t cli")
#define OS_EXIT_CRITICAL() __asm__("popf")
#endif
 
#if OS_CRITICAL_METHOD == 3
#define OS_ENTER_CRITICAL() (cpu_sr = OSCPUSaveSR())
#define OS_EXIT_CRITICAL() (OSCPURestoreSR(cpu_sr))
#endif

第一种方式,OS_ENTER_CRITICAL()简单地关中断,OS_EXIT_CRITICAL()简单地开中断,这种方式虽然简单高效,但无法满足嵌套的情况。第二种方式,OS_ENTER_CRITICAL()会在关中断前保存之前的标志寄存器内容到堆栈中,OS_EXIT_CRITICAL()从堆栈中恢复之前保存的状态,这样就允许了临界区嵌套的情况,但有时候入栈的状态标志寄存器内容会被冲掉,导致恢复状态时出现异常错误。 第三种,在关中断前,使用局部变量保存中断状态,这也是几乎所有实时操作系统共有的选择。

临界区保护是需要关闭中断的,如果频繁关中断,势必会影响RTOS系统响应的实时性,有没有更好的方式实现任务同步和任务间通信呢?回想下C++线程同步中用到的两个工具:互斥量条件变量,互斥量提供了一个全局互斥锁,可以方便管理多线程间对共享资源的访问同步,条件变量则引入了通知唤醒机制,避免了程序无谓的等待,且两者一般配合使用保证线程间的高效同步。那么操作系统比如UCOS中有没有类似的工具实现任务同步呢?

二、事件控制块

2.1 事件控制块的结构

简单的操作系统比如UCOS提供了任务控制块TCB来描述和管理任务,同时提供了一个事件控制块ECB来描述和管理事件,这里的事件指的是任务间传递的信号,包括对共享资源进行同步访问的控制信号比如信号量与互斥量,也包括多个任务间传递的数据信号比如消息邮箱和消息队列。下面先给出ECB(EVENT CONTROL BLOCK)的数据结构代码:

// Micrium\Software\uCOS-II\Source\ucos_ii.h

typedef struct os_event {
   
    INT8U    OSEventType;                    /* Type of event control block (see OS_EVENT_TYPE_xxxx)    */
    void    *OSEventPtr;                     /* Pointer to message or queue structure                   */
    INT16U   OSEventCnt;                     /* Semaphore Count (not used if other EVENT type)          */
    OS_PRIO  OSEventGrp;                     /* Group corresponding to tasks waiting for event to occur */
    OS_PRIO  OSEventTbl[OS_EVENT_TBL_SIZE];  /* List of tasks waiting for event to occur                */

#if OS_EVENT_NAME_EN > 0u
    INT8U   *OSEventName;
#endif
} OS_EVENT;

#define  OS_EVENT_TYPE_UNUSED           0u
#define  OS_EVENT_TYPE_MBOX             1u
#define  OS_EVENT_TYPE_Q                2u
#define  OS_EVENT_TYPE_SEM              3u
#define  OS_EVENT_TYPE_MUTEX            4u
#define  OS_EVENT_TYPE_FLAG             5u

其中OSEventType表示事件类型,主要类型也在上面列出来了,就是前面提到的控制类型如信号量/互斥量和消息类型如消息邮箱/消息队列,当然还包括未使用类型。OSEventPtr指向消息类型结构的地址,OSEventCnt记录控制类型的信息如信号量计数器。OSEventGrp与OSEventTbl[OS_EVENT_TBL_SIZE]则是共同管理一个事件等待任务表,跟前面任务调度器中提到的OSRdyGrp与OSTCBTbl[OS_MAX_TASKS]共同管理一个任务就绪表类似,无非前者表示的是处于等待事件状态的任务及其优先级,后者表示的是处于就绪状态的任务及其优先级,对任务等待表的操作(某任务对应位置1、某任务对应位清0、查找到当前任务就绪表中优先级最高的任务优先级)也是跟对任务就绪表的操作一致,后面给出操作代码。二者还有一点不同之处是,整个操作系统由一个任务就绪表管理所有的任务,而每个事件都有一个任务等待表管理请求该事件的任务。下面是等待任务表图示:
任务等待表
任务间通信一般都是有方向性的,比如控制信息互斥量需要先等一个任务释放互斥量,另一个任务才能获得该互斥量,再比如数据信息消息邮箱需要先等一个任务写入消息,另一个任务才能读取消息。假如前一个任务还在占用某共享资源,后一个任务就需要等待直到前一个任务对共享资源访问完成。任务的切换和执行都是针对处于就绪态的任务而言的,如果一个任务正在等待某一资源或事件,则可以先将该任务置于等待状态,这样该任务就不会被执行。如果前一个任务已完成对某共享资源的访问,则将后一个处于等待状态的任务从等待状态转换到就绪状态,继续参与任务调度和运行。这不仅实现了互斥锁访问同步的功能,也实现了类似通知-唤醒机制的条件变量的功能,不得不赞叹操作系统设计者的巧思。

由于每个事件控制块都有一个任务等待表,每个任务等待表可以容纳所有的64个任务,所以事件控制块是支持多个任务同时等待一个事件的,至于哪一个任务优先获得该事件的访问权,还是跟优先级有关,处于等待状态最高优先级的任务优先拥有该事件的访问权。一个任务也可能同时等待多个事件,UCOS也是支持的,任务控制块里面有一个指向事件控制块的指针变量,同时也提供了一个指向事件控制块数组的指针变量,所以一个任务是可以同时等待多个事件的,这多个事件控制块以数组的形式管理。

2.2 事件控制块的操作

对事件控制块的操作主要是对任务等待表的操作,也可以说是对任务状态的切换,比如使一个任务进入等待状态或就绪状态等。任务控制块在使用前自然需要对其进行初始化操作,初始化只是将任务等待表清零。让一个任务进入就绪态或等待态都需要操作任务就绪表和任务等待表,由于之前对这两个表具体是如何操作的略去了,下面给出相关的代码供参考:

// Micrium\Software\uCOS-II\Source\os_core.c

/*
*********************************************************************************************************
*                             MAKE TASK READY TO RUN BASED ON EVENT OCCURING
*
* Description: This function is called by other uC/OS-II services and is used to ready a task that was
*              waiting for an event to occur.
*
* Arguments  : pevent      is a pointer to the event control block corresponding to the event.
*
*              pmsg        is a pointer to a message.  This pointer is used by message oriented services
*                          such as MAILBOXEs and QUEUEs.  The pointer is not used when called by other
*                          service functions.
*
*              msk         is a mask that is used to clear the status byte of the TCB.  For example,
*                          OSSemPost() will pass OS_STAT_SEM, OSMboxPost() will pass OS_STAT_MBOX etc.
*
*              pend_stat   is used to indicate the readied task's pending status:
*
*                          OS_STAT_PEND_OK      Task ready due to a post (or delete), not a timeout or
*                                               an abort.
*      
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

流云IoT

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值