多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

HRTOS 互斥锁的优先级继承:从实现原理到内核代码分析

HRTOS 互斥锁的优先级继承:从实现原理到内核代码分析 在实时操作系统中互斥锁不仅需要解决共享资源的互斥访问问题还需要考虑资源竞争对任务调度的影响。当低优先级任务持有互斥锁而高优先级任务需要获取同一把锁时高优先级任务只能等待。如果此时又有中优先级任务不断抢占低优先级任务那么持锁任务可能迟迟无法运行高优先级任务也就无法及时获得资源。这种现象称为优先级反转Priority Inversion。为缓解这一问题HRTOS 在互斥锁实现中引入了优先级继承机制。本文结合mutex_lock.c和mutex_unlock.c分析优先级继承的工作原理、实现流程以及当前实现需要关注的边界情况。一、什么是优先级反转假设系统中有三个任务任务初始优先级职责Task L1低优先级任务持有互斥锁Task M3中优先级任务不需要该锁Task H5高优先级任务需要获取同一把锁这里假设数值越大任务优先级越高。执行过程如下Task L 获得互斥锁开始访问共享资源。Task H 抢占 Task L但发现互斥锁已经被占用因此进入等待状态。Task M 就绪其优先级高于 Task L因此抢占 Task L。Task L 无法及时继续执行也就无法释放互斥锁。Task H 只能继续等待。问题在于Task H 本来具有最高优先级却因为低优先级任务持有资源而被迫等待与此同时与该资源无关的 Task M 还可能延长这段等待时间。这就是优先级反转问题。需要注意优先级反转并不意味着调度器直接把高优先级任务排到了低优先级任务之后而是任务之间的资源依赖关系间接影响了调度结果。二、优先级继承的基本原理优先级继承的核心思路是当高优先级任务因互斥锁而阻塞时临时提高持锁任务的当前优先级使其能够更快获得 CPU 时间并释放资源。在上面的例子中Task L 的基础优先级为 1Task H 的优先级为 5Task H 因等待互斥锁而阻塞Task L 的当前优先级从 1 提升到 5Task L 更容易抢占 Task M继续执行并释放互斥锁Task H 获得锁后继续运行。当互斥锁释放后持锁任务的优先级需要恢复。优先级继承并不是永久修改任务的基础优先级而是根据资源竞争情况临时调整当前优先级。因此内核需要区分两个概念基础优先级base_prio任务原本的优先级。当前优先级cur_prio任务当前参与调度时使用的优先级。HRTOS 的任务控制块包含这两个字段typedef struct { u8 base_prio; /* 静态优先级 */ u8 cur_prio; /* 动态优先级 */ u8 state; u8 wait_type; u8 wait_flag; u8 wait_obj; u16 wait_tick; } OS_TCB;这种设计为优先级继承提供了必要的数据基础内核可以临时调整cur_prio并保留base_prio作为恢复依据。三、HRTOS 如何实现优先级继承1. 检查互斥锁参数在os_mutex_lock()中首先检查互斥锁编号是否合法if (mid 0 || mid OS_RESOURCE_MAX) { return -1; }通过参数检查避免使用超出资源对象范围的互斥锁编号。随后内核获取当前任务 ID 和对应的资源对象并进入临界区。2. 禁止递归获取同一把锁HRTOS 当前的互斥锁不支持递归获取。代码通过检查锁的拥有者来识别当前任务是否已经持有该锁if (m-owner tid) { EA 1; return -1; }如果当前任务已经是锁拥有者再次获取同一把锁就会返回失败。这能够避免任务因为重复获取非递归互斥锁而陷入自我等待。3. 互斥锁空闲时直接获取如果互斥锁没有拥有者当前任务可以直接获得该锁if (m-owner OS_INVALID_ID) { m-owner tid; OS_TASK[tid].base_prio (OS_PROCESS_OK[tid] 14) 1; OS_TASK[tid].cur_prio OS_TASK[tid].base_prio; EA 1; return 1; }这里有两个重要操作。第一将当前任务设置为互斥锁拥有者。第二根据OS_PROCESS_OK中编码的优先级恢复任务的基础优先级并同步到cur_prio。这段逻辑体现了当前实现的优先级管理方式在直接获得空闲互斥锁时任务的当前优先级被重新设为基础优先级。4. 锁已被占用时执行优先级继承这是整个实现最关键的部分OS_TASK[tid].base_prio (OS_PROCESS_OK[tid] 14) 1; OS_TASK[tid].cur_prio OS_TASK[tid].base_prio; if (OS_TASK[tid].cur_prio OS_TASK[m-owner].cur_prio) { OS_TASK[m-owner].cur_prio OS_TASK[tid].cur_prio; OS_PROCESS_OK[m-owner] 0xF1; OS_PROCESS_OK[m-owner] | ((OS_TASK[tid].cur_prio 0x07) 1); }可以将这段代码拆成三个步骤。第一步取得等待任务的基础优先级。内核从OS_PROCESS_OK中提取优先级并将其保存为当前任务的基础优先级。按照当前代码的位操作优先级存储在OS_PROCESS_OK的 bit1bit3 中。第二步比较等待任务与锁拥有者的当前优先级。if (OS_TASK[tid].cur_prio OS_TASK[m-owner].cur_prio)只有等待任务的当前优先级更高时才需要提升锁拥有者的优先级。如果等待任务的优先级低于或等于锁拥有者则不需要通过这次竞争进一步提升锁拥有者的优先级。第三步更新锁拥有者的当前优先级及调度字段。OS_TASK[m-owner].cur_prio OS_TASK[tid].cur_prio;这条语句完成任务控制块中的优先级提升。随后OS_PROCESS_OK[m-owner] 0xF1; OS_PROCESS_OK[m-owner] | ((OS_TASK[tid].cur_prio 0x07) 1);清除OS_PROCESS_OK中原有的 bit1bit3再写入新的优先级编码。这一步很重要如果调度器实际根据OS_PROCESS_OK中的优先级字段进行任务选择仅修改cur_prio就不够还需要同步调度器使用的优先级信息。因此HRTOS 当前的优先级继承涉及两个层面更新OS_TCB中的当前优先级同步OS_PROCESS_OK中用于调度的优先级编码。5. 进入等待状态优先级继承处理完成后当前代码退出临界区再调用return os_wait(WAIT_MUTEX, mid, 0);这表示当前任务等待指定的互斥锁等待类型为WAIT_MUTEX超时参数为 0。这里需要区分两个动作优先级继承提高锁拥有者的当前优先级。任务等待让当前任务进入互斥锁等待流程。前者用于改善持锁任务的调度机会后者用于管理无法立即获取锁的任务。四、释放互斥锁时如何恢复优先级优先级继承不仅需要提升优先级还需要处理资源释放后的优先级恢复。在os_mutex_unlock()中首先验证互斥锁编号并确认当前任务确实是锁拥有者。随后执行OS_TASK[tid].cur_prio OS_TASK[tid].base_prio; OS_PROCESS_OK[tid] 0xF1; OS_PROCESS_OK[tid] | ((OS_TASK[tid].cur_prio 0x07) 1);这段代码将当前任务的优先级恢复为基础优先级并同步更新调度字段。在只有一把相关互斥锁、且没有其他优先级继承需求的简单场景中这符合预期任务不再需要为等待者继承高优先级便可以恢复原有优先级。但这里存在一个需要认真对待的边界条件如果一个任务同时持有多把互斥锁释放其中一把锁并不一定意味着它可以立即恢复到基础优先级。例如Task L 持有互斥锁 A。Task L 又获得互斥锁 B。高优先级任务因等待锁 A导致 Task L 的优先级被提升。Task L 释放锁 B但仍然持有锁 A。在这种情况下Task L 仍然可能需要维持继承优先级。当前代码在每次成功释放互斥锁时都会直接执行cur_prio base_prio。如果释放的锁不是导致优先级继承的唯一原因这种恢复方式就可能过早降低任务优先级。因此这段代码适用于怎样的资源使用约束需要结合 HRTOS 对多锁持有及优先级继承的整体设计进一步确认。五、互斥锁释放时如何选择等待任务完成当前任务的优先级恢复后代码检查互斥锁的等待位图if (m-wait_mask)如果存在等待任务就遍历等待集合选择当前优先级最高的任务for (i 0; i OS_PROCESS_MAX; i) { if (mask ((u16)1 i)) { if (OS_TASK[i].cur_prio best_prio) { best_prio OS_TASK[i].cur_prio; next i; } } }这里采用位图表示等待任务集合再逐个检查对应任务的当前优先级。由于当前代码使用严格大于号比较如果多个等待任务具有相同的最高优先级那么最终选择的是遍历顺序中最先遇到的那个任务也就是 ID 较小的任务。找到等待任务后内核先转移互斥锁所有权m-owner next;随后唤醒该任务wake_task(next, WAIT_SIGNAL);最后返回 1表示释放互斥锁并将所有权转移给等待任务。这种处理方式的一个重要特点是所有权转移发生在唤醒之前。对于被唤醒的任务而言内核已经把互斥锁所有者更新为该任务而不是先把锁设为空闲再等待任务重新竞争。这种直接交接方式能够让锁的释放与等待者唤醒保持一致。不过优先级继承与等待者选择仍然是两个不同的问题前者影响持锁任务的调度优先级后者决定释放锁后哪个等待任务获得所有权。六、当前实现的适用边界从代码可以看出HRTOS 已经具备优先级继承的基本路径检查互斥锁编号与递归获取空闲时直接获取互斥锁竞争发生时比较等待任务与锁拥有者的当前优先级必要时提升锁拥有者优先级在OS_PROCESS_OK中同步调度优先级释放互斥锁时恢复基础优先级从等待位图中选择最高优先级任务并转移锁所有权。但从实时操作系统的完整协议角度还需要区分几个问题。第一多互斥锁场景下的优先级恢复。如果任务持有多把锁释放其中一把锁后是否仍然存在其他优先级继承需求需要由内核明确处理。简单恢复基础优先级无法覆盖所有此类场景。第二优先级继承链的传播。如果任务 L 持有锁 A同时等待任务 M 持有的锁 B那么更高优先级任务等待锁 A 时优先级提升是否需要继续传播到锁 B 的拥有者取决于内核是否实现了传递式优先级继承。当前给出的代码没有展示这类传播逻辑。第三基础优先级的来源。当前实现从OS_PROCESS_OK中提取基础优先级因此需要保证这个字段始终反映正确的基础优先级而不会因为动态优先级更新而丢失原始值。第四优先级提升后的调度一致性。当前代码同步修改cur_prio与OS_PROCESS_OK这是必要的实现细节。还应结合调度器确认在锁拥有者已经就绪、等待或处于其他状态时优先级更新是否能够正确反映到任务选择过程。这些问题不意味着当前实现必然存在实际故障而是说明基础优先级继承路径与完整、可处理复杂资源依赖的优先级继承协议之间仍然存在需要明确的设计边界。总结优先级继承是 RTOS 互斥锁设计中的重要机制。它通过临时提高锁拥有者的当前优先级缓解低优先级任务持锁导致高优先级任务长期等待的问题。HRTOS 当前实现将优先级继承与任务控制块、OS_PROCESS_OK调度字段、互斥锁等待位图以及任务唤醒机制结合起来形成了一条完整的基本处理路径。与此同时互斥锁机制的可靠性不仅取决于能否提升优先级还取决于释放锁后的优先级恢复、多锁持有以及资源依赖链等边界条件。优先级继承真正需要解决的不只是让持锁任务变得更高优先级而是让优先级调整始终与任务的资源依赖关系保持一致。对于 8051 这类资源受限平台如何在有限的内存与代码规模下实现清晰、可验证的互斥锁行为是内核设计中值得深入研究的问题。
返回列表