4. 优先级天花板协议:协议原理、与优先级继承的区别、代码示例(VxWorks优先级天花板)、适用场景

好,咱们接着聊优先级反转的解决方案。

上一节我们讲了优先级继承,那是个很实用的机制。但说实话,我在实际项目中用得更顺手的,其实是优先级天花板协议。为什么?因为它更简单、更粗暴、也更可靠。

4.1 协议原理:一句话说清楚

优先级天花板协议的核心思想,说白了就是一句话:

谁拿了锁,谁就立刻把优先级提到“天花板”那么高。

这个“天花板”,就是所有可能竞争这把锁的任务中,最高的那个优先级。

举个例子。假设有三个任务:T1(优先级10)、T2(优先级20)、T3(优先级30)。它们都可能访问同一个共享资源。那么这把锁的“天花板优先级”就是30。

只要T1拿到了锁,它的优先级立刻被提升到30。这时候T2就算想抢跑,也抢不过T1。T1能一口气把资源用完、释放锁,然后降回原来的优先级。

你想想看,这比优先级继承简单多了吧?不需要去查“谁在等我”、“谁阻塞了谁”,直接一刀切——锁的优先级就是天花板。

4.2 与优先级继承的区别

很多初学者容易把这两个搞混。我当年也踩过这个坑。咱们用一张表说清楚:

对比项 优先级继承 优先级天花板
提升时机 只有高优先级任务被阻塞时才提升 拿到锁就立刻提升
提升到什么优先级 阻塞它的那个任务的优先级 锁的“天花板”优先级(固定值)
实现复杂度 较高,需要跟踪阻塞链 较低,只需查表
死锁风险 可能发生死锁(嵌套锁场景) 可避免死锁(如果天花板设置正确)
实时性 相对较好,只提升必要的任务 可能过度提升,造成优先级“膨胀”

嗯,这里要注意一个关键点:优先级继承是被动的,优先级天花板是主动的

优先级继承是“出事了再补救”——高优先级任务被堵住了,才去拉低优先级任务一把。而优先级天花板是“先预防”——不管有没有人堵,拿了锁就提优先级。

我个人习惯,在系统比较简单、锁的竞争不太激烈时,用优先级天花板。因为代码写起来清爽,调试也方便。

4.3 代码示例:VxWorks 中的优先级天花板

VxWorks 对优先级天花板的支持很直接。它通过信号量的属性来设置。

我给大家看一段我实际项目里用过的代码:

/* 定义天花板优先级 */
#define RESOURCE_CEILING_PRIO 50

/* 创建带优先级天花板的信号量 */
SEM_ID semResource;
semResource = semMCreate(SEM_Q_PRIORITY | SEM_INVERSION_SAFE | 
                         SEM_CEILING_PRIO(RESOURCE_CEILING_PRIO));

/* 任务A:低优先级,拿锁干活 */
void taskA(void) {
    while (1) {
        semTake(semResource, WAIT_FOREVER);
        /* 临界区:操作共享资源 */
        printf("TaskA: 拿到锁,当前优先级=%d\n", taskPriorityGet(0));
        taskDelay(10);  /* 模拟干活 */
        semGive(semResource);
        taskDelay(100);
    }
}

/* 任务B:中优先级,不碰锁 */
void taskB(void) {
    while (1) {
        /* 纯计算任务 */
        printf("TaskB: 运行中,优先级=%d\n", taskPriorityGet(0));
        taskDelay(5);
    }
}

/* 任务C:高优先级,也要拿锁 */
void taskC(void) {
    while (1) {
        semTake(semResource, WAIT_FOREVER);
        /* 临界区 */
        printf("TaskC: 拿到锁,当前优先级=%d\n", taskPriorityGet(0));
        taskDelay(5);
        semGive(semResource);
        taskDelay(50);
    }
}

小提示:SEM_INVERSION_SAFE 这个标志就是告诉 VxWorks 启用优先级天花板机制。如果你忘了加这个标志,那信号量就只是普通的互斥信号量,优先级反转该发生还是会发生。

这段代码跑起来后,你会发现一个有意思的现象:TaskA 拿锁期间,它的优先级会跳到50。这时候 TaskB 虽然优先级比 TaskA 原来的优先级高,但比天花板低,所以 TaskB 抢不过 TaskA。TaskA 能安心干完活再释放锁。

4.4 适用场景

优先级天花板不是万能的。我给大家总结一下它的“用武之地”:

  • 锁的竞争任务数量已知且固定:天花板优先级可以静态计算出来,不需要动态调整。
  • 系统对实时性要求高,但实现要简单:比如一些工业控制器、简单的嵌入式系统。
  • 嵌套锁场景较少:如果锁嵌套很深,天花板协议可能导致优先级过度提升,反而影响实时性。
  • 你不想处理复杂的阻塞链追踪:嗯,说白了就是懒人福音。我承认,有时候我就是图省事。

警告:天花板优先级设置过高,会导致“优先级膨胀”。比如一个低优先级任务拿了锁,被提到很高的优先级,它可能会长时间霸占CPU,影响其他不相关的任务。我曾经在一个项目中,把天花板设成了系统最高优先级,结果一个打印日志的低优先级任务,把整个系统的实时性拖垮了。切记:天花板不是越高越好,够用就行。

4.5 知识体系图

下面这张图,是我自己画的一个总结。它把优先级天花板协议的核心逻辑串起来了:

优先级天花板协议核心逻辑 任务A (优先级10) 拿锁 → 优先级升至50 任务B (优先级20) 不碰锁,正常调度 任务C (优先级30) 等锁,被A阻塞 共享资源 天花板优先级 = 50 拿锁 阻塞等待 优先级低于天花板 无法抢占A 核心机制:任务A拿锁后优先级升至天花板(50), 任务B(20)和任务C(30)都无法抢占,直到A释放锁

这张图里,任务A拿了锁之后,优先级直接跳到天花板50。任务B虽然优先级20比A原来的10高,但比天花板低,所以抢不了。任务C优先级30,同样被压制。直到A释放锁,一切恢复正常。

嗯,这就是优先级天花板协议的精髓。简单、粗暴、有效。下一节我们聊聊另一种更优雅的方案——优先级置顶协议。不过那是后话了。


公众号:蓝海资料掘金营,微信deep3321