• 【freertos】012-事件标志概念和实现细节



    前言

    默认以32bit事件类型和任务专用API讲解。

    事件独立于消息队列、信号量和互斥量这些章节是因为内部实现机制不同。

    参考:李柱明博客:https://www.cnblogs.com/lizhuming/p/16353453.html

    12.1 实现事件机制的预备知识

    12.1.1 守护任务

    和守护进程一样理解即可。

    守护任务(Daemon)又称为精灵任务,是运行在后台的一种特殊任务,周期性地执行某种任务或等待处理某些事情的发生,主要表现为以下两个特点:

    • 长期运行。守护任务是一种生存期很长的任务,一般在系统启动就开始运行,到系统退出或调用接口强制停止而结束。
    • 后台运行。用户一般不能直接接触控制该任务。用户的任务也不会影响守护任务的生存。

    比如freertos的软件定时器服务任务。

    12.1.2 事件的不确定性

    先明白freertos不允许在中断或临界中操作不确定的业务。

    而事件有个不确定的业务是因为事件的一对多特性,如当发生事件置位时,会遍历阻塞在这个事件组的链表,而阻塞在这个链表任务是不确定的。

    所以任务专用的API事件置位时,不是在临界,而是在调度锁内完成的。

    而中断专用的API事件置位,整个上下文都不符合要求,所以中断专用的API的事件置位实现是通过给FreeRTOS的守护任务发送一个消息,让置位事件组的操作在守护任务(软件定时器服务任务)里面完成,守护任务是基于调度锁而非临界段的机制来实现的。

    12.1.3 事件组的报文

    事件组报文就一个系统位长的变量。

    最高位bit表示该值表示事件组有效。这个bit影响到整个系统的事件阻塞、优先级继承等等任务事件节点相关的业务的实现。

    最高字节的[6:0]bit是该事件组的控制信息。

    剩余bit表示各个事件。

    12.2 事件概念

    事件是一种实现任务间通信的机制,主要用于实现多任务间的同步,但事件通信只能是事件类型的通信,无数据传输。

    和信号量又是不同的,事件可以一对多,多对多同步。

    事件(bit):

    • 0:事件没有发生。需要该事件的任务阻塞或直接返回失败。
    • 1:事件发生。需要该事件的任务解除阻塞返回成功或者直接返回成功。

    事件组:多个事件组合在一起,用户可以选择等待某个事件或者等待所有事件实现同步。

    12.3 事件用途参考

    事件位用于指示事件是否发生。事件位通常被称为事件标志。例如,申请可以:

    • 定义一个位(或标志),当它设置为1时,表示“消息已接收并准备处理”,当它设置为0时,表示“没有消息等待处理”。
    • 定义一个位(或标志),当它设置为1时,表示“应用程序已将准备发送到网络的消息排队”,而当它设置为0时,表示“没有准备发送到网络的消息排队”。
    • 定义一个位(或标志),当它设置为1时,表示“是时候向网络发送一个心跳消息了”,而当它设置为0时,表示“还没有到发送另一个心跳消息的时候了”。

    事件组是事件位的集合。事件组中的个别事件位由位号引用。展开上面提供的示例:

    • 表示“消息已接收并准备处理”的事件位可能是事件组中的位0。
    • 在同一个事件组中,表示“应用程序已将准备发送到网络的消息排队”的事件位可能是第1位。
    • 表示“是时候向网络发送心跳消息了”的事件位可能位于同一事件组中的第2位。

    12.4 事件实现原理简述

    核心原理就是一个全局变量+访问机制+阻塞机制。

    这些组件都用控制块数据结构管理起来。

    封装一些API访问即可。

    事件组由EventGroupHandle_t类型的变量引用。

    如果configUSE_16_BIT_TICKS设置为1,则事件组中存储的比特数(或标志数)为8,如果configUSE_16_BIT_TICKS设置为0,则为24。

    configUSE_16_BIT_TICKS的依赖源于任务内部实现中用于线程本地存储的数据类型。

    事件组中的所有事件位都存储在EventBits_t类型的单个无符号变量中。

    事件位0存储在位0中,事件位1存储在位1中,依此类推。

    如图是一个24位事件组,它使用3位来保存前面描述的3个示例事件。在图像中,只设置了事件位2。

    12.5 事件实现需要克服的问题

    在实现事件组时,RTOS必须克服的两个主要挑战是:应用程序竞态混合运行不确定性。

    12.5.1 避免在用户的应用程序中创建竞争条件

    如果出现以下情况,事件组实现将在应用程序中产生竞争条件:

    • 不清楚谁负责清除单个事件。
    • 不清楚何时要清除位。
    • 不清楚在任务退出测试位值的 API 函数时是否设置或清除了事件(可能是另一个任务或中断已更改该位的状态)。

    这样对全局资源的这个事件组来说,应用层的调用很模糊,所以为了解决这些问题,避免应用程序的竞态产生,实现事件机制时可以用一下方法解决:

    • FreeRTOS 事件组实现通过包含内置智能来确保位的设置、测试和清除看起来是原子的,在处理了所有对该事件感兴趣的任务后再统一对这个事件bit做更新,从而消除了竞争条件的可能性。

      • 但是这样就会出现不确定性。第二个问题就是解决不确定性。
    • 线程本地存储(任务事件节点值存储当前任务对该事件组的信息)和 API 函数返回值的谨慎使用。

    12.5.2 避免不确定性

    事件组概念意味着不确定性行为,因为它不知道在一个事件组上有多少任务被阻塞,因此当事件位被设置时,不知道有多少条件需要被测试或多少任务需要被解除阻塞。

    FreeRTOS 质量标准不允许在中断被禁用时或在中断服务程序中执行不确定的动作。为了确保在设置事件位时不违反这些严格的质量标准:

    • 调度锁用于确保在 RTOS 任务设置事件位时中断保持启用状态。

      • 即是原子性不使用临界,而是调度锁级别。
    • 集中延迟中断机制用于在尝试从中断服务程序设置事件位时,将设置位的动作推迟到任务。

      • 即是把中断上下文对事件的操作转包给守护任务实现,这样就维护了调度锁级别的原子性。

    12.6 事件控制块

    从事件控制块看就知道事件使用了非常少的RAM实现。

    EventBits_t uxEventBits

    • 最高bit:表示当前值为事件组件使用。主要用于在任务事件节点值中区分任务优先级。
    • 最高字节的[6:0]bit:表示事件组控制信息。
    • 剩下的bit:表示各个事件。

    事件控制信息:

    • eventCLEAR_EVENTS_ON_EXIT_BIT:该标记用于等待事件时配置,表示获得事件触发任务解锁后,要清除这些事件。
    • eventUNBLOCKED_DUE_TO_BIT_SET:用于区分等待事件而阻塞的任务被唤醒的原因(事件或超时),该标记表示因为事件而触发唤醒。
    • eventWAIT_FOR_ALL_BITS:用于等待事件时配置,表示该任务等待标记的所有事件都发生时才有效。否则就是任意事件有效。
    • eventEVENT_BITS_CONTROL_BYTES:用于区分事件控制字段和事件字段。

    12.7 创建事件

    创建事件使用API xEventGroupCreate()

    12.8 事件置位

    注意:事件置位是没有阻塞这个说法的,事件发生了就置位即可。

    xEventGroupSetBits()用于置位事件组中指定的位,当位被置位之后,阻塞在该位上的任务将会被解锁。

    事件置位,只需要设置事件字段即可,事件标记字段和事件控制字段只是在等待事件时使用的。

    事件组的解除任务阻塞处理:vTaskRemoveFromUnorderedEventList()

    12.9 事件置位中断版

    xEventGroupSetBitsFromISR()

    事件置位业务发送到守护任务中运行,发送的API实现:

    事件置位函数在守护任务的回调:

    12.10 等待事件

    xEventGroupWaitBits()

    • 因为有阻塞机制,所以实现的框架和消息队列接收消息的函数类似,上段检查、获取数据,下段处理阻塞。

    事件匹配prvTestWaitCondition()

    12.11 清除事件

    xEventGroupClearBits()xEventGroupClearBitsFromISR()都是用于清除事件组指定的位,如果在获取事件的时候没有将对应的标志位清除,那么就需要用这个函数来进行显式清除。

    xEventGroupClearBitsFromISR()这个中断专用API也是和事件置位一样,都是发通知给守护任务执行置位业务。

    这里主要分析xEventGroupClearBits()

    12.12 删除事件

    vEventGroupDelete()


    __EOF__

  • 本文作者: 李柱明
  • 本文链接: https://www.cnblogs.com/lizhuming/p/16353453.html
  • 关于博主: 嵌入式从业者。RTOS、Linux ...
  • 版权声明: 版权归博主所有
  • 声援博主: 学习笔记分享
  • 相关阅读:
    雪花算法生成分布式主键ID
    禅道的使用
    【Java】设计Java程序,假设有50瓶饮料,喝完3个空瓶可以换一瓶饮料,依次类推,请问总共喝了多少瓶饮料?
    2、数组、Map+HashMap、Set+Hashset、Char和Character类、String类和Char类、Math类
    python 模块 — logging模块、smtplib和email模块
    现代控制理论入门+理解
    【趣学算法】第一章读书笔记
    【C++】多态的使用详解
    Python中time模块
    Locality-Driven Dynamic GPU Cache Bypassing
  • 原文地址:https://www.cnblogs.com/lizhuming/p/16353453.html