• 【freertos】013-任务通知及其实现细节


    前言

    参考:

    13.1 任务通知实现原理个人构想

    任务通知的实现机制和消息队列和事件标志机制不一样。

    消息队列和事件标志机制实现需要的资源,数据结构都是需要申请,创建的,独立于内核,分离于任务的组件。

    这些组件的实现构想都是拿个内存块组成数据结构,一部分内存做信息传递,一部分内存做阻塞链表等等,然后通过封装各种API实现各种对这个数据结构的骚操作而形成的任务间通信机制。

    而任务通知,首先内存源自任务控制块,这个内存的数据结构并未确定,所以我们能把这个内存做信息传递。

    阻塞部分,我们可以利用系统的延时链表,做个等待信息阻塞。

    不过没有发送阻塞,因为向某个任务发送信息只是向某个任务控制块写入数据的骚操作而已,没有因为没有空间而阻塞的这个说法,因为任务控制块根本就没有给被写入失败而阻塞的链表的内存。

    明白了任务通知的实现原理,就知道这玩意是啥了。

    13.2 任务通知概念

    每个RTOS任务都有一个任务通知数组。

    每个任务通知都有一个通知状态,可以是pending或者not pending,并且有一个32位的通知值。

    常量configTASK_NOTIFICATION_ARRAY_ENTRIES设置任务通知数组中的索引数量。

    注意:在FreeRTOS V10.4.0之前,任务只有一个任务通知,而不是一组通知。

    任务的通知是直接发送给任务的事件,而不是通过队列、事件组或信号量等中间对象间接发送给任务。

    向任务发送直接到任务的通知会将目标任务通知的状态设置为pending

    就像一个任务可以阻塞一个中间对象,比如一个信号量,以等待该信号量可用一样,一个任务也可以阻塞一个任务通知,以等待该通知的状态变为pending

    13.3 任务通知实现各种IPC

    任务通知方式可以实现计数信号量,二值信号量,事件标志组和消息邮箱(消息邮箱就是消息队列长度为1的情况)。

    使用方法与前面章节讲解的事件标志组和信号量基本相同,只是换了不同的函数来实现。

    任务通知方式实现的计数信号量,二值信号量,事件标志组和消息邮箱是通过修改任务对应任务控制块的ulNotifiedValue数组成员实现:

    • 覆盖该值,无论接收任务是否读取了被覆盖的值。(消息邮箱)
    • 仅当接收任务已读取才能写入。(消息队列)
    • 在值中设置一个或多个位。(事件组)
    • 增加(加1)值。(信号量)

    调用xTaskNotifyWait()xTaskNotifyWaitIndexed()来读取一个通知值,将该通知的状态清除为not pending

    通知状态也可以通过调用xTaskNotifyStateClear()xTaskNotifyStateClearIndexed()显式地设置为not pending

    13.4 任务通知性能优势

    使用任务通知比通过信号量等ICP通信方式解除阻塞的任务要快45%,并且更加省RAM内存空间。

    13.5 任务通知使用注意

    1. freertos的任务通知功能默认是启用的,可以通过在FreeRTOSConfig.h中将configUSE_TASK_NOTIFICATIONS设置为0来关闭。
    2. RTOS 任务通知只能在只有一个任务可以作为事件的接收者时使用。
    3. 数组中的每个通知都是独立操作的。一个任务一次只能阻塞数组中的一个通知,并且不会被发送到任何其他数组索引的通知解除阻塞。
    4. 在使用 RTOS 任务通知代替队列的情况下:虽然接收任务可以在阻塞状态等待通知(因此不消耗任何 CPU 时间),但如果发送不能立即完成,则发送任务不能在阻塞状态下等待发送完成。
    5. FreeRTOS StreamMessage Buffers 在数组索引 0 处使用任务通知。如果想在调用 StreamMessage Buffer API 函数时保持任务通知的状态,则在数组索引大于 0 处使用任务通知。

    13.6 任务通知数据结构

    任务通知是任务控制块的资源。

    ulNotifiedValue

    • 任务通知值数组。

    ucNotifyState:

    • 任务通知状态数组。
    • 与任务通知值一一对应。

    13.7 任务通知为什么没有写阻塞?

    通过任务通知的数据结构就知道,其数据结构嵌入在任务控制块中,一个通知对应一个通知状态。

    13.7.1 读阻塞实现

    读阻塞很容易实现,读取当前任务通知不满足条件时,可以将其阻塞到延时链表或者挂起链表,当有任务往这个任务通知发送数据时,满足当前任务通知要求,可以将当前任务通知从延时链表或者挂起链表解除阻塞。

    13.7.2 写阻塞实现(修改内核组件实现)

    先按照当前官方任务通知数据结构分析,并不能实现。

    试想下,如果不能写,就进入阻塞,可以参考读阻塞一样先插入延时链表或挂起链表,但是怎么唤醒呢?当任务通知可以写时,通过什么方式找到这个写阻塞的任务呢?

    任务通知这个对象嵌入到任务控制块中,归属于某个任务,其它任务往这里写,发生阻塞,当可写时,需要解除阻塞时,是找不到这个写任务并将其唤醒的。

    以上就是按照freertos任务通知数据结构基础来演进分析。

    其实解决了写阻塞的唤醒,就可以解决写阻塞这个问题了。
    也很简单,在任务通知这个数据结构中再添加一个写阻塞链表即可。有兴趣的同学可以自己改写下内核源码实现。

    13.8 任务通知类型

    13.9 任务通知状态

    taskNOT_WAITING_NOTIFICATION:任务没有在等待通知。

    taskWAITING_NOTIFICATION:任务在等待通知。

    taskNOTIFICATION_RECEIVED:任务接收到通知。

    13.10 发送任务通知基函数

    在分析任务通知做信号量前先分析一个任务通知基函数xTaskGenericNotify(),任务消息、任务邮箱、任务事件这些发送端都是通过封装该API实现的。

    xTaskGenericNotify()

    • xTaskToNotify:任务句柄。即是任务通知的对象。

    • uxIndexToNotify

      • 通知将发送到的目标任务通知值数组中的索引。
      • uxIndexToNotify必须小于configTASK_NOTIFICATION_ARRAY_ENTRIES
    • ulValue:用于更新目标任务的通知值。

    • pulPreviousNotificationValue:任务原本的通知值返回。(回传)

    13.11 任务事件等待通知基函数

    需要明白任务通知并不是独立的IPC通信组件,而是基于任务控制块的通信组件,所以只能运行该任务时调用等待发给该任务的通知,所以上下文只能是线程形,没有中断形。这样,实现的API只需要实现任务版即可。

    接收任务通知的基函数是xTaskNotifyWaitIndexed():

    • uxIndexToWait:等待的通知索引。

    • ulBitsToClearOnEntry:没有接到通知时才生效的参数。标记接收通知前清空通知。

      • 通知值=通知值& ~(ulBitsToClearOnEntry)
    • ulBitsToClearOnExit:收到通知后需要清除的通知的值。

      • 通知值=通知值& ~(ulBitsToClearOnExit)
    • pulNotificationValue:回传退出清除通知前的通知的容器。

    • xTicksToWait:等待阻塞超时时间。

    13.12 任务全功能通知函数

    xTaskNotify()xTaskNotifyIndexed(),对比内部通用函数,只是没有回传功能。

    中断版:xTaskNotifyFromISR()xTaskNotifyIndexedFromISR(),对比内部通用函数中断版,只是没有回传功能。

    13.13 任务通知全功能等待通知函数

    xTaskNotifyWait()xTaskNotifyWaitIndexed()

    13.14 任务通知之信号量

    通过学习了上述两个内部通用任务通知API后,我们可以通过封装这两个API来实现对应IPC通信概念的专用API。

    13.14.1 释放信号量

    任务通知之释放信号量:xTaskNotifyGive()xTaskGenericNotify()

    中断版:(内部源码可以自己分析,参考上述通用API中的信号量逻辑,只是没有阻塞机制)

    13.14.2 获取信号量

    ulTaskNotifyTake()ulTaskNotifyTakeIndexed()

    如果参数xClearCountOnExit设置为pdTRUE则用于任务通知的二值信号量。

    13.15 任务通知之消息邮箱

    xTaskNotifyAndQuery()xTaskNotifyAndQueryIndexed()

    xTaskNotifyAndQueryIndexed()执行与xTaskNotifyIndexed()相同的操作,另外它还在附加的pulPreviousNotifyValue参数中返回目标任务的之前的通知值(函数被调用时的通知值,而不是函数返回时的通知值)。(对应的xTaskNotifyAndQuery()xTaskNotify()差别也一样)。

    中断版:

    13.16 任务通知之事件组标志

    并没有封装出专门的事件标志API,但是可以使用全能版的API实现。用户也可以自己封装。

    13.17 清除任务通知状态

    如果一个通知被发送到通知数组中的一个索引,那么该索引的通知被称为pending,直到任务读取它的通知值或通过调用xTaskNotifyStateClear()显式地清除通知状态为not pending

    xTaskNotifyStateClear()xTaskNotifyStateClearIndexed()

    内部实现:xTaskGenericNotifyStateClear()

    13.18 清除任务通知值

    ulTaskNotifyValueClear()ulTaskNotifyValueClearIndexed():

    清除任务通知值,返回的是清除前的任务通知值。

    注意:是按bit清除。

    内部通用函数:ulTaskGenericNotifyValueClear()


    __EOF__

  • 本文作者: 李柱明
  • 本文链接: https://www.cnblogs.com/lizhuming/p/16557005.html
  • 关于博主: 嵌入式从业者。RTOS、Linux ...
  • 版权声明: 版权归博主所有
  • 声援博主: 学习笔记分享
  • 相关阅读:
    多卡聚合路由器在近海通讯中的应用
    LLM_入门指南(零基础搭建大模型)
    【ARMv7-A】——ATPCS(ARM-Thumb 过程调用标准)
    2023年高教杯数学建模2023B题解析(仅从代码角度出发)
    PostgreSQL的学习心得和知识总结(一百四十五)|深入理解PostgreSQL数据库之ShowTransactionState的使用及父子事务有限状态机
    【PostgreSQL】日期操作
    obsidian配合hugo的使用,让markdown本地编辑软件与在线化无缝衔接
    SpringBoot 快速开发
    黑马点评-发布探店笔记
    八、T100应付管理系统之员工费用报销管理篇
  • 原文地址:https://www.cnblogs.com/lizhuming/p/16557005.html