Raw-OS互斥的源代码分析的量的Mutex

   作为分析的内核版本2014-04-15,基于1.05正式版。blogs我们会跟上的内核开发进度的最新版本,如果出现源代码的目光”???”的话,没有深究的部分是理解。

        Raw-OS官方站点:http://www.raw-os.org/

        Raw-OS托管地址:https://github.com/jorya/raw-os/

 

        今天来说说Raw-OS的相互排斥锁Mutex模块,这个Mutex用起来非常easy,用户api也仅仅有5个函数。可是内核关于这个模块的代码有点复杂,看明确要花点时间,所以要做好心理准备啊~亲~

 

一、优先级反转问题

        先来看看所谓的“优先级反转”问题

 

 Raw-OS互斥的源代码分析的量的Mutex

 

        任务訪问某个硬件。那么硬件对于系统来说是一个共享资源,用相互排斥量来保护,那么

        1.task z訪问硬件。获取相互排斥锁。然后运行硬件相关操作(读写)

        2.然后task x优先级比task z高。那么抢占task z

        3.Task x也要訪问硬件进行读写,然后发现硬件被占用。那么task就被堵塞

        4.系统调度。task y运行,等待task y运行完成

        5.系统调度,task z运行。运行完硬件操作后,释放相互排斥锁

        6.最后才轮到task x运行硬件操作

 

        这里task x是最高优先级。反而被低优先级任务抢占执行,这就是所谓的“优先级反转”问题的由来。

 

二、“优先级反转”问题的一般解决方法

        依照一般RTOS理论,解决“优先级反转”的问题有两种一般方法:

        优先级继承协议、优先级天花板协议

 

        以下简单描写叙述下两种协议的原理:

 

        优先级继承协议:

 

Raw-OS互斥的源代码分析的量的Mutex

 

        依照“优先级反转”问题的延续,关键在于步骤4的优先级提升

        1.task z訪问硬件。获取相互排斥锁,然后运行硬件相关操作(读写)

        2.然后task x优先级比task z高,那么抢占task z

        3.Task x也要訪问硬件进行读写。然后发现硬件被占用。那么task就被堵塞

        4.将task z的优先级提升至task x的优先级,然后运行系统调度

        5.task z提升优先级后。那么task y优先级就不足够高来抢占task z。所以task z继续运行

        6.task z运行完硬件操作后。释放信号量,还原原来的基优先级

        7.系统调度。运行task x进行硬件读写

 

        这里“优先级反转”的问题能够通过继承搞优先级任务就得到解决

 

        优先级天花板协议

 

Raw-OS互斥的源代码分析的量的Mutex

 

        优先级天花板协议就要比优先级继承协议简单得多

        1.给相互排斥锁定义一个优先级,这个优先级比全部要获取相互排斥锁的任务的优先级都要高

        2.task z获取相互排斥锁时,直接将优先级提升到提前定义优先级

        3.由于提前定义优先级比全部要获取相互排斥锁的任务优先级都要高。所以获取相互排斥锁后的task z无法被更高的优先级任务抢占,就能够顺利运行硬件操作

        4.运行完硬件操作后,task z还原原来的基优先级,运行系统调度

        5.唤醒堵塞的task x

        6.task x运行硬件操作

 

