p45 ~ p46(约 22 分钟)
事件组的本质
队列、信号量、互斥量都有一个共同的局限:一个写入、一个量,只能唤醒一个任务。如果想「一个任务写数据、唤醒多个任务」,就需要广播机制,也就是事件组(Event Group)。
事件组也是一个结构体,里面有几样东西:
一个整数 高八位不用,其余每一位代表一个事件,含义由应用程序定义 bit0 = 1 表示 0 号事件发生了,bit1 = 0 表示对应事件没发生
一个等待链表 挂进去一个个「正在等待某些事件」的任务
每个等待任务自己的结构体里也有一个整数 bit0 = 1 表示它想等 0 号事件,bit3 = 1 表示想等 3 号事件 也就是「我想等哪些事件」
与/或的关系 用高八位里的某些位表示[待确认] 标准 FreeRTOS 是用 xEventGroupWaitBits 的 xWaitForAllBits 参数表达与/或,字幕里关于「高八位编码与/或」的说法前后比较模糊,可能是个教学简化模型。
广播的原理举个例子就清楚了。事件组初值全 0:
任务 A 想等 0 号或 3 号事件任务 B 想等 0 号或 7 号事件两个任务都进等待链表
任务 C 写 bit0、bit1、bit2 → 遍历链表,A、B 都不满足,不唤醒任务 C 写 bit7 = 1 → 遍历链表,A 不满足,B 满足(它在等 7 号事件)→ 唤醒 B任务 C 写 bit0 → A(等 0 号或 3 号)满足,B(等 0 号或 7 号)满足 → 两个都唤醒写事件的时候会遍历整个等待链表、逐个判断条件是否满足、满足就唤醒——这就是广播。
三辆车演示广播、或、与
在第 22 个程序基础上改,做出第 23、24、25 三个程序,分别演示广播、等待「或」、等待「与」。
创建用动态分配函数 xEventGroupCreate(),定义一个全局事件组变量。原先的互斥量代码注释掉。
第 23 个程序(广播):汽车一任务不再 Take 信号量,一直运行,到最右用 xEventGroupSetBits(xEventCar, bit0) 把 bit0 置 1。
xEventGroupWaitBits(xEventCar, bit0, /* 等哪些位 */ xClearOnExit, /* 退出前清除所等到的位 */ xWaitForAllBits, /* 与还是或 */ xTicksToWait); /* 超时 */汽车二、三改成调用它:xClearOnExit = pdTRUE 表示退出前清除所等到的位(下次可以继续等新事件);只等一个位的时候与/或随便写;超时传「一直等待」。二、三原来释放信号量的代码删掉。
现象:任务 1 优先级最低,一跑就让车往右走(二、三都在等事件);任务 1 到终点置位,唤醒二、三,优先级高的任务 3 先跑。所以顺序是一到终点、三第二到、二最后到。把二、三的优先级改成一致,就能看到两车同时运行,这就是广播的效果。
第 24 个程序(「或」):任务 1 到最右置 bit0;任务 2 不等待(它是事件发生者),随机延迟后到最右,自杀之前置 bit1;任务 3 用 xEventGroupWaitBits 等 bit0 | bit1,xWaitForAllBits = pdFALSE(或的关系),于是任意一辆到站都能唤醒第三辆。改任务 2 的延迟可以控制谁先到:延迟改小则二先到、三先跑、一后跑;延迟加大 100 则一先到、三立即被唤醒,三一跑二就跑不了了。
第 25 个程序(「与」):把任务 3 的 xWaitForAllBits 改成 pdTRUE,要 bit0 和 bit1 都发生才退出,也就是一、二两车都到终点之后,第三辆车才启动。
坑
- 事件组适合表达「事件发生了」,不适合搬运数据。
xClearOnExit传pdTRUE才能反复等同一批事件;不清除的话位一直是 1。xWaitForAllBits是pdFALSE表示等任意一个,pdTRUE表示全都要满足。- 被唤醒的任务之间仍然按优先级竞争,想看到「同时运行」得把优先级设成一样。
请输入编辑凭据,只有站点所有者可以修改文章。