版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
1、THREADX深入学习简介最近在做THREADX移植项目,所以在开始学习THREADX操作系统。想把自己学到的东西总结一下。学习操作系统时,按照领导的意思把操作系统进行模块划分。通过查找资料将操作系统划分为任务调度模块、任务管理模块、任务间同步和通信模块、内存管理模块、中断管理模块、时钟管理模块。下面将分别对各个模块进行分析和研究。我将深入介绍各个模块的工作原理,通过此文档能对操作系统的工作原理有深入的了解。首先得我的分析是针对MIPS、ARM、251内核进行分析。我移植的平台是16位的251平台。个人认为移植一个操作系统,首先对操作系统的内核调度原理必须十分清楚,然后对你的移植平台架构、指令
2、集也要十分清楚,比如说下面几个方面:1、子程序调用时PC值是怎么被保存得(MPIS,将子程序的返回值存放在了RA寄存器中,251是PC自动入栈(ECALL指令)退出时使用ERET等指令,ARM是在LR寄存器中要计算相应减去的数值)。2、中断发生时(251PC自动入栈但顺序和子程序调用压入顺序不一样,中断返回使用RETI指令。MIPS,PC是被存入了EPC寄存器中,使用eret指令。ARM,LR中数值的计算,赋值给PC即可)2.任务调度操作系统的核心模块就是内核调度。首先要弄清楚其调度原理。带着下面几个问题去思考。1、 任务入口函数第一次是怎么被执行的。2、 任务是怎么被切换的。3、 任务是怎么
3、被抢占的。以上几个问题是任务调度的核心。带着这几个问题去看内核源码发现任务调度使用的方法就是任务栈和系统栈,内核利用入栈和出栈完成对任务的调度和切换。而任务被调度起来是依靠timer驱动来工作。基于此分析可以得出内核调度重点是以下几个方面:1、明白任务栈的构建方式,即任务创建时初始化任务堆栈时保存的数据。这些数据要根据具体的硬件平台去实现,这个栈的初始化就是解决上面的第一个问题的。因为在内核调度时,任务第一次被执行是出此栈来执行对应的入口函数的。对于栈我们要明白任务栈和系统栈的区别,要针对不同的硬件平台而做不同的设计。任务栈有两种类型,一种叫做中断类型栈,是在产生中断保存任务上下文到任务栈空间
4、的数据,其出栈方式是要利用中断返回时的出栈方式;另外一种栈叫做用任务栈是任务在执行过程中自挂起时需要把CPU控制权重新交给调度器的时候需要把当前的任务上下文进行保护,在任务出此栈的时候要用子程序返回的方式去出栈。系统栈主要系统内核自己需要使用的栈。移植过程中我们可能要不断的切换SP在这两个栈之间。也可能只用系统的SP,不切换只是来回的复制两个栈空间的内容(如51系列)。这里移植的时候可能会出现以下几个问题:a、TIMER中断产生后,任务调度任务第一次执行出栈时,是按照子程序返回的方式去出栈。这样中断将永远不会被返回。这样就有问题了,这个在任务切换的时候经常遇到就是任务入口函数第一次被执行的时候
5、。解决的办法是初始化任务栈的时候初始化成中断类型的栈。b、要明白硬件平台的压栈方式。发生中断时的压栈方式和子程序调用时的压栈方式是不同的。像MIPS这样产生中断和子程序调用时都有相应的寄存器保存相应的值,这样的平台实现起来比较简单。而251平台确没有,251不管是中断还是子程序调用都是把PC值压入栈中,而且压入得顺序也不一样。(251中产生中断和子程序调用时PC值入栈的顺序是不一样的,PC分三次入栈,见具体的芯片手册)c、任务的出栈方式。任务出栈是在任务调度后恢复优先级最高的任务时执行的,先判断任务栈类型然后采用相应的出栈方式。251平台任务栈和中断栈保护的数据一样,但出栈的方式也就是子程序返
6、回的方式是RETI和ERET。子程序调用可以采用RETI而中断产生时不能采用ERET否则会中断一直无法返回。d、入栈的方式:对于mips把相应的寄存器数据压入到任务栈就OK了。而251平台是系统自动压栈,出栈的时候要出对应的PC值才OK。因此在产生中断时先进行入栈操作,然后调用子程序。由于是在子程序中把栈数据拷贝到任务栈中,因此要把SP减去相应的值,要看子程序调用压入了几个数据。ECALL是减3。切记不要在任务栈中存入额外的数据。否则出栈就出错了。2、TIMER产生中断执行timer中断处理函数,timer中断会对当前任务的时间片进行计时,时间到期后会调用任务切换函数,切换下一个更高优先级的任
7、务。任务切换函数只是把当前任务切换到同一个优先级列表的最后,并没有去执行下一个任务。这个工作交给了任务上下文恢复函数,此函数会比较当前正在执行的任务和下一个要执行的任务控制块指针。如果两个控制块指针的值不一样就要去回到调度接口去重新调度了。正好上面的2问题。3、任务在执行过程中释放信号量、中断等操作时使一个更高优先级任务就绪时就会发生抢占。此时当前任务上下文会被保存同时高优先级任务的控制块指针会赋值给更高下一个要被执行的控制块指针。如果是中断产生的下面的操作如同2中的一样。如果是任务间同步等操作导致则会调用转交CPU控制权到调度接口去重新调度。这样更高优先级的任务就被执行了。这个就是上面的3问
8、题。4、任务调度流程图。力求详细而明了。图1是任务调度流程图。图1.任务调度流程图 2.1 任务调度接口 图2.是THREADX操作系统函数总体框架示意图。图2.THREADX操作系统函数总体框架示意图2.1.1调度器接口1、_tx_thread_schedule (调度器)函数原形Void _tx_thread_schedule(void)返回值无参数无说明调度任务去执行调度器接口主要负责从就绪列表中获取优先级最高的任务去执行。2.1.2 TIMER中断接口2、_tx_timer_interrupt (timer中断处理函数)函数原形Void _tx_timer_interrupt(void
9、)返回值无参数无说明Timer中断处理函数,负责时间片和定时器计时2.1.3 任务切换接口1、_tx_thread_time_slice (任务切换函数)函数原形UINT _tx_thread_time_slice(VOID)返回值True,任务已经切换False,任务没有切换参数无说明进行任务切换,任务时间片到时进行同一个优先级任务切换2.1.4 定时器任务入口函数2、_tx_timer_thread_entry (定时器任务入口函数)函数原形VOID _tx_timer_thread_entry(ULONG timer_thread_input)返回值无参数ULONG timer_thre
10、ad_input 任务处理参数说明定时器任务处理函数3任务管理任务管理主要有任务创建、任务挂起、任务恢复、睡眠、等接口组成。一、注意问题:1、就绪列表的组成以及获取下一个可以执行的最高优先级任务的方式。THREADX支持的优先级为32个,任务个数为任意,视具体资源而定。具有一32个元素的就绪链表,是按照任务优先级进行排序存放的。还有一个最低位映射表thread_lowest_bit。3.1 任务初始化任务初始化主要是对任务的一些全局变量做初始化。主要包括以下变量和数组:_tx_thread_current_ptr:当前正在运行的任务。_tx_thread_execute_ptr:下一个要执行的
11、任务。_tx_thread_priority_map:记录任务优先级的位置,每一位对应一位优先级。_tx_thread_highest_priority:就绪任务中最高的优先级值。_tx_thread_lowest_bit:优先级最低位映射表。_tx_thread_priority_list:就绪链表优先级链表。_tx_thread_created_ptr:创建的任务链表的头指针。_tx_thread_created_count:创建任务的数量。_tx_thread_preempt_disable:禁止抢占标志。具体含义详细解释见附录A。3.1 任务创建注意点:1、初始化任务的定时器超时入口函
12、数。这个入口函数很有作用,在定时器到时时会调用这个接口。所有的任务延时到时间时最终由定时器处理任务来调用。2、如果禁止抢占,则在调用tx_tcr.s 时判断如果禁止抢占,则直接从中断中退出,不会再发生抢占。3.2 任务超时处理函数在任务采用延时挂起时,定时器如果到期则调用该处理函数。该处理函数的入口参数就是任务自己的控制块。任务cleanup函数为任务挂起时相应导致任务挂起的模块,如信号量、消息队列等。3.3 任务挂起注意问题:1、任务正在挂起标志设置为TRUE代表任务正在挂起,任务还在就绪链表中,当被设置为FALSE的时候,已经从就绪链表中删除了此任务,在从链表中删除任务时,操作时是关闭中断
13、的,所以是不可能是被抢占的。2、因为任务要被挂起,所以获取下一个能被执行的任务,就是从就绪链表中获取下一个优先级最高的任务去执行。判断和被挂起的任务同一个优先级就绪链表中是否还有其它任务。如果这个链表中有其它任务存在并且挂起任务为链表的头,从就绪链表中删除此任务并直接把这个链表的头指针指向挂起任务的下一个任务,判断下个要执行的任务是否为挂起任务,如果是则获取下一个就绪链表中优先级最高的任务;如果不是链表的头就直接从链表中删除此任务就好了。如果同一个优先级的就绪链表中就只有一个任务,要做两个方面的工作:a、把此优先级的就绪链表的头指针指向为空。b、获取下一个要执行的任务。c、清楚挂起任务在优先级
14、位映射标志的对应位。d、获取优先级位映射标志。e、获取挂起任务所在的优先级组和优先级组中对应的数据,即这个优先级组对应的8位数据。获取优先级组对应的位数据的方法为优先级位映射表向右移动组号乘以8位,priority_group = (UINT) (priority_map TX_THREAD_GROUP_1);。如果系统只创建了一个任务,则挂起任务后,系统就会进入调度接口的死循环。f、获取任务就绪链表中最高优先级。g、判断下一个要执行的任务是否为挂起任务,如果不是则执行i。如果是则先利用最高优先级值获取下一个要执行的任务,然后再判断是否有阈值抢占的情况发生。h、如果没有则执行i,如果有,利用_
15、tx_thread_preempted_map值获取阈值抢占的最高优先级任务,平判断该任务的阈值和最高优先级值得大小。如果优先级大于等于阈值则设置下一个要执行的任务为阈值抢占发生时的任务。具体解释看_tx_thread_preempted_map变量解析。i、判断是否有抢占发生。有重新调度,没有则退出。3、_tx_thread_priority_map此标志是用来记录优先级对位的位的。如果就绪链表中同一个优先级还有其它任务,则此优先级对应位不用清除,如果链表中只有挂起任务自己就得清除此位。3.4 任务恢复注意问题:1、任务恢复接口主要是把挂起任务恢复到就绪状态。2、一般是在发生任务抢占的时候会
16、调用此接口。3、用于调用任务恢复接口。4、调用该接口前都会把禁止抢占标志加1操作,也就是禁止抢占。但是在该接口里面会设置完成是否发生抢占标志后进行减1操作,恢复抢占。3.5 任务睡眠睡眠只能在任务中调用。必须有时间片的支持。在定时器到时的时候会调用睡眠任务的超时处理函数,超时处理函数会判断是什么导致任务挂起计时,如果是睡眠就直接恢复任务。3.6 创建任务栈就是根据要保存的硬件资源主要是一些在被中断打断时必须要保存的寄存器在建立一个足够大小的任务栈空间。3.7 转换控制权就是如果发生了抢占当前任务挂起时就要把CPU控制权提交给调度接口重新去调度。注意:1、对于251平台而言,调用子函数时PC是自
17、动入栈的。执行ERET和RETI时恢复PC的顺序还不一样。本系统运行在中断中释放信号量,因此所有子函数调用也执行RETI,所以在此接口一进来,要重新修改PC的入栈顺序。2、如果任务挂起时,本身的时间片是0则不重新设置时间片,否则重新设置为新的时间片。就是说,任务如果时间片不为0,执行了一段时间后挂起恢复的时候时间片会重新开始。4任务同步4.1 互斥量互斥量具有的功能是优先级继承,永远只能有一个任务占有互斥量。一、注意:如果互斥量不支持优先级继承,线程获取时,如果获取成功就直接将互斥量的占有着记录为当前任务。否则直接挂起就可以了mutex_ptr - tx_mutex_suspension_li
18、st = thread_ptr - tx_suspended_next;。只有互斥量支持优先级继承的时候,任务的占有互斥量链表才进行保存互斥量。thread_ptr - tx_owned_mutex_list指向任务占有互斥量链表。因为优先级继承时要考虑占有任务优先级恢复问题,因为每一个互斥量都有可能使占有任务的优先级发生改变。二、重要接口描述_tx_mutex_priority_change互斥量优先级改变接口,把占有任务优先级改变为新的优先级_tx_mutex_put释放互斥量接口_tx_mutex_get获取互斥量接口4.1.1 获取互斥量1、 一个任务可以占用多个互斥量。2、 同一个互
19、斥量只能被同一个任务占有。3、 同一个任务可以多次占有同一个信号量。4、 互斥量的计数值为0时,方可被获取到。获取到后设置为1。如果被同一个任务多次获取后,则互斥量计数值做简单的加1操作。4.1.2 释放互斥量一、释放互斥量要考虑几个问题。5、 是否支持优先级继承。6、 恢复占有线程的优先级。7、 支持优先级继承和不支持的处理的区别。8、 占有互斥量的线程的处理和互斥量本身的处理。9、 线程什么时候恢复到原始优先级。二、回答:问题2:如果支持优先级继承,则要考虑该互斥量被释放后,该互斥量的占有任务要恢复的优先级。因为优先级已经被改变过了。又因为一个任务可能占有多个互斥量。因此在支持优先级的情况
20、下,占有任务要恢复到该任务剩余占有互斥量的最高等待优先级。互斥量的等待优先级是任务在互斥量时被挂起时设置的。问题3:支持优先级继承则要进行恢复占有任务的优先级,然后恢复在该互斥量挂起的任务, 同时使挂起任务中优先级最高的任务先被执行。不支持则不执行恢复优先级工作,直接恢复挂起任务,把挂起的任务按顺序恢复。问题5:占有任务的优先级,会先被恢复到剩余占有的互斥量的最高等待优先级,然后释放完所有占有的互斥量后,会被恢复到原始的优先级。三、工作原理:1、 先判断当前任务是否占有该互斥量并且该互斥量值大于0,如果不是就直接退出。可见,不占有该互斥量的任务不能释放互斥量,且互斥量等于0的时候也不能释放互斥
21、量。2、 满足1的条件,则将互斥量值减1操作。3、 判断减1后的互斥量值是否大于0,如果大于0说明该任务还要继续占有此互斥量,则简单的返回就可以了。4、 如果等于0,说明该任务释放互斥量后不再占有互斥量。则要进行相应的操作了。5、 如果等于0,则要判断该互斥量是否支持优先级继承。6、 如果支持优先级继承则进行下面的操作。如不支持优先级继承则从第7部开始执行。a、 占有任务占用的互斥量数值减1操作b、 判断任务占有的互斥量数量是否大于0c、 如果等于0,则直接把占有任务指向的占有的互斥量链表头指针设置为0。如果不为0,则把当前的互斥量节点互斥量链表中删除。同时修改头指针指向的位置。d、 获取占有
22、任务的原始优先级。禁止抢占,恢复中断。e、 获取占有任务占有的下一个互斥量。判断此互斥量的等待优先级是否高于占有任务的原始优先级。如果高于则将要恢复的任务优先级设置为当前的获取的互斥量的等待优先级。如此直到查询完毕。f、 禁止中断,恢复抢占。g、 互斥量挂起的任务数量是否大于1?如果是禁止抢占、恢复中断。调用按优先级排序挂起任务接口,保证优先级最高的挂起任务先被执行。禁止中断、恢复抢占。h、 如果小于等于1。按第7部开始执行。7、 判断互斥量挂起的任务数量是否为0?如果为0按下面步骤执行:不为0按照第8部开始执行。a、 如果为0,禁止抢占,恢复中断。b、 判断是否支持优先级继承,如果支持互斥量
23、的最高等待优先级设置为最低,同时把占有任务的优先级恢复到上面获取到得剩余互斥量的最高等待优先级。当然如果当前占有任务的优先级和要恢复的一样,就不用调用改变优先级接口,否则调用互斥量改变优先级接口。c、 禁止中断,恢复抢占。恢复中断。d、 判断是否会发生抢占,如果发生则重新调度。不能发生则返回。8、 如果不为0,则挂起的任务要被恢复到就绪列表中。要恢复的任务则相当于要占有刚才释放的互斥量,因此要进行类似获取互斥量的操作,要判断优先级是否继承。进行下面的操作。a、 获取挂起链表中的第一个任务。b、 判断该互斥量是否支持优先级继承。c、 如果支持优先级继承,保护互斥量的先前占有任务。互斥量记录恢复任
24、务的优先级和阈值。判断恢复任务占有的互斥量数量是否大于0。如果大于0,将互斥量插入互斥量链表的末尾。如果占有的互斥量等于0,则直接插入链表头,并且恢复任务保护自己的原始优先级和阈值。然后将恢复任务占有的互斥量数量加1,同时设置互斥量最高等待优先级为最低。然后从d开始执行。d、 如果不支持优先级继承,将互斥量的计数值设置为1。记录互斥量的拥有者。9、 判断挂起的任务数是否只有一个,如果只有一个设置互斥量挂起任务链表头指针为0。如果不只有一个,则从互斥量链表删除恢复任务节点。10、 设置cleanup为null11、 禁止抢占。恢复中断。12、 如果恢复任务启动了定时器停止定时器。13、 设置恢复
25、任务的挂起状态为success14、 检测互斥量是否支持优先级继承。如果支持优先级继承则做如下操作: a、互斥量挂起任务数量是否大于0?如果大于执行b。等于0执行d。 b、检测任务挂起的数量是否大于1,大于1则调用互斥量优先级排序接口改变优先级。就是要把优先级最高的任务放置到排序链表头位置。然后向下执行c。c、如果等于1,禁止中断。判断互斥量的挂起任务是否为0,如果不为0,则设置互斥量最高等待优先级为挂起任务的第一个任务。因为第一个任务已经调用互斥量优先级排序接口,其优先级已经被设置为最高。恢复中断。d、恢复占有任务的优先级,恢复到占有任务占有的剩余互斥量中等待优先级最高的优先级。15、恢复任
26、务,是否发生抢占。16、发生抢占,则重新调度。17、不发生抢占,返回结束。.4.1.3互斥量优先级改变接口这个接口会在优先级继承时用到,改变占有任务的优先级,主要是任务获取互斥量时若被挂起且挂起任务优先级高于占有任务,则要改变占有任务的优先级到挂起任务。任务释放互斥量时,占有任务要恢复到原始优先级或者是占有任务占有的剩余互斥量的最高等待优先级时,也会调用此接口。 此接口需要注意的几点问题:1、要看要改变的任务状态是否处于就绪状态。2、如果任务不处于就绪态,直接改变其优先级即可。3、如果任务处于就绪态,则此任务要从就绪态中删除,同时设置下一个要执行的任务。如果和该任务处于同一个优先级的任务有多个
27、则这个任务将会被恢复到同一个优先级链表的末尾。如果该任务优先级链表中只有一个任务,则此任务会被恢复到同一个优先级链表的头位置。4、此函数接口不会导致任务发生抢占。只是会改变下一个要被执行的任务,当然下一个被执行的任务有可能是刚刚被改变优先级完成后的任务,因为最后还要调度任务恢复接口。5、如果当前运行的任务是被改变优先级的任务,则此任务会在执行完成后去执行其它的任务,不会马上执行别的任务。会继续向下执行。6、总之,要改变的任务在就绪链表中要先被从链表中删除,然后改变其优先级。最后再恢复这个任务。在这个过程中要从新设置下一个要被执行的任务。如果当前正在执行的任务是要被改变优先级的任务,即使优先级被
28、改低,当前任务也会被继续执行,不会发生抢占。因为改变任务优先级的接口没有去判断是否抢占,还是继续执行当前任务。4.2 计数信号量4.2.1 获取计数信号量一、注意问题:1、信号量计数值大于0,就可以获取到。2、信号量计数值等于0,则不能获取到,获取的任务就要被挂起。3、在中断中释放信号量问题:当一个任务获取信号时,如果不能获取到则要被挂起,在执行挂起过程中,中断是被禁止的。挂起完成后,中断被恢复,此时可以被中打断。在tx_tsus.c文件中,挂起完成后,执行下面的操作:/* Check for a preemption condition that might have occurred fr
29、om an ISR. */if (_tx_thread_current_ptr != _tx_thread_execute_ptr) & (_tx_thread_system_state = 0) /* Return control to the system. */ _tx_thread_system_return(); 在执行上面的代码过程中可能被中断打断。此时被中断打断在执行上下文恢复的过程中就会发生任务抢占,会去执行if内部的语句去执行另外一个任务,因为_tx_thread_current_ptr 和 _tx_thread_execute_ptr的值不一样,但是上下文恢复完后,调用任务
30、调度接口的时候上面的两个值又被设置成了一样。这样在中断中释放信号量的时候,被挂起的任务又被恢复。同理_tx_thread_current_ptr 和 _tx_thread_execute_ptr的值最终也被设置成了一样。所以被恢复的任务将不会再执行if语句内部的程序。继续执行被挂起的任务,且获取信号量成功。If语句内部的程序是切换到另外一个任务去执行。正常的情况下如果任务挂起时不被中断打断就会执行if内部语句去切换到另外一个任务。但是被中断打断了,恢复任务上下文的时候就会切换到另外一个任务去了。即,条件发生了变化,再次发生释放信号量的时候,就继续执行被挂起的任务了。4.2.2 释放计数信号量4
31、.3 事件标志组4.3.1 事件标志组创建4.3.2 事件标志组设置4.3.3 事件标志组获取5任务通信任务通信只采用了消息队列方式实现任务间通信。注意:1、向消息已满的队列发送休息数据时,任务会刮起。2、从消息为空的队列中获取消息数据时,任务会刮起。3、支持数据发送1、2、4、8、16字(32位)大小的元素,源码中定义sizeof(ULONG)大小来做为一个字的大小。4、如果传送的数据太大可以传送一个指针方式。5.1消息队列初始化只是初始化两个全局变量。_tx_queue_created_ptr = TX_NULL;_tx_queue_created_count = 0;5.2消息队列创建1
32、源码中的capacity是说明消息队列能发送多少个指明消息大小的队列元素。2消息队列的大小只用消息元素大小的整数倍,多余的剩余字节则不会被使用,所以创建消息队列的时候要指定消息队列的大小为消息元素大小的整数倍。不要有多余的浪费。消息队列大小指定的是字节数,消息元素的大小为字(32位)大小,消息队列大小应为消息元素大小*32的整数倍。3创建的队列是一个循环队列,按顺序写和读取。4写消息队列的时候如果已经写满了,这时要写数据的地址tx_queue_write又更新到了队列的起始地址,而此时如果有写任务挂起的时候写队列的地址tx_queue_write是不发生变化的。则在任务接收队列数据的时候,如果
33、发现有挂起的任务等待去写数据则会把写挂起任务的源数据地址和写的地址获取到。读任务执行时,会先读走一个元素数据,然后把数据写到对应的空间去。5队列的满与空是通过一个标志tx_queue_available_storage来判断的,tx_queue_write和tx_queue_read的指向相等时队列可能是空也可能是满。6写队列和读队列都是指向要写的空间和要读的空间,读和写完成后相应的指针都要做加加操作。tx_queue_write永远指向空,队列没有满之前,其后一个的空间都是空的。tx_queue_read永远指向有数据的空间,队列未满之前其前一个空间一定是空的,因为已经读取过。5.3消息队列
34、发送数据注意:1队列不可能既有发送任务挂起又有接收任务挂起。因为二者是矛盾的,因为发送任务挂起是在队列满时,而接收任务挂起是在队列空时。队列不可能是即空又满。2所以队列发送和接收的成员挂起标志使用的是同一个。3发送队列里面判断是否有挂起任务是指是否有任务正等着从队列中接收数据。4同理,接收数据时判断是否有挂起任务是指是否有任务正等着向队列写数据。5对于3和4的操作,进行判断是否有任务挂起时已经判断队列是否已满或者为空,然后才可能执行3和4的操作。6thread_ptr - tx_additional_suspend_info这个是在发送或者接收消息队列时任务挂起时会把目的地址和源地址赋值给此成
35、员变量,带任务恢复的时候会引用此值。在任务接收消息队列挂起的时候,就是把任务从消息队列中获取到的数据要存放的目的地址保留下来,保留到此成员变量中,待有任务发送数据的时候,会直接把数据赋值到挂起任务保存的目的地址中去。然而发送任务要挂起的时候是把源数据地址要保存到此变量成员中。5.4消息队列接收数据5.5消息队列发送数据到队列首部注意:1如果发送数据时队列已满,并且任务不要求直接挂起则直接返回队列已满状态。如果是在当前队列已经满的情况下操作则任务必须挂起,当然该数据也是要被先读取到,挂起任务后待被恢复时临时BUF中数据会被放回到队列中。挂起的任务要放置到挂起任务链表的头位置。2如果队列已经满了,
36、且要求挂起,则会提供一个临时buf把写空间的数据拷贝到临时buf。这样就腾出了一个空间去写消息元素数据。3这样判断队列是否已满的情况可能有两种情况,1个是队列本身没有满,另外一个是队列满了,但是通过开辟一个临时buf,从队列中腾出一个可用空间出来。4此函数的功能是把数据放置到让读任务能先读取的位置,并不是放置到队列的起始位置。 5如果已经有挂起任务等待去读数据,则队列肯定是为空,则直接把数据写到队列就可以了,不用考虑情况。6中断管理中断管理主要负责任务上下文保护和恢复。251平台处理流程:注意:1、中断嵌套发生时系统的处理。不同的硬件平台需要不同的处理。对于251平台只允许在中断处理程序的时候
37、被打断(即发生嵌套),所以保护任务上下文的时候,如果是嵌套发生时就不做任何操作只是简单的eret操作。这样没有改变SP。发生嵌套时就不要在进行SP切换了。2、当任务都挂起时进入死循环的时候,发生中断时该如何处理。就这种情况引发的问题在总结心得中已经详细说明。那么如何解决这个问题呢。解决方法入下:1、任务都挂起时,则系统进入了死循环,此时的SP为最后一个任务的栈空间。2、中断中判断是空闲任务时就不要在调用任务调度接口了。3、任务保护时是空闲任务就直接eret就OK了。4、sp切换时要判断是否为空闲任务如果是就不进行切换了。5、任务恢复就直接出栈就OK了。这样就不用再建立一个空闲任务了。6.1 任
38、务上下文保护就是在发生中断时,先进行保护cpu需要进行运行的寄存器。每个任务被打断的时候,保护到当前任务的栈中。也可以只有一个系统栈,但是任务中必须提供一个保护系统栈中数据的空间。中断发生时要把系统栈中的数据拷贝到任务栈中。栈拷贝的方法比较占用时间(251移植时栈拷贝的时间为30us,系统时钟为16MHz),最好使用切换SP的方法。在251平台中只允许在中断处理程序中和THREADX初始化的时候使用系统SP,其它时间只使用任务提供的SP。251中PC是不能被访问的,调用一个子函数的时候,系统会自动把PC入栈。而mips没有把PC自动入栈的程序,在调用一个子函数的时候,系统根据编译器编译的地址经
39、过计算找到子程序的地址利用调用子函数指令去调用子函数,期间是没有进行子函数的地址入栈操作。就是执行ECALL指令251系统会自动入栈,执行jal指令mips是不会自动入栈的。所以在mips上移植的时候保护上下文操作就不用考虑入栈减3的操作了,而251和51就必须的考虑了。下面的流程图就以251为硬件平台。6.2 任务上下文恢复注意:1、任务上下文恢复要判断是否有抢占要发生,有则要进行重新调度。2、如果当前系统没有任务在运行也要进行重新调度。7时钟管理8内存管理总结心得1.任务调度死循环1 如果系统只创建了一个任务,并且这个任务获取一个信号量时,如果信号量不能被获取,则该任务被阻塞。系统会挂该任
40、务,且去获取下个最高优先级的任务区执行。但是现在任务只有一个,系统会在_tx_thread_system_return和_tx_thread_context_restore接口中把变量_tx_thread_current_ptr值清零,且_tx_thread_execute_ptr的值在调用任务挂起接口的时候获取到的数据为0。则在任务调度接口时,就停在了死循环当中。要等着任务恢复就绪才能被执行,但是这种死循环的导致是在任务执行的过程中导致的,还是可以通过中断的方式恢复的。2 中断中进入了死循环。中断调度进入死循环是因为。系统只创建了一个任务,且去获取信号量,此时子挂起。任务调度进入死循环。此时
41、如果一直没有释放信号量则一直处于调度死循环状态。这时如果产生中断,中断处理函数执行完处理中断上下文恢复时,也要去调用任务调度接口函数。这时依然会处于死循环状态。解决方案:1、添加一个空闲任务使任务调度永远不会进入死循环。2、中断中恢复任务上下文接口时,判断_tx_thread_current_ptr接口值是否为0,为0表示当前没有任务在运行,就不要再去调用任务调度接口了。直接退出中断就可以了。这样任务调度继续在空运行,但是可以产生中断了。但是THREADX在MIPS上的实现确是恢复任务调度接口时不管结果是什么,最后都要去调度任务调度接口。在251上这样操作是有问题的。2.在中断里面释放信号量等
42、操作(使任务恢复) 之前一直想不明白在中断里面释放信号量的操作,认为有问题。就是中断中你释放一个信号量,有可能发生抢占。发生抢占就直接调用_tx_thread_system_return接口。如下代码: if (_tx_thread_resume(thread_ptr) /* Preemption is required, transfer control back to system. */ _tx_thread_system_return();则在中断执行一半的时候就直接执行任务调度去了,且出栈的返回类型应该为任务栈类型。信号量挂起时为任务栈类型。这样岂不是出了问题:1、 中断执行完的时候
43、,又要去调度,此时栈类型为任务栈类型。中断中执行了任务栈退出。2、 中断执行一半的时候就去执行其它任务了。第二个问题应该不村子,因为在释放信号量的时候,_tx_thread_resume(thread_ptr)不会发生抢占,因为_tx_thread_system_state变量在发生中断的时候会加1操作,_tx_thread_resume会判断_tx_thread_system_state值不为0而不会发生抢占。第一个问题是怎么解决的呢?针对第一个问题,经过查看代码和测试确实存在这个问题。要想解决这个问题必须把所以的任务入栈操作都改成RETI(251)类型。修改keil编译器使RETI按照四个
44、字节操作,把eret类型操作的PC改成RETI方式。3.优先级继承互斥量支持优先级继承。所谓优先级倒置是指高优先级的任务被低优先级的任务先执行,而高优先级的任务后执行。T1、T2、T3 3个任务,T1优先级最高、T2次之、T3、最低。T1和T3拥有同一个共享资源。T3先运行并占用了资源,T1就绪,T1优先级高打断了T3去运行,也要获取同一个共享资源,阻塞挂起。T3继续运行,T2就绪、T2优先级高于T3,T2抢占T3去运行。T2执行完之后,T3执行,T3释放信号量,T1才运行。这样T2先与T1去运行。解决方案就是不让T2先于T1去运行。就是T1去获取信号量的时候,把占有这个信号量的线程T3的优先
45、级提高和T1的优先级一样高,这样比T1优先级低的任务就不会被执行。THREADX支持优先级继承(mutex)。在获取互斥信号量的时候如果互斥信号量不能被获取到,则该任务就会被挂起。同时要检测该互斥量创建的时候是否支持优先级继承。如果支持,则要判断当前任务优先级是否比占有该互斥两的任务的优先级要高,如果要高则要把占有互斥量的任务的优先级升级到和当前任务的优先级一样高。当然一个任务可能会占有多个互斥量,则就有可能更多的任务去获取该互斥量而被阻塞,同样要改变占有互斥量任务的优先级,则占有互斥量任务的优先级可能被多次修改。释放互斥量的时候,则要恢复线程的原始优先级。问题:一个线程可以占有多个互斥量,一
46、个互斥量也可以被多个线程去获取。那么这个线程的优先级可能被修改多次。最后修改到优先级最高的那个线程的优先级。那么恢复这个线程的优先级是如何做的呢?当一个线程占用多个互斥量时,线程占用的一个互斥量要被释放时,则这个互斥量从任务占用的互斥量链表中删除。同时要把这个任务的优先级恢复到任务占用的剩余互斥量的等待的最高优先级。就是这个值tx_mutex_highest_priority_waiting。这个保存了占有互斥量的任务被改变后的优先级,即改变到被挂起任务的优先级。4.可重入reentrantKeil对于251内核函数的局部变量是不进行压栈操作的,所有要求可重入操作的函数,都要加此关键字。包括任
47、务入口也要加此关键字操作。附录A THREADX变量含义_tx_timer_current_ptr1、TIMER_DECLARE TX_INTERNAL_TIMER *_tx_timer_current_ptr:二级指针,指向定时器当前被定时的定时器列表。每次时间片来时,如果该定时器没有到期则在timer中断处理函数中将其值加1,要是定时器到期了则定时器任务处理中将此值加1。TIMER_DECLARE TX_INTERNAL_TIMER* _tx_timer_listTX_TIMER_ENTRIES;_tx_timer_current_ptr = &_tx_timer_list0;_tx_ti
48、mer_list0 指向了定时器列表。la $9, _tx_timer_current_ptr # Pickup address of current ptr#取二级指针本身的地址lw $8, ($9) # Pickup current pointer#取二级指针本身的值,即指针数组的地址&_tx_timer_list0;la $13, _tx_timer_expired # Pickup address of timer expired flaglw $10, ($8) # Pickup the current timer entry#取指针数组本身的值,即指向了定时器列表。ori $12,
49、 $0, 1 # Build TX_TRUE flagbeqz $10, _tx_timer_no_timer # If NULL, no timer has expired_tx_timer_system_clock2、TIMER_DECLARE ULONG _tx_timer_system_clock:系统时钟每次tick时加1。_tx_timer_time_slice3、TIMER_DECLARE ULONG _tx_timer_time_slice:当前线程的时间片值_tx_timer_expired4、TIMER_DECLARE UINT _tx_timer_expired:定时器过
50、期标志_tx_timer_time_slice5、TIMER_DECLARE ULONG _tx_timer_time_slice:当前线程的时间片值_tx_timer_created_ptr6、TIMER_DECLARE TX_TIMER *_tx_timer_created_ptr; 永远指向TX_TIMER列表的头_tx_timer_list_start7、TIMER_DECLARE TX_INTERNAL_TIMER *_tx_timer_list_start; 指向_tx_timer_list0的地址_tx_timer_list_end8、TIMER_DECLARE TX_INTER
51、NAL_TIMER *_tx_timer_list_end; 指向_tx_timer_listTX_TIMER_ENTRIES的地址_tx_timer_list9、TX_INTERNAL_TIMER* _tx_timer_listTX_TIMER_ENTRIES;激活定时器数组_tx_thread_preempt_disable10、THREAD_DECLARE volatile UINT _tx_thread_preempt_disable; 此值为非0时,代表抢占式不允许的。内部函数会调用,在任务切换函数_tx_thread_time_slice中会使用此值。_tx_thread_syst
52、em_state11、THREAD_DECLARE volatile ULONG _tx_thread_system_state:中断函数中判断是否有中断嵌套,大于0就有中断嵌套。系统初始化时先赋值为TX_INITIALIZE_IN_PROGRESS初始化完之后就被赋值为0,用来做为是否有嵌套。_tx_thread_preempt_disable12、THREAD_DECLARE volatile UINT _tx_thread_preempt_disable;禁止抢占标志,大于0不允许被抢占。任务切换函数中会判断此值是否大于0,大于则再给当前任务一个tick时间片,再给一次尝试机会。在恢复任
53、务上下文的时候会判断此标志。_tx_thread_current_ptr13、THREAD_DECLARE TX_THREAD * _tx_thread_current_ptr;正在执行线程控制块的指针,此值在调度时被赋值。_tx_thread_execute_ptr14、THREAD_DECLARE TX_THREAD * _tx_thread_execute_ptr;下一个要执行线程控制块的指针,此值在任务执行过程中随时可能被赋值,同时在中断恢复时会用此值和tx_thread_current_ptr比较以判断是否有更高有限级的任务就绪。时间片计数值到时会改变tx_thread_curren
54、t_pt的值,则在恢复任务上下文时会比较二者的值,不一样则进行重新调度。_tx_thread_created_ptr15、THREAD_DECLARE TX_THREAD * _tx_thread_created_ptr; 线程控制块TCB双向列表的头指针,此值永远指向TCB链表的头。_tx_thread_priority_map16、THREAD_DECLARE ULONG _tx_thread_priority_map;就绪任务的优先级在_tx_thread_priority_map相应位置位。在_tx_thread_suspend接口中获取下一个高优先级任务时,会利用此值。_tx_thr
55、ead_preempted_map17、THREAD_DECLARE ULONG _tx_thread_preempted_map;当恢复任务时如果恢复的任务的优先级高于当前正在执行的任务的优先级,则会发生抢占。先判断_priority tx_preempt_threshold如果小于则可以发生抢占。如果被抢占函数的优先级和抢占阈值不同则会把_tx_thread_preempted_map和优先级对应的位置1。任务被抢占后,还是在就绪链表中。如果恢复任务的时候,恢复的任务的优先级值比发生阈值抢占时的任务的阈值要高的话,则改为恢复阈值抢占的任务。在调用任务挂起接口的时候,如果挂起任务的优先级就绪链表中只有挂起任务一个任务,则要重新从就绪链表中获取下一个优先级最高的任务,从就绪任务优先级映射表中获取最高优先级值。然后判断下一个执行的任务是否为当前要挂起的任务,如果是则要重新获取下一个要执行的任务。首先,
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 外市电安全生产责任制度
- 气化器安全生产责任制度
- 商务局酒类安全责任制度
- 审批服务便民化责任制度
- 民爆安全生产责任制度
- 社保所安全生产责任制度
- 美发岗位卫生责任制度
- 蔬菜办安全生产责任制度
- 审计组成员工作责任制度
- 建筑企业保管员责任制度
- 2026年露天矿山复工复产试卷
- 2026广东广州市中级人民法院招募就业见习人员25人考试参考题库及答案解析
- 2026年扎兰屯职业学院单招职业技能考试题库含答案解析
- 2026年江西旅游商贸职业学院单招职业适应性测试题库含答案解析
- 2026吉林农业大学三江实验室办公室招聘工作人员考试参考题库及答案解析
- 2023年12月英语四级真题及答案-第3套
- 2026年内蒙古商贸职业学院单招职业技能测试题库带答案详解(考试直接用)
- 高职高专学生心理健康教育 第四版 课件 第第五讲 相伴适应路
- 心血管疾病健康知识科普
- 农副产品营销培训课件
- 装饰工程施工质量方案
评论
0/150
提交评论