三、获取相互排斥锁操作

        1.相互排斥锁占用任务

        内核中的相互排斥锁被占用时。用相互排斥锁控制块的mtxtsk变量表示被哪个任务占用

        mutex_ptr->mtxtsk;

 

        2.相互排斥锁链表

        内核的相互排斥锁链表。当然这个名词是我自己想出来的哈,看一个情况

 

 Raw-OS互斥的源代码分析的量的Mutex

 

        Task x在某一时刻内顺利的占用3个相互排斥锁,那么,在内核中,同一个任务占用多个相互排斥锁时。被同一个任务占用的相互排斥锁会形成一个相互排斥锁链表

 

 Raw-OS互斥的源代码分析的量的Mutex

 

        相互排斥锁间通过相互排斥锁控制块的mtxlist指针组成单向链表,第一个被任务占用的锁的相互排斥锁链表指针指向0。然后task通过mtxlist指针指向最迟获取的相互排斥锁的链表指针。

 

        相互排斥锁链表在样例中再解说内核是怎样使用的

 

        3.获取相互排斥锁情况举例

        由于使用优先级天花板协议的相互排斥锁相关操作都很之简单。所以只举例使用优先级继承协议的相互排斥锁的情况

 

 Raw-OS互斥的源代码分析的量的Mutex

 

        这是一个比較简单的情况:获取属于执行任务的相互排斥锁

        1.task x占有相互排斥锁

        2.task x成功占有相互排斥锁,返回

        3.task y抢占task x获取相互排斥锁,相互排斥锁被task x占有

        4.task x提升优先级

        5.task y堵塞在相互排斥锁上

 

        那么看一个比較复杂的样例:获取属于堵塞任务的相互排斥锁

 

 Raw-OS互斥的源代码分析的量的Mutex

 

        重点关注第5步骤之后的操作,特别是第9步

 

        1.task x获取相互排斥锁mutex1

        2.task x获取相互排斥锁mutex1成功,返回

        3.task y抢占task x,获取相互排斥锁mutex2

        4.task y获取相互排斥锁mutex2成功,返回

        5.task y运行过程。获取相互排斥锁mutex1

        6.由于mutex已经被task x占用,又由于task y优先级高于task x,所以task x提升到task y的优先级

        7.task y由于不能获取相互排斥锁mutex1,堵塞在mutex1上。等待mutex1被释放,然后系统调度后返回task x运行

        8.很之不幸的是。又来一个更高优先级的任务task z抢占task x,获取相互排斥锁mutex2

        9.由于mutex2已经被task y占有。所以,task y必须提升到task z的优先级,好了,这里重点来了,由于task y是堵塞在mutex1上面的。当task y提升了优先级,那么task x也须要提升优先级,这里发生的就是所谓的“相互排斥锁嵌套”的情况

        10.task z因为不能获取相互排斥锁mutex2,所以堵塞在mutex2上,返回task x运行

 

        这里须要注意的几点是:

        1.相互排斥锁优先级是全部要获取相互排斥锁任务的优先级的最高那个

        2.使用优先级继承协议的相互排斥锁被任务占有时,占有相互排斥锁过程的任务优先级等于可能的相互排斥锁任务优先级

        3.相互排斥锁嵌套时,会发生连续提升任务优先级的情况

 

四、创建相互排斥锁

        创建相互排斥锁就是创建一个相互排斥锁控制块。操作类似创建任务控制块过程类似

RAW_U16 raw_mutex_create(RAW_MUTEX *mutex_ptr, RAW_U8 *name_ptr, RAW_U8 policy, RAW_U8 ceiling_prio)
{
	/* 检查要创建的相互排斥量控制块是否有实体定义,未定义时返回 */
	#if (RAW_MUTEX_FUNCTION_CHECK > 0)
	if (mutex_ptr == 0) {
		return RAW_NULL_OBJECT;
	}
	/* 检查Raw-OS相互排斥量支持的协议:优先级继承、优先级天花板、无协议。不是这三种协议时返回 */
	if ((policy != RAW_MUTEX_CEILING_POLICY) && (policy != RAW_MUTEX_INHERIT_POLICY) && (policy != RAW_MUTEX_NONE_POLICY)){
		return RAW_MUTEX_NO_POLICY;
	}
	#endif

	/* 初始化相互排斥量控制块内的堵塞链表 */
	list_init(&mutex_ptr->common_block_obj.block_list);
	/* 相互排斥量堵塞方式:默认优先级排序 */
	mutex_ptr->common_block_obj.block_way = RAW_BLOCKED_WAY_PRIO;
	/* 相互排斥量控制块名称 */
	mutex_ptr->common_block_obj.name  =  name_ptr;
	/* 这个是当前在使用相互排斥锁的任务控制块 */
	mutex_ptr->mtxtsk 		= 0;
	/* 这个是当同一个任务占有多个相互排斥锁时,被同一个任务占用的相互排斥锁会形成相互排斥锁链表 */
	mutex_ptr->mtxlist 		= 0;
	/* 相互排斥锁的协议:优先级继承、优先级天花板、无协议之中的一个 */
	mutex_ptr->policy = policy;

	#if (CONFIG_RAW_TASK_0 > 0)
	if (policy == RAW_MUTEX_CEILING_POLICY) {
		if (ceiling_prio == 0) {
			return RAW_CEILING_PRIORITY_NOT_ALLOWED;
		}
	}
	#endif
	/* 设置优先级天花板的值 */
	mutex_ptr->ceiling_prio = ceiling_prio;
	/* 相互排斥锁类型:相互排斥锁对象 */
	mutex_ptr->common_block_obj.object_type = RAW_MUTEX_OBJ_TYPE;
	/* trace调试系统??? */
	TRACE_MUTEX_CREATE(raw_task_active, mutex_ptr, name_ptr, policy, ceiling_prio);

	return RAW_SUCCESS;
}

 

