关于RTOS中信号量、互斥量、邮箱、消息队列的一些理解

1. 信号量

信号量有两种:计数性信号量和二值信号量,计数性信号量可以被获取多次,二值信号量只有0和1两种状态,只能被获取一次。

信号量可以用来对资源进行保护,防止多个任务同时访问某个资源。为资源创建一个专属的二值信号量,任务在申请访问一个资源之前,先申请获取信号量,如果当前没有任务正在访问资源,则获取信号量可以成功,可以继续访问资源;如果当前资源正在被某个任务访问,则获取信号量会失败,任务进入挂起状态,等待其它任务访问资源完成并释放信号量后再访问资源。

如果某个资源允许多个任务同时访问,可以采用计数性信号量,每次有任务申请获取信号量则信号量计数减一,直到减为0后不再允许其他任务再申请获取,即可以通过信号量的初始值来控制同时访问资源的最大任务数量。

2. 优先级反转

使用信号量可能会导致优先级反转的问题,详细的说明见下图,截取自RT-Thread编程指南,简单来说就是当一个高优先级任务申请访问资源时,资源正在被低优先级任务占用,所以高优先级任务被挂起,此时一个中优先级任务就绪,剥夺低优先级任务的CPU使用权,开始执行,就造成了该任务先于高优先级任务开始执行。
关于RTOS中信号量、互斥量、邮箱、消息队列的一些理解

3. 互斥量

互斥量可以理解为二值信号量,它可以避免上述优先级反转的问题,方法是当高优先级任务申请访问资源但资源被低优先级任务时,将低优先级任务的优先级提高到高优先级同级别,就避免了中优先级任务就绪时剥夺低优先级任务的CPU使用权。详细的说明见下图。任务在申请获取互斥量时,系统会判断当前占有该互斥量的任务的优先级和申请任务的优先级,如果申请任务优先级更高,系统会把这个更高的优先级赋给占有互斥量的任务,当任务释放互斥量时再恢复其原有的优先级。
关于RTOS中信号量、互斥量、邮箱、消息队列的一些理解
此外,互斥量还支持重入,即多次获取,这时互斥量被获取了多少次就需要释放多少次,才能完全释放。且互斥量是归属于某个任务的,哪个任务获取互斥量就要由哪个任务释放互斥量,其它任务不能释放。

4. 死锁

死锁也称抱死,是指两个任务无限制地互相等待对方正在占用的资源。详细说明如下,截取自《嵌入式实时操作系统uC/OS-III》:
关于RTOS中信号量、互斥量、邮箱、消息队列的一些理解
避免死锁有以下三个方法:

  • (1)不同任务中用相同的顺序申请多个资源;
  • (2)任务中需要用到多个资源时,先获取所有资源,再做下一步工作;
  • (3)在申请获取互斥量或信号量时设置超时时间,避免永远等待。

最有效的是方法(1),按相同的顺序访问资源,就可以避免两个任务和两个资源间的交叉访问,如果任务中使用资源的顺序固定,那么可以先获取所有资源的访问权限,再访问资源,这样就能避免死锁的问题。方法(3)只是能暂时避免死锁,但会导致系统报错,不推荐。

5. 邮箱和消息队列

5.1 uCOS

uCOS-II有邮箱和消息队列两个概念,都是Event的一种,对于邮箱来说,Event控制块中有一个指针直接指向数据地址,所以每次只能传递一个数据指针。而对消息队列来说,Event控制块中的指针指向一个消息队列,消息队列中的每一个节点包含一个数据地址,所以能同时传递多个数据地址。队列大小为1的消息队列与邮箱的作用是一样的。

uCOS-II中有一个静态的全局结构体数组OSQTbl[OS_MAX_QS],包含了系统中所有可用的消息队列,每创建一个消息队列,就从这个数组中取一个节点出来。

在uCOS-III中,取消了邮箱的概念,只有消息队列。在代码中,定义了一个消息池:

OS_MSG         OSCfg_MsgPool       [OS_CFG_MSG_POOL_SIZE];

所有消息队列在发送消息的时候,都从消息池里取一个节点出来,挂到消息队列的消息链表上,消息队列控制块有指针指向消息链表的头和尾,还会记录消息节点的数量。

消息队列控制块的定义如下:

struct  os_msg_q {                                          /* OS_MSG_Q                                               */
    OS_MSG              *InPtr;                             /* Pointer to next OS_MSG to be inserted  in   the queue  */
    OS_MSG              *OutPtr;                            /* Pointer to next OS_MSG to be extracted from the queue  */
    OS_MSG_QTY           NbrEntriesSize;                    /* Maximum allowable number of entries in the queue       */
    OS_MSG_QTY           NbrEntries;                        /* Current number of entries in the queue                 */
    OS_MSG_QTY           NbrEntriesMax;                     /* Peak number of entries in the queue                    */
};

5.2 rt-thread

RTT中有邮箱和消息队列两个概念,它的邮箱和uCOS-II的邮箱概念不同。RTT的邮箱是基于一个数组来实现的,数组的成员是4字节变量,可以是数据地址或变量,这个数组就是该邮箱的消息池,在创建邮箱的时候固定进行分配。所以RTT的邮箱在传递数据时每次固定传输一个4字节的数据,每次发送邮件的时候从消息池中取一个4字节变量出来,接收邮件的时候释放一个4字节变量。邮箱控制块中存储着当前邮箱发送邮件(in_offset)和接收邮件(out_offset)的数据地址,以及当前邮箱中存在的邮件数量(entry)。

邮箱控制块的定义如下:

struct rt_mailbox
{
    struct rt_ipc_object parent;                        /**< inherit from ipc_object */

    rt_ubase_t          *msg_pool;                      /**< start address of message buffer */

    rt_uint16_t          size;                          /**< size of message pool */

    rt_uint16_t          entry;                         /**< index of messages in msg_pool */
    rt_uint16_t          in_offset;                     /**< input offset of the message buffer */
    rt_uint16_t          out_offset;                    /**< output offset of the message buffer */

    rt_list_t            suspend_sender_thread;         /**< sender thread suspended on this mailbox */
};

RTT消息队列的实现和uCOS相似。但RTT不再是所有消息队列共用一个消息池,而是每个消息队列单独有一个消息池,分别管理。

RTT的消息队列控制块定义如下:

struct rt_messagequeue
{
    struct rt_ipc_object parent;                        /**< inherit from ipc_object */

    void                *msg_pool;                      /**< start address of message queue */

    rt_uint16_t          msg_size;                      /**< message size of each message */
    rt_uint16_t          max_msgs;                      /**< max number of messages */

    rt_uint16_t          entry;                         /**< index of messages in the queue */

    void                *msg_queue_head;                /**< list head */
    void                *msg_queue_tail;                /**< list tail */
    void                *msg_queue_free;                /**< pointer indicated the free node of queue */

    rt_list_t            suspend_sender_thread;         /**< sender thread suspended on this message queue */
};
上一篇:SegmentTree——区间更新问题


下一篇:【操作系统】各类实时操作系统调研