• 【freertos】011-信号量、互斥量及优先级继承机制源码分析



    前言

    源码实现主要参考消息队列章节,因为底层源码是一样的,所以本章笔记侧重点在信号量、互斥量概念。
    源码部分与消息队列重叠的函数不分析。

    参考:李柱明博客

    11.1 任务同步

    同步,执行完一个再到下一个,一条逻辑流。

    异步,执行者着这个的时候也可执行另外一个,不止一条互相独立的逻辑流。

    资源保护,控制资源的被访问权限。

    在一个多任务并发系统中,可能存在多任务对共享资源的并发访问,这样可能会导致数据不可控、非预期。

    所以我们需要对共享资源进行保护,而任务同步可以实现对共享资源访问进行保护,维护数据一致性,产出结果达预期。

    实现任务同步常见的组件有信号量、互斥量、锁等等。

    同步和互斥的区别:

    • 同步:按序访问,并非针对某个资源的保护,而是达到某个条件才继续执行。
    • 互斥:指某一资源同时只允许一个访问者对其进行访问,具有唯一性和排他性。但是互斥无法限制访问者对资源的访问顺序,所以访问是无序的。

    11.2 信号量概念

    信号量(Semaphore)也是实现任务间通信的机制的一种。

    可实现任务间同步、临界资源的互斥访问。

    信号量的核心其实就是一个非负值,表示当前资源数量:

    • 为0时,表示没有资源,不能访问。
    • 非0时,表示还有资源,可以访问。

    但是在freertos内核,一个信号量除了核心的非负值外,还需要其他数据来维护当前信号量的特性、运作。

    如读、写阻塞链表,实现其阻塞机制,信号量内部中断锁,实现其中断访问特性。

    信号量细分:

    • 二值信号量。其资源最多只有1个。
    • 计数信号量。其资源最多不止1个。
    • 递归信号量。同任务可重复获取。获取多少次就需要释放多少次,这个任务才能真正释放当前递归信号量。

    11.3 二值信号量

    11.3.1 二值信号量概念

    二值信号量既可以用于临界资源访问也可以用于同步功能。

    二值信号量和互斥量其信号量最大都是1,只有0和1两种状态,使用逻辑也类似,但是也有区别,主要在内部实现特性和上层应用场景方面:

    • 互斥量有优先级继承机制。(在互斥量章节详细介绍)
    • 二值信号量存在优先级翻转缺点。
    • 互斥量多用于资源保护。
    • 互斥量多用于任务同步。

    11.3.2 优先级翻转

    例子运行条件:

    • 创建3个任务Task1,Task2和Task3,优先级分别为3,2,1。也就是Task1的优先级最高。
    • 任务Task1和Task3互斥访问串口打印printf,采用二值信号实现互斥访问。
    • 起初Task3通过二值信号量正在调用printf,被任务Task1抢占,开始执行任务Task1。

    运行过程描述如下:

    • 任务Task1运行的过程需要调用函数printf,发现任务Task3正在调用,任务Task1会被挂起,等待Task3释放函数printf。
    • 在调度器的作用下,任务Task3得到运行,Task3运行的过程中,由于任务Task2就绪,抢占了Task3的运行。优先级翻转问题就出在这里了,从任务执行的现象上看,任务Task1需要等待Task2执行完毕才有机会得到执行,这个与抢占式调度正好反了,正常情况下应该是高优先级任务抢占低优先级任务的执行,这里成了高优先级任务Task1等待低优先级任务Task2完成。所以这种情况被称之为优先级翻转问题。
    • 任务Task2执行完毕后,任务Task3恢复执行,Task3释放互斥资源后,任务Task1得到互斥资源,从而可以继续执行。

    该图源自安富莱

    11.3.3 二值信号量运作机制

    创建信号量:

    • 为其信号量分为资源。
    • 初始化信号量控制块及其资源初始个数。

    获取信号量:

    • 如果信号量为1,获取信号量成功,当前信号量改为0状态,在释放前,不能被获取,当前任务可以继续往下跑。
    • 如果信号量为0,说明有任务已经占用当前信号量了,获取失败,要么阻塞,要么返回获取失败。

    释放信号量:

    • 如果信号量为1,说明当前信号量没有被其他任务占用,直接调用释放要么阻塞semGIVE_BLOCK_TIME个节拍,要么直接返回释放失败。
    • 如果信号量为0,说明当前信号量已经被占用,通过释放后,当前信号量为1。

    11.4 计数信号量

    11.4.1 计数信号量概念

    计数信号量的最大资源大于1,主要用于计数。

    获取信号量,信号量值减1;释放信号量,信号量值加1。

    计数信号通常用于两种情况:

    1. 计算事件:

      1. 在事件发生时,获取一个计数信号量,计数信号量的值减1。
      2. 当事件得到处理时,释放一个计数信号量,计数信号量的值加1。
      3. 通过最大信号量值和当前值得差值就知道当前还有多少个事件没有被处理。
    2. 资源管理:

      1. 计数信号量表示当前可用资源值。
      2. 获取信号量表示需要占用一个资源,信号量值减1。
      3. 当资源值为0时,表示没有空闲的支援可用。
      4. 释放信号量表示资源不用了,当前信号量加1.
        该图片源自野火

    11.4.2 计数信号量运作

    和二值信号量类似,只是资源最大值不止1。

    11.5 互斥量

    11.5.1 互斥量概念

    互斥量是包含优先级继承机制的二值信号量。

    而二值信号量是实现同步(任务之间或任务与中断之间)的更好选择,互斥量是实现简单互斥的更好选择。

    互斥量就像保护资源的令牌一样。

    当一个任务希望访问该资源时,它必须首先获得令牌。

    当它使用完该资源时,它必须“归还”令牌——允许其他任务访问相同的资源。

    互斥锁不应该在中断中使用,因为:

    1. 它们包括优先级继承机制,这种机制只在互斥锁来自任务时才有意义,而不是在中断时。
    2. 中断不能阻塞以等待由互斥锁保护的资源变为可用。

    11.5.2 优先级继承机制概念

    优先级继承:高优先级任务TH在等待低优先级的任务TL继承占用的竞争资源时,为了使TH能够尽快获得调度运行,由操作系统把TL的优先级提高到TH的优先级,从而让TL以TH的优先级参与调度,尽快让TL执行并释放调TH欲获得的竞争资源,然后TL的优先级调整到继承前的水平,此时TH可获得竞争资源而继续执行。

    在FreeRTOS操作系统中为了降低优先级翻转问题利用了优先级继承算法。

    不过优先级继承也不能解决优先级反转。

    它只是在某些情况下将其影响降到最低。

    举个栗子:

    三个任务:任务A优先级10,任务B优先级5,任务C优先级1。

    在任务C占用互斥量时,任务A就绪,也需要该互斥量,此时任务C的优先级会继承任务A的优先级,从优先级1跃升到优先级10。就算当前任务B就绪了,也不能打断任务C,因为优先级比10底。

    11.5.3 互斥量运作

    和二值信号量类似,比二值信号量多个优先级继承机制。

    11.6 递归互斥量

    11.6.1 递归互斥量概念

    就是互斥量具有递归性。

    递归使用的互斥量可以被其所有者反复“获取”。

    互斥量只有在所有者为每个成功的xSemaphoreTakeRecursive()请求调用xSemaphoreGiveRecursive()之后才会再次可用。

    即是互斥量被同一个任务连续申请成功N次,就需要释放N次才算真正释放该互斥量。

    互斥量类型的信号量不能在中断服务程序中使用。

    因为:

    1. 互斥量包含了优先级继承机制,只有在互斥锁来自任务而不是中断时才有意义。
    2. 中断不能阻塞以等待由互斥锁保护的资源变为可用。

    11.6.2 递归互斥量运作

    参考互斥量运作机制,比互斥量多个递归性。

    11.7 死锁概念

    就是逻辑上获取一个已经被占用且逻辑上不可能被释放的锁而阻塞,永久阻塞。

    避免死锁需要遵循的规则

    • 对共享资源操作前一定要获得锁;
    • 完成操作后一定要释放锁;
    • 尽量短时间占用锁;
    • 如果有多锁, 如获得顺序是ABC连环扣, 释放顺序也应该是ABC。

    11.8 创建信号量

    11.8.1 创建二值信号量

    xSemaphoreCreateBinary()

    就是创建一个类型queueQUEUE_TYPE_BINARY_SEMAPHORE、是队列成员为1、不含数据区的队列。

    11.8.2 创建计数信号量

    xSemaphoreCreateCounting( uxMaxCount, uxInitialCount )

    11.8.3 创建互斥量

    xSemaphoreCreateMutex()

    11.8.4 创建递归互斥量

    11.8.5 信号量控制块数据结构图

    11.8.6 互斥量控制块数据结构图

    11.9 获取信号量

    获取和释放信号量都是区分任务和中断调用的API的,其主要区别也是中断调用是不能阻塞。

    这里主要分析任务调用。(中断调用,按区别理解下就可以了)

    二值信号量、计数信号量、互斥量都是使用xSemaphoreTake()获取信号量。

    而递归互斥量使用xSemaphoreTakeRecursive()获取互斥量。

    11.9.1 xSemaphoreTake()

    11.9.2 xSemaphoreTakeRecursive()

    11.10 释放信号量

    获取和释放信号量都是区分任务和中断调用的API的,其主要区别也是中断调用是不能阻塞。

    这里主要分析任务调用。(中断调用,按区别理解下就可以了)

    二值信号量、计数信号量、互斥量都是使用xSemaphoreGive()获取信号量。

    而递归互斥量使用xSemaphoreGiveRecursive()获取互斥量。

    注意:互斥量和递归互斥量只有持有者才有权限释放。

    11.10.1 xSemaphoreGive()

    参考消息队列章节。

    11.10.2 xSemaphoreGiveRecursive()

    11.11 删除信号量

    vSemaphoreDelete()用于删除一个信号量,包括二值信号量,计数信号量,互斥量和递归互斥量。

    11.12 优先级继承机制主要源码

    注意:当持有者持有多个互斥量时,不能通过单个互斥量来解除或者重置优先级继承的优先级,只能选择忽略。这种情况也会存在优先级翻转。

    优先级继承机制主要数据:Queue_t->u->xQueue

    11.12.1 优先级继承

    在互斥量被其它任务占用,当前高优先级任务因为该互斥量而进入阻塞时,会发生优先继承,互斥量持有者的任务优先级会跃升到阻塞在当前接收阻塞链表中最高,且比持有者高的任务优先级。

    xTaskPriorityInherit()

    11.12.2 解除优先级继承

    互斥量持有者真正释放互斥量时,方可解除优先级继承

    正常由持有者解除:xTaskPriorityDisinherit()

    11.12.3 重置优先级继承

    获取互斥量阻塞阻塞超时时会检查重置优先级继承。

    高优先级任务阻塞超时而解除:

    • 这种情况不是解除阻塞,而是重置阻塞。
    • 获取获取互斥量阻塞链表任务中的最高优先级:prvGetDisinheritPriorityAfterTimeout()
    • 重置优先级继承:vTaskPriorityDisinheritAfterTimeout()

    prvGetDisinheritPriorityAfterTimeout()

    vTaskPriorityDisinheritAfterTimeout()

    • 只有持有者只持有当前互斥量才能重置优先级继承,因为如果持有者持有多个互斥量时,并不能只参考当前互斥量来重置优先级。

    __EOF__

  • 本文作者: 李柱明
  • 本文链接: https://www.cnblogs.com/lizhuming/p/16350240.html
  • 关于博主: 嵌入式从业者。RTOS、Linux ...
  • 版权声明: 版权归博主所有
  • 声援博主: 学习笔记分享
  • 相关阅读:
    商标申请注册交费就一定会下注册证?
    在cmd里查看mvn输入mvn -v出现问题?
    java.sql.SQLException: Value ‘xxx‘ can not be represented as java.sql.Timestamp
    排序算法之详解冒泡排序
    软考-软件工程
    【编程之路】面试必刷TOP101:动态规划(78-82,Python实现)
    VSCode官方的配置同步方案
    关于pytorch的数据处理-数据加载Dataset
    【思悟】一定要给自己留出空间
    matlab——simulink学习(四)
  • 原文地址:https://www.cnblogs.com/lizhuming/p/16350240.html