五、获取相互排斥锁内核代码分析

         由于内核获取相互排斥锁的代码综合了各种各样的获取相互排斥锁的情况,光看代码事实上是非常难理解的。这里建议对上上面列举的两种获取情况来跟踪代码。相互排斥锁的内核代码要花点时间去看啊~各位亲~

RAW_U16 raw_mutex_get(RAW_MUTEX *mutex_ptr, RAW_TICK_TYPE wait_option)
{
	RAW_U16 		error_status;
	RAW_TASK_OBJ	*mtxtsk;
	/* 定义CPU状态机字变量 */
	RAW_SR_ALLOC();
	/* 检查相互排斥锁控制块是否存在,不存在返回 */
	#if (RAW_MUTEX_FUNCTION_CHECK > 0)
	if (mutex_ptr == 0) {
		return RAW_NULL_OBJECT;
	}
	/* 检查中断嵌套,中断内不能调用此函数。即中断内不能获取相互排斥锁 */
	if (raw_int_nesting) {
		return RAW_NOT_CALLED_BY_ISR;
	}
	#endif
	/* 保存CPU状态字 */
	RAW_CRITICAL_ENTER();
	/* 检查操作传入的相互排斥锁控制块,控制块内标识不是相互排斥锁对象类型时,返回 */
	if (mutex_ptr->common_block_obj.object_type != RAW_MUTEX_OBJ_TYPE) {
		RAW_CRITICAL_EXIT();
		return RAW_ERROR_OBJECT_TYPE;
	}

	/* 假设当前任务已经获取相互排斥锁后,再获取同一个相互排斥锁,那么会发生死锁,标记后返回 */
	if (raw_task_active == mutex_ptr->mtxtsk) {
		mutex_ptr->owner_nested++;
  		RAW_CRITICAL_EXIT();
		return RAW_MUTEX_OWNER_NESTED;
	}

	/*
	 * 这里是对于任务訪问使用优先级天花板协议的相互排斥锁的边界推断
	 *
	 * 这里注意Raw-OS的优先级数值越小优先级越高,那么依据优先级天花板协议,不论什么訪问相互排斥锁
	 * 的任务的优先级必须小于或等于相互排斥锁的天花板优先级
	 */
	if (mutex_ptr->policy == RAW_MUTEX_CEILING_POLICY) {
		/* 当訪问相互排斥锁的任务基优先级大于相互排斥锁的优先级时。错误返回 */
		if (raw_task_active->bpriority < mutex_ptr->ceiling_prio) {
			RAW_CRITICAL_EXIT();
			/* trace调试系统??? */
			TRACE_MUTEX_EX_CE_PRI(raw_task_active, mutex_ptr, wait_option);
			return RAW_EXCEED_CEILING_PRIORITY;
		}
	}

	/* 获取当前使用相互排斥锁的任务控制块 */
	mtxtsk = mutex_ptr->mtxtsk;
	/* 假设相互排斥锁有没被任务占用,任务将获取相互排斥锁 */
	if (mtxtsk == 0) {
		/* 将当前使用相互排斥锁的任务记录到相互排斥锁控制块的任务指针中 */
		mutex_ptr->mtxtsk = raw_task_active;
		/*
		 * 将当前相互排斥锁插入到当前任务控制块的相互排斥锁链表中
		 * 当某一个任务占用多个相互排斥锁时。相互排斥锁组成相互排斥锁链表
		 *
		 * 0.raw_task_active->mtxlist未空,表示任务不占有不论什么相互排斥锁
		 * 1.当raw_task_active获取一个相互排斥锁时,mutex_ptr->mtxlist指向0。raw_task_active->mtxlist指向当前锁
		 * 2.当任务此时再获取一个相互排斥锁,那么,新锁mtxlist指向任务mtxlist。又由于任务的mtxlist指向前一个锁,那么新锁就指向前一个锁
		 * 3.任务的mtxlist更新指向新获取的锁(总是指向最新占用的相互排斥锁)
		 *
		 * note:相互排斥锁的mtxlist是这样形式单项链表的
		 */
		mutex_ptr->mtxlist = raw_task_active->mtxlist;
		raw_task_active->mtxlist = mutex_ptr;
		/* 相互排斥锁的使用次数+1 */
		mutex_ptr->owner_nested = 1u;

		/* 当相互排斥锁使用优先级天花板协议时。须要在临界区提升获取相互排斥锁的任务优先级=优先级天花板 */
		if (mutex_ptr->policy == RAW_MUTEX_CEILING_POLICY) {
			if (raw_task_active->priority > mutex_ptr->ceiling_prio) {
				/*
				 * 暂时提升任务互优先级=相互排斥锁优先级天花板相互排斥锁
				 * 提升优先级的当前运行任务,所以改变优先级时只更改优先级后,又一次加入到就绪链表
				 */
				change_internal_task_priority(raw_task_active, mutex_ptr->ceiling_prio);
			}
		}

		RAW_CRITICAL_EXIT();
		/* trace调试系统 */
		TRACE_MUTEX_GET(raw_task_active, mutex_ptr, wait_option);
		/* 成功占用相互排斥锁,返回 */
		return RAW_SUCCESS;
	}

	/* 假设当不能获取相互排斥锁时,而且用户不设置等待,那么直接返回 */
	if (wait_option == RAW_NO_WAIT) {
		RAW_CRITICAL_EXIT();
		return RAW_NO_PEND_WAIT;
	}

	/* 当系统系统上锁时,任务不能被堵塞,直接返回 */
	SYSTEM_LOCK_PROCESS();

	/*
	 * 当前任务无法获取相互排斥锁时,而且当相互排斥锁使用的是优先级继承协议,
	 * 检查当前获取相互排斥锁任务优先级,假设当前任务优先级高于占用相互排斥锁任务的优先级,
	 * 那么之前占用相互排斥锁的任务,要继承当前无法获取相互排斥锁任务的优先级(即须要提升占用相互排斥锁任务的优先级)
	 */
	if (mutex_ptr->policy == RAW_MUTEX_INHERIT_POLICY) {
		if (raw_task_active->priority < mtxtsk->priority) {
			/* trace调试系统??? */
			TRACE_TASK_PRI_INV(raw_task_active, mtxtsk);
			/*
			 * 将此时占用相互排斥锁的任务继承当前无法获取相互排斥锁任务的优先级
			 * 改变优先级后又一次加入就绪链表适当位置。所谓适当位置。就是按优先级大小排序的位置
			 */
			change_internal_task_priority(mtxtsk, raw_task_active->priority);
		}
	}

	/* 这里把当前不能获取相互排斥锁的任务堵塞到相互排斥锁的堵塞链表上 */
	raw_pend_object((RAW_COMMON_BLOCK_OBJECT *)mutex_ptr, raw_task_active, wait_option);

	RAW_CRITICAL_EXIT();
	/* trace调试系统??? */
	TRACE_MUTEX_GET_BLOCK(raw_task_active, mutex_ptr, wait_option);

	/* 运行任务调度 */
	raw_sched();

	/*
	 * 运行到这里,是由于某些条件唤醒新的最高优先级任务,依据之前堵塞的情况,
	 * 推断之前堵塞的情况。置位新任务TCB堵塞标志,目的是得知是什么操作唤醒任务
	 */
	error_status = block_state_post_process(raw_task_active, 0);

	return error_status;
}

 

