2.7 互斥机制(mutex)
2.7.1 概念
信号量的处理核心原则在于,如果任务能获取信号量则返回成功,如果获取不到,则任务按照优先级挂载到sem的pend列表,等待信号量释放,或者等待超时时间到自动唤醒,或者立马及时上报获取错误。
与信号量相比,mutex的区别主要在于:
(1)mutex的获取完全互斥,即同一时刻,mutex只能被一个任务获取。sem按照起始count的配置,存在多个任务获取同一信号量的情况,直到count减为0,则后续任务无法再获取信号量,当然若sem的count初值设置为1,同样有互斥的效果。
(2)信号量的释放可以由其他任务上下文进行释放,并且可以选择释放单个任务或所有阻塞的任务;mutex的释放必须由占有该mutex的任务进行,其他任务进行释放,会直接返回失败。
(3)为了解决优先级反转问题,高优先级的任务获取mutex时,如果该mutex被某低优先级的任务占用,会动态提升该低优先级任务的优先级至等于高的优先级,并且将该优先级值依次传递给该低优先级任务依赖的互斥量关联的任务,以此递归下去。当某任务释放mutex时,会查找该任务的基础优先级,以及获取到的互斥量所阻塞的最高优先级的任务,取其优先级最小值,来重新设定此任务的优先级。总的原则就是,高优先级任务被mutex阻塞时,会将占用该mutex的低优先级任务的优先级临时提高;mutex被释放时,相应任务的优先级需要恢复。
2.7.2 互斥量的创建与删除
1.mutex创建
函数原型:kstat_t krhino_mutex_create(kmutex_t *mutex, const name_t *name)mutex的创建默认都是静态内存方式。管理结构体kmutex_t最主要的数据成员包括:
(1)blk_obj:等待该mutex的任务队列,此处和sem阻塞队列管理类似;
(2)mutex_task:当前占用该mutex的任务;
(3)mutex_list:对挂接在占用该mutex的任务的信号量链表进行管理,任务会获取到多个mutex。
mutex创建的主要目的在于初始化kmutex_t管理结构。
2.mutex删除
函数原型:kstat_t krhino_mutex_del(kmutex_t *mutex)
删除的主要目的包括:
(1)从该互斥量的阻塞队列blk_obj里面,释放被阻塞的任务。此释放机制的处理过程和sem一样,此处不再赘述。
(2)从获取到该互斥量的任务的mutex_list列表删除此mutex。
(3)重置获取到该互斥量的任务的优先级。重置优先级参考为:该任务创建时的基础优先级b_prio,该任务获取到的剩余mutex列表所阻塞的最高优先级任务的优先级,取两者中的较高优先级。
上述步骤(2)(3)的处理在函数mutex_release中实现。
2.7.3 互斥量的获取与释放
1.mutex获取
函数原型:kstat_t krhino_mutex_lock(kmutex_t *mutex, tick_t ticks)
某个mutex同时只能由一个任务占用,同任务多次获取会造成mutex嵌套。
获取互斥量的步骤包括:
(1)检测该mutex是否已经被当前任务获取,如果是,则表明进入mutex获取嵌套,增加嵌套次数mutex->owner_nested,返回RHINO_MUTEX_OWNER_NESTED。
(2)如果该互斥量未有任务占用,即mutex->mutex_task为NULL,则当前任务顺利获取mutex,并将其挂载到任务的互斥量链表mutex_list进行管理,并返回成功。
(3)如果当前互斥量已经被其他任务占用,且其优先级比当前active任务优先级低,则动态提升占用该互斥量的任务优先级等于当前active任务的优先级;并且递归修改其他关联任务的优先级。
(4)将当前任务放入mutex的阻塞队列blk_list进行管理,并且如果设置了超时,则加入tick队列g_tick_head等待超时处理。
(5)调用core_sched进行任务切换调度。
mutex获取接口通过ticks来设置非等待、无限等待以及等待延迟几种模式;也可以通过调用krhino_mutex_del删除此mutex,并释放被阻塞的任务;还可以通过krhino_task_wait_abort来强制唤醒被mutex阻塞的任务。这几点特性和sem一样,但本质的区别在于:一个是互斥,另一个是mutex依赖的任务优先级的动态调整。
2.mutex释放
函数原型:kstat_t krhino_mutex_unlock(kmutex_t *mutex)
mutex的释放只能由获取到该mutex的任务自己进行释放。其处理步骤包括:
(1)检测当前调用krhino_mutex_unlock是否为获取到该mutex的任务,如果不是,返回失败。
(2)检测mutex的占用嵌套次数mutex->owner_nested,如果不为0,则计数减1,并直接返回,表明未释放成功。因此krhino_mutex_unlock调用次数需要和krhino_mutex_lock一致,才能最终释放成功。
(3)将此mutex从当前任务获取的mutex_list中删除,并且重新设置任务优先级。设置的优先级参考该任务原始创建的优先级task->b_prio,以及剩余mutex_list列表中阻塞所有任务的最高优先级。
(4)从该mutex中取出优先级最高的一个阻塞任务并唤醒,并调用core_sched进行任务切换。
mutex被释放后,原先占用的任务优先级被重新动态调整,等待此mutex最高优先级的任务被调度。
2.7.4 互斥量的任务优先级变迁图
任务优先级的动态调整主要出现在以下两个阶段:
(1)调用krhino_mutex_lock时,如果此mutex被另一低优先级任务占用,则此时需要提升低优先级任务的优先级至当前任务的优先级;并且此低优先级任务依赖的mutex所占用的另一任务的优先级也需要提升,并按此原则递归处理,如图2-9所示。
图2-9 任务A获取mutex优先级动态调整示意图
(2)调用krhino_mutex_unlock时,需要恢复此任务的优先级,取值为任务创建的原始优先级和任务获取mutex列表所阻塞的所有任务优先级的最小值;并且递归修改拥有当前任务依赖的mutex的任务优先级,如图2-10所示。
图2-10 任务A释放mutex优先级动态调整示意图
在任务释放mutex时,恢复的优先级值表述为min[b_prio, cur_blockPri], b_prio表示该任务自身的基础优先级,cur_blockPri表示该任务占用的所有互斥量链表mutex_list(除去当前正在释放的mutex)阻塞的所有任务的最高优先级,即取决于该任务占有的mutex_list阻塞的最高优先级的任务。此处注意,优先级的值越小,相应的优先级越高。
mutex的原则在于,如果当前任务依赖的mutex被其他低优先级任务占用,则动态提升低优先级的任务的优先级,在释放mutex时再进行对应的还原操作,避免了优先级的反转问题。mutex一般用在需要完全对一个共享资源进行互斥访问的情况下,即当前时刻只有一个任务能占用此mutex。