参考:
- 【野火】物联网操作系统 LiteOS 开发实战指南
- Huawei LiteOS | 中文网
5. 互斥锁
5.1 基本概念
-
互斥锁
又叫互斥信号量,或者叫互斥量
,是一种特殊的二值信号量 -
支持
互斥所有权
- 常用于实现对**临界资源的独占式处理,**任意时刻,互斥锁的状态只有两种,
开锁
或者闭锁
,互斥锁被持有,则为闭锁状态,其他任务无法对该互斥锁进行开锁或持有,当任务释放互斥锁,该互斥锁处于开锁状态
- 常用于实现对**临界资源的独占式处理,**任意时刻,互斥锁的状态只有两种,
-
递归访问
- 持有该互斥锁的任务能够再次获得这个所而不被挂起
-
降低优先级翻转危害
-
利用优先级继承算法实现
-
优先级翻转:高优先级无法运行而低优先级任务可以运行的现象
-
5.2 优先级继承机制
5.2.1 优先级继承算法
- 优先级继承算法是指:暂时提高某个占有资源的低优先级任务的优先级,使之与所有等待该资源的任务中优先级最高那个任务的优先级相等,而当这个低优先级任务执行完毕
释放
该资源时,优先级重新回到初始设定值。因此,继承优先级的任务避免了系统资源被任何中间优先级的任务抢占。- 注:LiteOs的优先级继承机制不能解决优先级反转,智能将这种情况的影响降低到最小
5.2.1 优先级继承算法图解
- L 任务正在使用某临界资源, L 任务正在使用某临界资源, H 任务被唤醒,执行 H 任务。但 L 任务并未执行完毕,此时临界资源还未释放
- 某一时刻 H 任务也要对该资源进行访问,由于保护机制, H 任务进入阻塞态。此时发生优先级继承,系统将 L 任务的优先级暂时提升到与 H 任务优先级相同, L 任务继续执行
- 在某一时刻 M 任务被唤醒,由于此时 M 任务的优先级暂时低于 L 任务,所以 M 任务仅在就绪态,而无法获得 CPU 使用权
- L 任务运行完毕, H 任务获得对资源的访问权, H 任务从阻塞态变成运行态, 此时 L 任务的优先级会变回原来的优先级
- 当 H 任务运行完毕, M 任务得到 CPU 使用权,开始执行
- 系统正常运行,按照设定好的优先级运行
5.3 互斥锁的使用场景
- 互斥锁适合于
- 可能会引起优先级翻转的情况
- 任务可能会多次获取互斥锁的情况下,这样可以避免同一任务多次递归持有而造成死锁的问题
- 资源有限,例如硬件资源有限,比如共用串口资源
- 互斥锁不能再中断服务函数中使用,因为其特有的优先级继承机制只在任务其作用,在中断的上下文环境毫无意义
5.4 互斥锁的运作机制
- 多任务环境下多个任务访问同一临界资源
5.4.1 运作机制详解
- 互斥锁处理不同任务对临界资源的访问,只有获得互斥锁才能进行资源的访问,获得互斥锁后,互斥锁变为闭锁状态,其他想要访问该资源的任务只能根据用户自定义的等待时间进行等待,直至互斥锁被持有的任务释放之后再根据优先级进行获取互斥锁
- 因为互斥锁具有优先级继承机制,一般使用互斥锁对资源进行保护,如果资源被占用的时候,无论是么优先级的任务想要使用该资源都会被阻塞,如上图
1
,2
步 - 假如正在使用该资源的
任务1
比阻塞中的任务2
的优先级还低,那么任务1
将被系统临时提升到与高优先级任务2
的优先级(目前所有想获取互斥锁任务中优先级最高的)(任务1
的优先级从L
变成H
) - 当
任务1
使用完资源后,释放互斥锁,此时任务1的优先级会从H
变回原来的L
任务2
此时可以获取互斥锁,然后进行资源的访问,当任务2
访问资源的时候,该互斥锁的状态又为闭锁状态,其他任务无法获取互斥锁
5.5 互斥锁的使用详解
5.5.1 互斥锁控制块
typedef struct
{
UINT8 ucMuxStat; /* 互斥锁状态 OS_MUX_UNUSED,OS_MUX_USED */
UINT16 usMuxCount; /* 互斥锁锁定次数,usMuxCount=0时,表示互斥锁处于开锁状态 */
UINT32 ucMuxID; /* 互斥锁ID */
LOS_DL_LIST stMuxList; /* 互斥锁阻塞列表 */
LOS_TASK_CB *pstOwner; /* 互斥锁持有者 */
UINT16 usPriority; /* 记录持有互斥锁任务的优先级,用于处理优先级反转问题 */
} MUX_CB_S;
5.5.2 互斥锁功能函数
Huawei LiteOS 系统中的互斥锁模块为用户提供下面几种功能。
功能分类 | 接口名 | 描述 |
---|---|---|
互斥锁的创建和删除 | LOS_MuxCreate | 创建互斥锁 |
LOS_MuxDelete | 删除指定的互斥锁 | |
互斥锁的申请和释放 | LOS_MuxPend | 申请指定的互斥锁 |
LOS_MuxPost | 释放指定的互斥锁 |
5.5.3 互斥锁使用流程
注意:需要包含头文件:
los_mux.h
-
创建互斥锁
LOS_MuxCreate
UINT32 LOS_MuxCreate (UINT32 *puwMuxHandle)/* 互斥锁ID */
-
申请互斥锁
LOS_MuxPend
UINT32 LOS_MuxPend(UINT32 uwMuxHandle,/* 互斥锁ID */ UINT32 uwTimeout) /* 超时时间 */
- 申请模式有三种:
- 无阻塞模式:任务需要申请互斥锁,若该互斥锁当前没有任务持有,或者该互斥锁的任务和申请该互斥锁的任务为同一任务,则申请成功
- 永久阻塞模式:
- 任务需要申请互斥锁,若该互斥锁当前没有被占用则申请成功
- 否则该任务进入阻塞态,系统切换到就绪任务中优先级最高者继续执行
- 任务进入阻塞态,直到有其他任务释放该互斥锁,阻塞任务才会重新得以执行
- 定时阻塞模式
- 任务需要申请互斥锁,若该互斥锁当前没有被占用则申请成功
- 否则该任务进入阻塞态,系统切换到就绪任务中优先级最高者继续执行
- 任务进入阻塞态后,指定时间超时前有其他任务释放该互斥锁,或者用户指定时间超时后,阻塞任务才会重新得以执行
- 如果互斥锁获取成功,那么要在使用完毕需要理解释放,否则很容易造成其他任务无法获取互斥量
- 申请模式有三种:
-
释放互斥锁
LOS_MuxPost
UINT32 LOS_MuxPost(UINT32 uwMuxHandle)/* 互斥锁ID */
- 如果有任务阻塞于指定互斥锁,则唤醒最早被阻塞的任务,该任务进入就绪态,并进行任务调度
- 如果没有任务阻塞于指定互斥锁,则互斥锁释放成功
-
删除互斥锁
LOS_MuxDelete
UINT32 LOS_MuxDelete (UINT32 uwMuxHandle)/* 互斥锁ID */
5.5.3 互斥锁使用注意事项
-
两个任务不能对同一把互斥锁加锁。如果某任务对已被持有的互斥锁加锁,则该任务会被阻塞,直到持有该锁的任务对任务锁解锁,才能执行这把互斥锁的加锁操作
-
互斥锁不能在中断服务程序中使用
-
为保证操作系统的实时性,尽量避免任务的长时间阻塞,因此在获得互斥锁之后,应该尽快释放互斥锁
-
持有互斥锁的过程中,不得再调用
LOS_TaskPriSet
等函数,更改持有互斥锁任务的优先级 -
一个互斥量可以被已经获取这个互斥量的任务重复获取, 而不会形成死锁。 这个递归调用功能是互斥量控制块成员
usMuxCount
实现的, 这个变量用于记录任务递归调用的次数,每次获取
递归互斥量后,这个变量加 1
,在释放
递归互斥量后,这个变量减 1
。只有这个变量减到0
,即释放和获取的次数相等时,互斥量才能变成有效状态, 其他任务才能获取该信号量