六、释放相互排斥锁内核代码分析

        那么。当任务运行完临界区的时候。任务就要释放获取的相互排斥锁,而且。依据须要还原成任务原本的优先级,分两种情况:

        1.假设task只占有一个相互排斥锁时。那么释放相互排斥锁后,就还原task原来的基优先级

        2.假设task占有两个以上相互排斥锁时。那么释放当中一个相互排斥锁后,就会检查其余相互排斥锁的相互排斥锁优先级。然后改变成其余相互排斥锁优先级中最高优先级的那一个

 

        那么这里占有两个以上相互排斥锁时,相互排斥锁链表就发挥作用了,检查全部相互排斥锁优先级时,就是历遍整个相互排斥锁链表,检查每个相互排斥锁链表项的相互排斥锁堵塞链表来得到相互排斥锁优先级。这里我可能讲得不明不白的,看以下我自己理解的图吧~

 

 Raw-OS互斥的源代码分析的量的Mutex

 

        之前说过

        相互排斥锁优先级:定义为全部是全部要获取相互排斥锁任务的优先级的最高那个

        相互排斥锁项优先级:实际上等于相互排斥锁项的堵塞链表的堵塞任务的最高优先级(堵塞链表第1个任务。由于堵塞链表是按优先级大小排序的)

        相互排斥锁链表最高优先级:定义为整条相互排斥锁链表中全部相互排斥锁项的相互排斥锁优先级最大值

 

        那么当任务占用2个或2个以上相互排斥锁时,相互排斥锁就会形成相互排斥锁链表,那么在任务释放相互排斥锁后,任务必须更改任务的执行优先级至相互排斥锁链表最高优先级

 

        这些是Raw-OS的相互排斥锁模块比較难理解的部分。之前提过的相互排斥锁链表并结合源代码去理解,略微话点时间去看

