互斥量
概述
互斥量的获取是完全互斥的,即同一时刻,互斥量只能被一个任务获取。而信号量按照起始的计数值的配置,可以存在多个任务获取同一信号量的情况,直到计数值减为0,则后续任务无法再获取信号量,当信号量的计数初值设置为1,同样有互斥的效果。但信号量无法避免优先级反转问题。
优先级反转
优先级反转是一种不希望发生的任务调度状态,该状态下,一个高优先级任务间接的被一个低优先级任务所抢占,使得两个任务的相对优先级反转了。当高、中、低三个优先级任务同时访问使用信号量互斥资源时,可能会引起优先级反转。当高优先级的任务需要的信号量被低优先级任务占用时,处理器资源会调度给低优先级任务。此时如果低优先级需要获取的另一个信号量被中优先级的pend任务所占用,那么低优先级的任务则需要等待中优先级的任务事件到来,并释放信号量,则就出现了高、中优先级的任务并不是等待一个信号量,但是中优先级任务先运行的现象。如下图:
任务H具有高优先级,任务M具有中等优先级,任务L具有低优先级,在t0时刻任务H还未运行,任务M因等待信号量2而阻塞,低优先级任务L得到调度占用信号量1;t1时刻,任务H运行并抢占任务L,任务L占用信号量还未释放;t2时刻,任务H阻塞于信号量1,任务L得以继续运行;t3时刻,任务L等待信号量2而阻塞,t4时刻,信号量2被释放,任务M得到运行,此时任务H虽然具有高优先级,但需先等待任务M释放信号量2让任务L运行,并且任务L释放信号量1后才能运行,这种情况即出现了优先级反转。
优先级继承
互斥量可以解决优先级反转问题,高优先级的任务获取互斥量时,如果该互斥量被某低优先级的任务占用, 会动态提升该低优先级任务的优先级等于高优先级,并且将该优先级值依次传递给该低优先级任务依赖的互斥量关联的任务,以此递归下去。当某任务释放互斥量时,会查找该任务的基础优先级,以及获取到的互斥量所阻塞的最高优先级的任务的优先级,取两者中高的优先级来重新设定此任务的优先级。总的原则就是,高优先级任务被互斥量阻塞时,会将占用该互斥量的低优先级任务临时提高;互斥量释放时,相应任务的优先级需要恢复。如下图:
任务H具有高优先级,任务M具有中等优先级,任务L具有低优先级,在t0时刻任务H还未运行,任务M获取互斥量2而阻塞,低优先级任务L得到调度获得互斥量1;t1时刻taskH抢占taskL,但因无法获得互斥量1而阻塞,此时taskL的优先级被提升至与taskH一样高,并继续运行;t2时刻taskL因无法获得互斥量2而阻塞,t3时刻互斥量2被释放,taskL因比taskM优先级高获得互斥量2得到运行;在t4时刻,taskL释放互斥量1,并将优先级恢复到之前状态,taskH因获得互斥量1得到运行,该机制消除了优先级反转的发生。
超时时间
互斥量获取可设置超时时间,如果任务在超时时间到期后仍未获得互斥量,则任务解除阻塞进入就绪状态。
互斥量功能函数
函数名 | 描述 |
aos_mutex_create() | 互斥量创建函数(推荐) |
aos_mutex_new() | 互斥量创建函数(兼容3.1) |
aos_mutex_free() | 互斥量删除函数 |
aos_mutex_lock() | 互斥量获取函数 |
aos_mutex_unlock() | 互斥量释放函数 |
aos_mutex_is_valid() | 判断互斥量具柄是否合法函数 |
常用配置
互斥量优先级继承:默认关闭,如需修改,在YAML中修改RHINO_CONFIG_MUTEX_INHERIT配置
def_config:
RHINO_CONFIG_MUTEX_INHERIT: 1
API说明
使用示例
示例代码参考example/mutex_example.c,该示例使用互斥量实现共享资源的互斥访问,具体场景为创建任务A和任务B,以及一互斥量。任务A和任务B使用互斥量同时访问共享数据区,访问共享数据区时使用互斥量做保护。
示例说明如下:
t0时刻,任务T调用aos_mutex_create()创建一互斥量。任务T然后调用aos_task_create()创建任务A和任务B。任务A得到运行,并获取互斥量对数据区record_status进行读写操作。
t1时刻,任务A因时间片耗尽,让出CPU,任务B得到运行。
t2时刻,任务B因无法获得互斥量,进入阻塞状态,任务A得到运行。
t3时刻,任务A对数据区record_status的操作完成,释放互斥量,任务B获得互斥量开始对数据区record_status进行读写操作。
该示例可配置到helloworld_demo中运行,相关代码的下载、编译和固件烧录均依赖AliOS Things配套的开发工具 ,所以首先需要参考《AliOS Things集成开发环境使用说明之搭建开发环境》,下载安装 。 待开发环境搭建完成后,可以按照以下步骤进行示例的测试。
步骤1 创建或打开工程
打开已有工程
如果用于测试的案例工程已存在,可参考《AliOS Things集成开发环境使用说明之打开工程》打开已有工程。
创建新的工程
组件的示例代码可以通过编译链接到AliOS Things的任意案例(solution)来运行,这里选择helloworld_demo案例。helloworld_demo案例相关的源代码下载可参考《AliOS Things集成开发环境使用说明之创建工程》。
步骤2 添加组件
案例下载完成后,需要在helloworld_demo组件的package.yaml中添加对组件的依赖:
depends:
- osal_aos: dev_aos # helloworld_demo中引入osal_aos组件
步骤3 下载组件
在已安装了的开发环境工具栏中,选择Terminal -> New Terminal启动终端,并且默认工作路径为当前工程的workspace,此时在终端命令行中输入:
aos install osal_aos
上述命令执行成功后,组件源码则被下载到了./components/osal_aos路径中。
步骤4 添加示例
osal_aos组件的package.yaml中添加example示例代码:
depends:
- rhino: dev_aos
- cli: dev_aos # 添加cli依赖
source_file:
- "*.c"
- "example/mutex_example.c" # 添加 mutex_example.c
步骤5 编译固件
在示例代码已经添加至组件的配置文件,并且helloworld_demo已添加了对该组件的依赖后,就可以编译helloworld_demo案例来生成固件了,具体编译方法可参考《AliOS Things集成开发环境使用说明之编译固件》。
步骤6 烧录固件
helloworld_demo案例的固件生成后,可参考《AliOS Things集成开发环境使用说明之烧录固件》来烧录固件。
步骤7 打开串口
固件烧录完成后,可以通过串口查看示例的运行结果,打开串口的具体方法可参考《AliOS Things集成开发环境使用说明之查看日志》。
当串口终端打开成功后,可在串口中输入help来查看已添加的测试命令。
步骤8 测试示例
CLI命令行输入:
mutex_example
关键日志:
[aos_mutex_example][0x34035d80]field1=1709388289, field2=1709388289, field3=1709388289, field4=1709388289
[aos_mutex_example][0x340358b0]field1=1386345753, field2=1386345753, field3=1386345753, field4=1386345753
[aos_mutex_example][0x34035d80]field1=2042805823, field2=2042805823, field3=2042805823, field4=2042805823
[aos_mutex_example][0x340358b0]field1=1026149465, field2=1026149465, field3=1026149465, field4=1026149465
[aos_mutex_example][0x34035d80]field1=839374236, field2=839374236, field3=839374236, field4=839374236
[aos_mutex_example][0x340358b0]field1=532559370, field2=532559370, field3=532559370, field4=532559370
[aos_mutex_example][0x34035d80]field1=1406670318, field2=1406670318, field3=1406670318, field4=1406670318
[aos_mutex_example][0x340358b0]field1=24443955, field2=24443955, field3=24443955, field4=24443955
[aos_mutex_example][0x34035d80]field1=693331083, field2=693331083, field3=693331083, field4=693331083
注意事项
互斥量只能由获取该互斥量的任务的释放,不能由其他任务释放。
互斥量已被当前任务获取,若当前任务再次获取互斥量则返回错误。
FAQ
Q1: 调用aos_mutex_lock()接口无限期的获取互斥量,timeout参数怎么设置? 答:将timeout赋值为AOS_WAIT_FOREVER。