互斥锁又称互斥互斥锁,是一种特殊的信号量,它和信号量不一样的是,它具备互斥锁全部权、递归访问以及优先级继承
等特性,在操做系统中经常使用于对临界资源的独占式
处理。在任意时刻互斥锁的状态只有两种,开锁或闭锁
,当互斥锁被任务持有时,该互斥锁处于闭锁状态,当该任务释放互斥锁时,该互斥锁处于开锁状态。算法
全部权
特性。递归访问
特性。这个特性与通常的信号量有很大的不一样,在信号量中,因为已经不存在可用的信号量,任务递归获取信号量时会发生挂起任务最终造成死锁
。优先级继承
机制,它能够将低
优先级任务的优先级临时提高
到与获取互斥锁的高
优先级任务的优先级相同
,尽量下降
优先级翻转的危害。在实际应用中,若是想要实现同步功能,可使用信号量,虽然互斥锁也能够用于任务与任务间的同步,但互斥锁更多的是用于临界资源的互斥访问。数据结构
使用互斥锁对临界资源保护时,任务必须先获取互斥锁以得到访问该资源的全部权,当任务使用资源后,必须释放互斥锁以便其余任务能够访问该资源(而使用信号量保护临界资源时则可能发生优先级翻转,且危害
是不可控的)。函数
简单来讲就是高优先级任务在等待低优先级任务执行完毕,这已经违背了操做系统的设计初衷(抢占式调度)。post
为何会发生优先级翻转?ui
当系统中某个临界资源受到一个互斥锁保护时,任务访问该资源时须要得到互斥锁,若是这个资源正在被一个低优先级
任务使用,此时互斥锁处于闭锁状态;若是此时一个高优先级
任务想要访问该资源,那么高优先级任务会由于获取不到互斥锁而进入阻塞态,此时就造成高优先级任务在等待低优先级任务运行(等待它释放互斥锁)。spa
优先级翻转是会产生危害
的,在一开始设计系统的时候,就已经指定任务的优先级,越重要的任务优先级越高,可是发生优先级翻转后,高优先级任务在等待低优先级任务,这就有可能让高优先级任务得不到有效的处理,严重时可能致使系统崩溃。操作系统
优先级翻转的危害是不可控的,由于低优先级任务极可能会被系统中其余中间优先级
任务(低优先级与高优先级任务之间的优先级任务)抢占,这就有可能致使高优先级任务将等待全部中间优先级任务运行完毕的状况,这种状况对高优先级任务来讲是不可接受的,也是违背了操做系统设计的原则。设计
在TencentOS tiny
中为了下降
优先级翻转产生的危害使用了优先级继承算法
:暂时提升占有某种临界资源的低优先级任务的优先级,使之与在全部等待该资源的任务中,优先级最高的任务优先级相等,而当这个低优先级任务执行完毕释放该资源时,优先级恢复初始设定值(此处能够看做是低优先级任务临时继承了高优先级任务的优先级)。所以,继承优先级的任务避免了系统资源被任何中间优先级任务抢占。互斥锁的优先级继承机制,它确保高优先级任务进入阻塞状态的时间尽量短,以及将已经出现的“优先级翻转”危害下降到最小
,但不能消除
优先级翻转带来的危害。指针
值得一提的是TencentOS tiny
在持有互斥锁时还容许调用修改任务优先级的API接口。code
TencentOS tiny
经过互斥锁控制块操做互斥锁,其数据类型为k_mutex_t
,互斥锁控制块由多个元素组成。
pend_obj
有点相似于面向对象的继承,继承一些属性,里面有描述内核资源的类型(如互斥锁、队列、互斥量等,同时还有一个等待列表list
)。pend_nesting
其实是一个uint8_t
类型的变量,记录互斥锁被获取的次数,若是pend_nesting
为0则表示开锁状态,不为0则表示闭锁状态,且它的值记录了互斥量被获取的次数(拥有递归访问特性)*owner
是任务控制块指针,记录当前互斥锁被哪一个任务持有。owner_orig_prio
变量记录了持有互斥锁任务的优先级,由于有可能发生优先级继承机制而临时改变任务的优先级。(拥有优先级继承机制)。owner_list
是一个列表节点,当互斥锁被任务获取时,该节点会被添加到任务控制块的task->mutex_own_list
列表中,表示任务本身获取到的互斥锁有哪些。当互斥锁被彻底
释放时(pend_nesting等于0
),该节点将从任务控制块的task->mutex_own_list
列表中移除。prio_pending
变量比较有意思:在通常的操做系统中,任务在持有互斥锁时不容许修改优先级,但在TencentOS tiny
中是容许的,就是由于这个变量,当一个任务处于互斥锁优先级反转的时候,我假设他由于优先级反转优先级获得了提高,此时有用户企图改变这个任务的优先级,但这个更改后的优先级会使此任务违背其优先级必须比全部等待他所持有的互斥锁的任务还高的原则时,此时须要将用户的此次优先级更改请求记录到prio_pending
里,待其释放其所持有的互斥锁后再更改,至关于将任务优先级的更改延后了。举个例子:比如一个任务优先级是10,且持有一把锁,此时一个优先级为6的任务尝试获取这把锁,那么此任务优先级会被提高为6,若是此时用户试图更改他的优先级为7,那么不能马上响应此次请求,必需要等这把锁放掉的时候才能作真正的优先级修改
typedef struct k_mutex_st { pend_obj_t pend_obj; k_nesting_t pend_nesting; k_task_t *owner; k_prio_t owner_orig_prio; k_list_t owner_list; } k_mutex_t;
typedef struct k_task_st { #if TOS_CFG_MUTEX_EN > 0u k_list_t mutex_own_list; /**< 任务拥有的互斥量 */ k_prio_t prio_pending; /*< 在持有互斥锁时修改任务优先级将被记录到这个变量中,在释放持有的互斥锁时再更改 */ #endif } k_task_t;
在tos_config.h
中,使能互斥锁的宏定义是TOS_CFG_MUTEX_EN
#define TOS_CFG_MUTEX_EN 1u
系统中每一个互斥锁都有对应的互斥锁控制块,互斥锁控制块中包含了互斥锁的全部信息,好比它的等待列表、它的资源类型,以及它的互斥锁值,那么能够想象一下,建立互斥锁的本质是否是就是对互斥锁控制块进行初始化呢?很显然就是这样子的。由于在后续对互斥锁的操做都是经过互斥锁控制块来操做的,若是控制块没有信息,那怎么能操做嘛~
建立互斥锁函数是tos_mutex_create()
,传入一个互斥锁控制块的指针*mutex
便可。
互斥锁的建立实际上就是调用pend_object_init()
函数将互斥锁控制块中的mutex->pend_obj
成员变量进行初始化,它的资源类型被标识为PEND_TYPE_MUTEX
。而后将mutex->pend_nesting
成员变量设置为0
,表示互斥锁处于开锁状态;将mutex->owner
成员变量设置为K_NULL
,表示此事并没有任务持有互斥锁;将mutex->owner_orig_prio
成员变量设置为默认值K_TASK_PRIO_INVALID
,毕竟此事没有任务持有互斥锁,也无需记录持有互斥锁任务的优先级。最终调用tos_list_init()
函数将互斥锁的持有互斥锁任务的列表节点owner_list
。
__API__ k_err_t tos_mutex_create(k_mutex_t *mutex) { TOS_PTR_SANITY_CHECK(mutex); pend_object_init(&mutex->pend_obj, PEND_TYPE_MUTEX); mutex->pend_nesting = (k_nesting_t)0u; mutex->owner = K_NULL; mutex->owner_orig_prio = K_TASK_PRIO_INVALID; tos_list_init(&mutex->owner_list); return K_ERR_NONE; }
互斥锁销毁函数是根据互斥锁控制块直接销毁的,销毁以后互斥锁的全部信息都会被清除,并且不能再次使用这个互斥锁,当互斥锁被销毁时,其等待列表中存在任务,系统有必要将这些等待这些任务唤醒,并告知任务互斥锁已经被销毁了PEND_STATE_DESTROY
。而后产生一次任务调度以切换到最高优先级任务执行。
TencentOS tiny
对互斥锁销毁的处理流程以下:
pend_is_nopending()
函数判断一下是否有任务在等待互斥锁pend_wakeup_all()
函数将这些任务唤醒,而且告知等待任务互斥锁已经被销毁了(即设置任务控制块中的等待状态成员变量pend_state
为PEND_STATE_DESTROY
)。pend_object_deinit()
函数将互斥锁控制块中的内容清除,最主要的是将控制块中的资源类型设置为PEND_TYPE_NONE
,这样子就没法使用这个互斥锁了。mutex->pend_nesting
成员变量恢复为默认值0
。mutex_old_owner_release()
函数将互斥锁释放。knl_sched()
注意:若是互斥锁控制块的RAM是由编译器静态分配
的,因此即便是销毁了互斥锁,这个内存也是没办法释放的。固然你也可使用动态内存为互斥锁控制块分配内存,只不过在销毁后要将这个内存释放掉,避免内存泄漏。
__API__ k_err_t tos_mutex_destroy(k_mutex_t *mutex) { TOS_CPU_CPSR_ALLOC(); TOS_PTR_SANITY_CHECK(mutex); #if TOS_CFG_OBJECT_VERIFY_EN > 0u if (!pend_object_verify(&mutex->pend_obj, PEND_TYPE_MUTEX)) { return K_ERR_OBJ_INVALID; } #endif TOS_CPU_INT_DISABLE(); if (!pend_is_nopending(&mutex->pend_obj)) { pend_wakeup_all(&mutex->pend_obj, PEND_STATE_DESTROY); } pend_object_deinit(&mutex->pend_obj); mutex->pend_nesting = (k_nesting_t)0u; if (mutex->owner) { mutex_old_owner_release(mutex); } TOS_CPU_INT_ENABLE(); knl_sched(); return K_ERR_NONE; }
tos_mutex_pend_timed()
函数用于获取互斥锁,互斥锁就像是临界资源的令牌,任务只有获取到互斥锁时才能访问临界资源。当且仅当互斥锁处于开锁的状态,任务才能获取互斥锁成功,当任务持有了某个互斥锁的时候,其它任务就没法获取这个互斥锁,须要等到持有互斥锁的任务进行释放后,其余任务才能获取成功,任务经过互斥锁获取函数来获取互斥锁的全部权,任务对互斥锁的全部权是独占的,任意时刻互斥锁只能被一个任务持有,若是互斥锁处于开锁状态,那么获取该互斥锁的任务将成功得到该互斥锁,并拥有互斥锁的使用权;若是互斥锁处于闭锁状态,获取该互斥锁的任务将没法得到互斥锁,任务将可能被阻塞,也可能当即返回,阻塞时间timeout
由用户指定,在指定时间还没法获取到互斥锁时,将发送超时,等待任务将自动恢复为就绪态。在任务被阻塞以前,会进行优先级继承,若是当前任务优先级比持有互斥锁的任务优先级高,那么将会临时提高持有互斥锁任务的优先级。
TencentOS tiny
提供两组API接口用于获取互斥锁,分别是tos_mutex_pend_timed()
和tos_mutex_pend()
,主要的差异是参数不一样:可选阻塞与永久阻塞获取互斥锁,实际获取的过程都是同样的。获取互斥锁的过程以下:
TOS_IN_IRQ_CHECK()
检查上下文是否处于中断中,由于互斥锁的操做是不容许在中断中进行的。mutex->pend_nesting
成员变量是否为0
,为0表示互斥锁处于开锁状态,调用mutex_fresh_owner_mark()
函数将获取互斥锁任务的相关信息保存到互斥锁控制块中,如mutex->pend_nesting
成员变量的值变为1
表示互斥锁处于闭锁状态,其余任务没法获取,mutex->owner
成员变量指向当前获取互斥锁的任务控制块,mutex->owner_orig_prio
成员变量则是记录当前任务的优先级,最终使用tos_list_add()
函数将互斥锁控制块的mutex->owner_list
节点挂载到任务控制块的task->mutex_own_list
列表中,任务获取成功后返回K_ERR_NONE
。mutex->pend_nesting
成员变量不为0
,则表示互斥锁处于闭锁状态,那么因为互斥锁具备递归访问特性,那么会判断一下是否是已经持有互斥锁的任务再次获取互斥锁(knl_is_self()
),由于这也是容许的,判断一下mutex->pend_nesting
成员变量的值是否为(k_nesting_t)-1
,若是是则表示递归访问次数达到最大值,互斥锁已经溢出了,返回错误代码K_ERR_MUTEX_NESTING_OVERFLOW
。不然就将mutex->pend_nesting
成员变量的值加1,返回K_ERR_MUTEX_NESTING
表示递归获取成功。timeout
是否为不阻塞TOS_TIME_NOWAIT
,若是不阻塞则直接返回K_ERR_PEND_NOWAIT
错误代码。knl_is_sched_locked()
,则没法进行等待操做,返回错误代码K_ERR_PEND_SCHED_LOCKED
,毕竟须要切换任务,调度器被锁则没法切换任务。tos_task_prio_change()
函数进行改变优先级。pend_task_block()
函数将任务阻塞,该函数实际上就是将任务从就绪列表中移除k_rdyq.task_list_head[task_prio]
,而且插入到等待列表中object->list
,若是等待的时间不是永久等待TOS_TIME_FOREVER
,还会将任务插入时间列表中k_tick_list
,阻塞时间为timeout
,而后进行一次任务调度knl_sched()
。pend_state2errno()
时,则表示任务等获取到互斥锁
,又或者等待发生了超时
,那么就调用pend_state2errno()
函数获取一下任务的等待状态,看一下是哪一种状况致使任务恢复运行,若是任务已经获取到互斥锁,那么须要调用mutex_new_owner_mark()
函数标记一下获取任务的信息,将获取互斥锁任务的相关信息保存到互斥锁控制块中。注意:当获取互斥锁的任务能从阻塞中恢复运行,也不必定是获取到互斥锁,也多是发生了超时,所以在写程序的时候必需要判断一下获取的互斥锁状态,若是返回值是K_ERR_NONE
与K_ERR_MUTEX_NESTING
则表示获取成功!
__API__ k_err_t tos_mutex_pend_timed(k_mutex_t *mutex, k_tick_t timeout) { TOS_CPU_CPSR_ALLOC(); k_err_t err; TOS_PTR_SANITY_CHECK(mutex); TOS_IN_IRQ_CHECK(); #if TOS_CFG_OBJECT_VERIFY_EN > 0u if (!pend_object_verify(&mutex->pend_obj, PEND_TYPE_MUTEX)) { return K_ERR_OBJ_INVALID; } #endif TOS_CPU_INT_DISABLE(); if (mutex->pend_nesting == (k_nesting_t)0u) { // first come mutex_fresh_owner_mark(mutex, k_curr_task); TOS_CPU_INT_ENABLE(); return K_ERR_NONE; } if (knl_is_self(mutex->owner)) { // come again if (mutex->pend_nesting == (k_nesting_t)-1) { TOS_CPU_INT_ENABLE(); return K_ERR_MUTEX_NESTING_OVERFLOW; } ++mutex->pend_nesting; TOS_CPU_INT_ENABLE(); return K_ERR_MUTEX_NESTING; } if (timeout == TOS_TIME_NOWAIT) { // no wait, return immediately TOS_CPU_INT_ENABLE(); return K_ERR_PEND_NOWAIT; } if (knl_is_sched_locked()) { TOS_CPU_INT_ENABLE(); return K_ERR_PEND_SCHED_LOCKED; } if (mutex->owner->prio > k_curr_task->prio) { // PRIORITY INVERSION: // we are declaring a mutex, which's owner has a lower(numerically bigger) priority. // make owner the same priority with us. tos_task_prio_change(mutex->owner, k_curr_task->prio); } pend_task_block(k_curr_task, &mutex->pend_obj, timeout); TOS_CPU_INT_ENABLE(); knl_sched(); err = pend_state2errno(k_curr_task->pend_state); if (err == K_ERR_NONE) { // good, we are the owner now. TOS_CPU_INT_DISABLE(); mutex_new_owner_mark(mutex, k_curr_task); TOS_CPU_INT_ENABLE(); } return err; } __API__ k_err_t tos_mutex_pend(k_mutex_t *mutex) { TOS_PTR_SANITY_CHECK(mutex); return tos_mutex_pend_timed(mutex, TOS_TIME_FOREVER); }
mutex_fresh_owner_mark
与mutex_new_owner_mark()
函数的实现:
__STATIC_INLINE__ void mutex_fresh_owner_mark(k_mutex_t *mutex, k_task_t *task) { mutex->pend_nesting = (k_nesting_t)1u; mutex->owner = task; mutex->owner_orig_prio = task->prio; tos_list_add(&mutex->owner_list, &task->mutex_own_list); } __STATIC_INLINE__ void mutex_new_owner_mark(k_mutex_t *mutex, k_task_t *task) { k_prio_t highest_pending_prio; mutex_fresh_owner_mark(mutex, task); // we own the mutex now, make sure our priority is higher than any one in the pend list. highest_pending_prio = pend_highest_prio_get(&mutex->pend_obj); if (task->prio > highest_pending_prio) { tos_task_prio_change(task, highest_pending_prio); } }
互斥锁的释放是不容许在中断中释放的,主要的缘由是由于中断中没有上下文的概念,因此中断上下文不能持有,也不能释放互斥锁;互斥锁有所属
关系,只有持有互斥锁的任务才能将互斥锁释放,而持有者是任务。
任务想要访问某个资源的时候,须要先获取互斥锁,而后进行资源访问,在任务使用完该资源的时候,必需要及时
释放互斥锁,这样别的任务才能访问临界资源。任务能够调用tos_mutex_post()
函数进行释放互斥锁,当互斥锁处于开锁状态时就表示我已经用完了,别人能够获取互斥锁以访问临界资源。
使用tos_mutex_post()
函数接口时,只有已持有互斥锁全部权的任务才能释放它,当任务调用tos_mutex_post()
函数时会将互斥锁释放一次,当互斥锁彻底释放完毕(mutex->pend_nesting
成员变量的值为0
)就变为开锁状态,等待获取该互斥锁的任务将被唤醒。若是任务的优先级被互斥锁的优先级翻起色制临时提高,那么当互斥锁被释放后,任务的优先级将恢复为本来设定的优先级。
TencentOS tiny
中能够只让等待中的一个任务获取到互斥锁(在等待的任务中具备最高优先级)。
在tos_mutex_post()
函数中的处理也是很是简单明了的,其执行思路以下:
K_ERR_MUTEX_NOT_OWNER
。mutex->pend_nesting
成员变量的值减1,而后判断它的值是否为0,若是不为0则表示当前任务仍是持有互斥锁的,也无需进行后续的操做,直接返回K_ERR_MUTEX_NESTING
。mutex->pend_nesting
成员变量的值为0,则表示互斥锁处于开锁状态,则须要调用mutex_old_owner_release()
函数彻底释放掉互斥锁,在这个函数中会将互斥锁控制块的成员变量(如owner_list、owner、owner_orig_prio
等都设置为初始值),此外还有最重要的就是判断一下任务是否发生过优先级继承,若是是则须要将任务恢复为本来的优先级,不然就无效理会。pend_is_nopending()
函数判断一下是否有任务在等待互斥锁,若是没有则返回K_ERR_NONE
表示释放互斥锁成功,由于此时没有唤醒任务也就无需任务调度,直接返回便可。pend_wakeup_one()
函数唤醒一个等待任务,这个任务在等待任务中是处于最高优先级的,由于TencentOS tiny
的等待任务是按照优先级进行排序。knl_sched()
。__API__ k_err_t tos_mutex_post(k_mutex_t *mutex) { TOS_CPU_CPSR_ALLOC(); TOS_PTR_SANITY_CHECK(mutex); #if TOS_CFG_OBJECT_VERIFY_EN > 0u if (!pend_object_verify(&mutex->pend_obj, PEND_TYPE_MUTEX)) { return K_ERR_OBJ_INVALID; } #endif TOS_CPU_INT_DISABLE(); if (!knl_is_self(mutex->owner)) { TOS_CPU_INT_ENABLE(); return K_ERR_MUTEX_NOT_OWNER; } if (mutex->pend_nesting == (k_nesting_t)0u) { TOS_CPU_INT_ENABLE(); return K_ERR_MUTEX_NESTING_OVERFLOW; } --mutex->pend_nesting; if (mutex->pend_nesting > (k_nesting_t)0u) { TOS_CPU_INT_ENABLE(); return K_ERR_MUTEX_NESTING; } mutex_old_owner_release(mutex); if (pend_is_nopending(&mutex->pend_obj)) { TOS_CPU_INT_ENABLE(); return K_ERR_NONE; } pend_wakeup_one(&mutex->pend_obj, PEND_STATE_POST); TOS_CPU_INT_ENABLE(); knl_sched(); return K_ERR_NONE; }
持有互斥锁的任务释放互斥锁mutex_old_owner_release()
。
__STATIC_INLINE__ void mutex_old_owner_release(k_mutex_t *mutex) { k_task_t *owner; owner = mutex->owner; tos_list_del(&mutex->owner_list); mutex->owner = K_NULL; // the right time comes! let's do it! if (owner->prio_pending != K_TASK_PRIO_INVALID) { tos_task_prio_change(owner, owner->prio_pending); owner->prio_pending = K_TASK_PRIO_INVALID; } else if (owner->prio != mutex->owner_orig_prio) { tos_task_prio_change(owner, mutex->owner_orig_prio); mutex->owner_orig_prio = K_TASK_PRIO_INVALID; } }
相关代码能够在公众号后台回复 “ 19 ” 获取。欢迎关注“物联网IoT开发”公众号