RAW_U16 raw_mutex_put(RAW_MUTEX *mutex_ptr)
{
	LIST 				*block_list_head;
	RAW_TASK_OBJ   		*tcb;
	/* 定义CPU状态机字变量 */
	RAW_SR_ALLOC();
	/* 检查相互排斥锁控制块是否存在,不存在返回 */
	#if (RAW_MUTEX_FUNCTION_CHECK > 0)
	if (mutex_ptr == 0) {
		return RAW_NULL_OBJECT;
	}
	/* 检查中断嵌套,中断内不能调用此函数。即中断内不能释放相互排斥锁 */
	if (raw_int_nesting) {
		return RAW_NOT_CALLED_BY_ISR;
	}
	#endif

	RAW_CRITICAL_ENTER();
	/* 检查操作传入的相互排斥锁控制块,控制块内标识不是相互排斥锁对象类型时,返回 */
	if (mutex_ptr->common_block_obj.object_type != RAW_MUTEX_OBJ_TYPE) {
		RAW_CRITICAL_EXIT();
		return RAW_ERROR_OBJECT_TYPE;
	}

	/* 检查占用相互排斥锁的任务是否是当前任务。系统要求必须是获取相互排斥锁的任务自行释放相互排斥锁 */
	if (raw_task_active != mutex_ptr->mtxtsk) {
		RAW_CRITICAL_EXIT();
		return RAW_MUTEX_NOT_RELEASE_BY_OCCYPY;
	}
	/* 释放相互排斥锁后。相互排斥锁使用次数-- */
	mutex_ptr->owner_nested--;
	/* 当此时相互排斥锁使用次数不为0,那么仅仅有一种情况,就是当时已经获取相互排斥锁的任务又一次获取相互排斥锁。死锁状态 */
	if (mutex_ptr->owner_nested) {
		RAW_CRITICAL_EXIT();
		return RAW_MUTEX_OWNER_NESTED;
	}
	/*
	 * 历遍传入相互排斥锁的相互排斥锁链表,而且将传入的相互排斥锁从相互排斥链表中删除,
	 * 而且设置释放相互排斥锁的任务的优先级,或者是基优先级。或者是改变成相互排斥锁链表的最高优先级
	 *
	 * 1.当任务仅占用一个相互排斥锁时,释放后还原成基优先级
	 * 2.当任务占用2个或2个以上相互排斥锁时,改变成相互排斥锁链表的最高优先级
	 */
	release_mutex(raw_task_active, mutex_ptr);

	/* 获取相互排斥锁堵塞链表头 */
	block_list_head = &mutex_ptr->common_block_obj.block_list;

	/* 假设相互排斥锁堵塞链表为空,那么没有其它任务堵塞在这个相互排斥锁上 */
	if (is_list_empty(block_list_head)) {
		/* 设置相互排斥锁被任务占有的标志为0。返回 */
		mutex_ptr->mtxtsk = 0;
		RAW_CRITICAL_EXIT();
		TRACE_MUTEX_RELEASE_SUCCESS(raw_task_active, mutex_ptr);
		return RAW_SUCCESS;
	}

	/* 执行到这里,说明堵塞相互排斥锁的堵塞链表不为空。那么,获取堵塞链表的第一个任务(最高优先级任务) */
	tcb = list_entry(block_list_head->next, RAW_TASK_OBJ, task_list);

	/* 唤醒当前被释放相互排斥锁中堵塞链表的最高优先级任务 */
	raw_wake_object(tcb);

	/* 设置占有相互排斥锁的任务为唤醒后的任务 */
	mutex_ptr->mtxtsk = tcb;
	/* 更新相互排斥锁链表 */
	mutex_ptr->mtxlist = tcb->mtxlist;
	tcb->mtxlist = mutex_ptr;
	mutex_ptr->owner_nested = 1u;

	/*
	 * 这里为什么释放相互排斥锁后,唤醒相互排斥锁堵塞链表中的最高优先级任务后,要还改变任务的执行优先级 ???
	 * 由于当任务堵塞在相互排斥锁上后,仅仅会受到优先级继承协议的影响,而被唤醒后,任务优先级也仅仅会是相互排斥锁优先级
	 * 而假设任务是堵塞在使用优先级天花板协议的相互排斥锁上被唤醒后,就要提升至相互排斥锁的优先级天花板
	 */
	/* 假设相互排斥锁是优先级天花板协议 */
	if (mutex_ptr->policy == RAW_MUTEX_CEILING_POLICY) {
		/* 当任务的执行优先级低于相互排斥锁的天花板优先级, */
		if (tcb->priority > mutex_ptr->ceiling_prio) {
			/* 将任务执行优先级提升到相互排斥锁的优先级天花板,又一次增加到就绪队列 */
			change_internal_task_priority(tcb, mutex_ptr->ceiling_prio);
		}
	}

	TRACE_MUTEX_WAKE_TASK(raw_task_active, tcb);
	RAW_CRITICAL_EXIT();
	/* 执行系统调度 */
	raw_sched();

	return RAW_SUCCESS;
}

 

七、相互排斥锁中的任务优先级改变

        经过上面的分析就能够知道获取相互排斥锁,释放相互排斥锁等等的操作都随时涉及到任务执行优先级的改变,这也是之前blog中任务篇没涉及的操作。首先要理解好上面相互排斥锁的操作再去看怎样依据各种各样的情况更改任务的执行优先级,这个在内核中也是比較难理解的

 

        总结相互排斥锁的几点,再结合看代码再理解。确实mutex的代码是比較复杂的啊~亲~

        1.当一个任务占有多个相互排斥锁时,同一个任务的全部相互排斥锁组成相互排斥锁链表

        2.相互排斥锁优先级等于被堵塞在相互排斥锁上堵塞链表全部任务的最高优先级

        3.相互排斥锁链表最高优先级等于全部相互排斥锁链表项的相互排斥锁优先级的最大值

        4.取得相互排斥锁的任务的执行优先级等于相互排斥锁链表的最高优先级

        5.注意任务获取执行任务占有的相互排斥锁时,执行任务的执行优先级怎样改变

        6.注意任务获取堵塞任务占有的相互排斥锁时,堵塞任务的执行优先级怎样改变

 

 

 

版权声明:本文博客原创文章,博客,未经同意,不得转载。







本文转自mfrbuaa博客园博客,原文链接:http://www.cnblogs.com/mfrbuaa/p/4740420.html,如需转载请自行联系原作者


上一篇:【C++标准库】7-STL容器-Map与Multimap-关联数组-异常处理-运用实例


下一篇:快速体验 Sentinel 集群限流功能,只需简单几步