THREADX操作系统各模块详解第一部分.docx
- 文档编号:24294844
- 上传时间:2023-05-26
- 格式:DOCX
- 页数:68
- 大小:850.41KB
THREADX操作系统各模块详解第一部分.docx
《THREADX操作系统各模块详解第一部分.docx》由会员分享,可在线阅读,更多相关《THREADX操作系统各模块详解第一部分.docx(68页珍藏版)》请在冰豆网上搜索。
THREADX操作系统各模块详解第一部分
THREADX深入学习
简介
最近在做THREADX移植项目,所以在开始学习THREADX操作系统。
想把自己学到的东西总结一下。
学习操作系统时,按照领导的意思把操作系统进行模块划分。
通过查找资料将操作系统划分为任务调度模块、任务管理模块、任务间同步和通信模块、内存管理模块、中断管理模块、时钟管理模块。
下面将分别对各个模块进行分析和研究。
我将深入介绍各个模块的工作原理,通过此文档能对操作系统的工作原理有深入的了解。
首先得我的分析是针对MIPS、ARM、251内核进行分析。
我移植的平台是16位的251平台。
个人认为移植一个操作系统,首先对操作系统的内核调度原理必须十分清楚,然后对你的移植平台架构、指令集也要十分清楚,比如说下面几个方面:
1、子程序调用时PC值是怎么被保存得(MPIS,将子程序的返回值存放在了RA寄存器中,251是PC自动入栈(ECALL指令)退出时使用ERET等指令,ARM是在LR寄存器中要计算相应减去的数值)。
2、中断发生时(251PC自动入栈但顺序和子程序调用压入顺序不一样,中断返回使用RETI指令。
MIPS,PC是被存入了EPC寄存器中,使用eret指令。
ARM,LR中数值的计算,赋值给PC即可)
2.任务调度
操作系统的核心模块就是内核调度。
首先要弄清楚其调度原理。
带着下面几个问题去思考。
1、任务入口函数第一次是怎么被执行的。
2、任务是怎么被切换的。
3、任务是怎么被抢占的。
以上几个问题是任务调度的核心。
带着这几个问题去看内核源码发现任务调度使用的方法就是任务栈和系统栈,内核利用入栈和出栈完成对任务的调度和切换。
而任务被调度起来是依靠timer驱动来工作。
基于此分析可以得出内核调度重点是以下几个方面:
1、明白任务栈的构建方式,即任务创建时初始化任务堆栈时保存的数据。
这些数据要根据具体的硬件平台去实现,这个栈的初始化就是解决上面的第一个问题的。
因为在内核调度时,任务第一次被执行是出此栈来执行对应的入口函数的。
对于栈我们要明白任务栈和系统栈的区别,要针对不同的硬件平台而做不同的设计。
任务栈有两种类型,一种叫做中断类型栈,是在产生中断保存任务上下文到任务栈空间的数据,其出栈方式是要利用中断返回时的出栈方式;另外一种栈叫做用任务栈是任务在执行过程中自挂起时需要把CPU控制权重新交给调度器的时候需要把当前的任务上下文进行保护,在任务出此栈的时候要用子程序返回的方式去出栈。
系统栈主要系统内核自己需要使用的栈。
移植过程中我们可能要不断的切换SP在这两个栈之间。
也可能只用系统的SP,不切换只是来回的复制两个栈空间的内容(如51系列)。
这里移植的时候可能会出现以下几个问题:
a、TIMER中断产生后,任务调度任务第一次执行出栈时,是按照子程序返回的方式去出栈。
这样中断将永远不会被返回。
这样就有问题了,这个在任务切换的时候经常遇到就是任务入口函数第一次被执行的时候。
解决的办法是初始化任务栈的时候初始化成中断类型的栈。
b、要明白硬件平台的压栈方式。
发生中断时的压栈方式和子程序调用时的压栈方式是不同的。
像MIPS这样产生中断和子程序调用时都有相应的寄存器保存相应的值,这样的平台实现起来比较简单。
而251平台确没有,251不管是中断还是子程序调用都是把PC值压入栈中,而且压入得顺序也不一样。
(251中产生中断和子程序调用时PC值入栈的顺序是不一样的,PC分三次入栈,见具体的芯片手册)
c、任务的出栈方式。
任务出栈是在任务调度后恢复优先级最高的任务时执行的,先判断任务栈类型然后采用相应的出栈方式。
251平台任务栈和中断栈保护的数据一样,但出栈的方式也就是子程序返回的方式是RETI和ERET。
子程序调用可以采用RETI而中断产生时不能采用ERET否则会中断一直无法返回。
d、入栈的方式:
对于mips把相应的寄存器数据压入到任务栈就OK了。
而251平台是系统自动压栈,出栈的时候要出对应的PC值才OK。
因此在产生中断时先进行入栈操作,然后调用子程序。
由于是在子程序中把栈数据拷贝到任务栈中,因此要把SP减去相应的值,要看子程序调用压入了几个数据。
ECALL是减3。
切记不要在任务栈中存入额外的数据。
否则出栈就出错了。
2、TIMER产生中断执行timer中断处理函数,timer中断会对当前任务的时间片进行计时,时间到期后会调用任务切换函数,切换下一个更高优先级的任务。
任务切换函数只是把当前任务切换到同一个优先级列表的最后,并没有去执行下一个任务。
这个工作交给了任务上下文恢复函数,此函数会比较当前正在执行的任务和下一个要执行的任务控制块指针。
如果两个控制块指针的值不一样就要去回到调度接口去重新调度了。
正好上面的2问题。
3、任务在执行过程中释放信号量、中断等操作时使一个更高优先级任务就绪时就会发生抢占。
此时当前任务上下文会被保存同时高优先级任务的控制块指针会赋值给更高下一个要被执行的控制块指针。
如果是中断产生的下面的操作如同2中的一样。
如果是任务间同步等操作导致则会调用转交CPU控制权到调度接口去重新调度。
这样更高优先级的任务就被执行了。
这个就是上面的3问题。
4、任务调度流程图。
力求详细而明了。
图1是任务调度流程图。
图1.任务调度流程图
2.1任务调度接口
图2.是THREADX操作系统函数总体框架示意图。
图2.THREADX操作系统函数总体框架示意图
2.1.1调度器接口
1、_tx_thread_schedule(调度器)
函数原形
Void_tx_thread_schedule(void)
返回值
无
参数
无
说明
调度任务去执行
调度器接口主要负责从就绪列表中获取优先级最高的任务去执行。
2.1.2TIMER中断接口
2、_tx_timer_interrupt(timer中断处理函数)
函数原形
Void_tx_timer_interrupt(void)
返回值
无
参数
无
说明
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(ULONGtimer_thread_input)
返回值
无
参数
ULONGtimer_thread_input任务处理参数
说明
定时器任务处理函数
3.任务管理
任务管理主要有任务创建、任务挂起、任务恢复、睡眠、等接口组成。
一、注意问题:
1、就绪列表的组成以及获取下一个可以执行的最高优先级任务的方式。
THREADX支持的优先级为32个,任务个数为任意,视具体资源而定。
具有一32个元素的就绪链表,是按照任务优先级进行排序存放的。
还有一个最低位映射表thread_lowest_bit。
3.1任务初始化
任务初始化主要是对任务的一些全局变量做初始化。
主要包括以下变量和数组:
_tx_thread_current_ptr:
当前正在运行的任务。
_tx_thread_execute_ptr:
下一个要执行的任务。
_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、初始化任务的定时器超时入口函数。
这个入口函数很有作用,在定时器到时时会调用这个接口。
所有的任务延时到时间时最终由定时器处理任务来调用。
2、如果禁止抢占,则在调用tx_tcr.s时判断如果禁止抢占,则直接从中断中退出,不会再发生抢占。
3.2任务超时处理函数
在任务采用延时挂起时,定时器如果到期则调用该处理函数。
该处理函数的入口参数就是任务自己的控制块。
任务cleanup函数为任务挂起时相应导致任务挂起的模块,如信号量、消息队列等。
3.3任务挂起
注意问题:
1、任务正在挂起标志设置为TRUE代表任务正在挂起,任务还在就绪链表中,当被设置为FALSE的时候,已经从就绪链表中删除了此任务,在从链表中删除任务时,操作时是关闭中断的,所以是不可能是被抢占的。
2、因为任务要被挂起,所以获取下一个能被执行的任务,就是从就绪链表中获取下一个优先级最高的任务去执行。
判断和被挂起的任务同一个优先级就绪链表中是否还有其它任务。
如果这个链表中有其它任务存在并且挂起任务为链表的头,从就绪链表中删除此任务并直接把这个链表的头指针指向挂起任务的下一个任务,判断下个要执行的任务是否为挂起任务,如果是则获取下一个就绪链表中优先级最高的任务;如果不是链表的头就直接从链表中删除此任务就好了。
如果同一个优先级的就绪链表中就只有一个任务,要做两个方面的工作:
a、把此优先级的就绪链表的头指针指向为空。
b、获取下一个要执行的任务。
c、清楚挂起任务在优先级位映射标志的对应位。
d、获取优先级位映射标志。
e、获取挂起任务所在的优先级组和优先级组中对应的数据,即这个优先级组对应的8位数据。
获取优先级组对应的位数据的方法为优先级位映射表向右移动组号乘以8位,
priority_group=(UINT)(priority_map>>TX_THREAD_GROUP_1);。
如果系统只创建了一个任务,则挂起任务后,系统就会进入调度接口的死循环。
f、获取任务就绪链表中最高优先级。
g、判断下一个要执行的任务是否为挂起任务,如果不是则执行i。
如果是则先利用最高优先级值获取下一个要执行的任务,然后再判断是否有阈值抢占的情况发生。
h、如果没有则执行i,如果有,利用_tx_thread_preempted_map值获取阈值抢占的最高优先级任务,平判断该任务的阈值和最高优先级值得大小。
如果优先级大于等于阈值则设置下一个要执行的任务为阈值抢占发生时的任务。
具体解释看_tx_thread_preempted_map变量解析。
i、判断是否有抢占发生。
有重新调度,没有则退出。
3、_tx_thread_priority_map此标志是用来记录优先级对位的位的。
如果就绪链表中同一个优先级还有其它任务,则此优先级对应位不用清除,如果链表中只有挂起任务自己就得清除此位。
3.4任务恢复
注意问题:
1、任务恢复接口主要是把挂起任务恢复到就绪状态。
2、一般是在发生任务抢占的时候会调用此接口。
3、用于调用任务恢复接口。
4、调用该接口前都会把禁止抢占标志加1操作,也就是禁止抢占。
但是在该接口里面会设置完成是否发生抢占标志后进行减1操作,恢复抢占。
3.5任务睡眠
睡眠只能在任务中调用。
必须有时间片的支持。
在定时器到时的时候会调用睡眠任务的超时处理函数,超时处理函数会判断是什么导致任务挂起计时,如果是睡眠就直接恢复任务。
3.6创建任务栈
就是根据要保存的硬件资源主要是一些在被中断打断时必须要保存的寄存器在建立一个足够大小的任务栈空间。
3.7转换控制权
就是如果发生了抢占当前任务挂起时就要把CPU控制权提交给调度接口重新去调度。
注意:
1、对于251平台而言,调用子函数时PC是自动入栈的。
执行ERET和RETI时恢复PC的顺序还不一样。
本系统运行在中断中释放信号量,因此所有子函数调用也执行RETI,所以在此接口一进来,要重新修改PC的入栈顺序。
2、如果任务挂起时,本身的时间片是0则不重新设置时间片,否则重新设置为新的时间片。
就是说,任务如果时间片不为0,执行了一段时间后挂起恢复的时候时间片会重新开始。
4.任务同步
4.1互斥量
互斥量具有的功能是优先级继承,永远只能有一个任务占有互斥量。
一、注意:
如果互斥量不支持优先级继承,线程获取时,如果获取成功就直接将互斥量的占有着记录为当前任务。
否则直接挂起就可以了
mutex_ptr->tx_mutex_suspension_list=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、同一个互斥量只能被同一个任务占有。
3、同一个任务可以多次占有同一个信号量。
4、互斥量的计数值为0时,方可被获取到。
获取到后设置为1。
如果被同一个任务多次获取后,则互斥量计数值做简单的加1操作。
4.1.2释放互斥量
一、释放互斥量要考虑几个问题。
5、是否支持优先级继承。
6、恢复占有线程的优先级。
7、支持优先级继承和不支持的处理的区别。
8、占有互斥量的线程的处理和互斥量本身的处理。
9、线程什么时候恢复到原始优先级。
二、回答:
问题2:
如果支持优先级继承,则要考虑该互斥量被释放后,该互斥量的占有任务要恢复的优先级。
因为优先级已经被改变过了。
又因为一个任务可能占有多个互斥量。
因此在支持优先级的情况下,占有任务要恢复到该任务剩余占有互斥量的最高等待优先级。
互斥量的等待优先级是任务在互斥量时被挂起时设置的。
问题3:
支持优先级继承则要进行恢复占有任务的优先级,然后恢复在该互斥量挂起的任务,同时使挂起任务中优先级最高的任务先被执行。
不支持则不执行恢复优先级工作,直接恢复挂起任务,把挂起的任务按顺序恢复。
问题5:
占有任务的优先级,会先被恢复到剩余占有的互斥量的最高等待优先级,然后释放完所有占有的互斥量后,会被恢复到原始的优先级。
三、工作原理:
1、先判断当前任务是否占有该互斥量并且该互斥量值大于0,如果不是就直接退出。
可见,不占有该互斥量的任务不能释放互斥量,且互斥量等于0的时候也不能释放互斥量。
2、满足1的条件,则将互斥量值减1操作。
3、判断减1后的互斥量值是否大于0,如果大于0说明该任务还要继续占有此互斥量,则简单的返回就可以了。
4、如果等于0,说明该任务释放互斥量后不再占有互斥量。
则要进行相应的操作了。
5、如果等于0,则要判断该互斥量是否支持优先级继承。
6、如果支持优先级继承则进行下面的操作。
如不支持优先级继承则从第7部开始执行。
a、占有任务占用的互斥量数值减1操作
b、判断任务占有的互斥量数量是否大于0
c、如果等于0,则直接把占有任务指向的占有的互斥量链表头指针设置为0。
如果不为0,则把当前的互斥量节点互斥量链表中删除。
同时修改头指针指向的位置。
d、获取占有任务的原始优先级。
禁止抢占,恢复中断。
e、获取占有任务占有的下一个互斥量。
判断此互斥量的等待优先级是否高于占有任务的原始优先级。
如果高于则将要恢复的任务优先级设置为当前的获取的互斥量的等待优先级。
如此直到查询完毕。
f、禁止中断,恢复抢占。
g、互斥量挂起的任务数量是否大于1?
如果是禁止抢占、恢复中断。
调用按优先级排序挂起任务接口,保证优先级最高的挂起任务先被执行。
禁止中断、恢复抢占。
h、如果小于等于1。
按第7部开始执行。
7、判断互斥量挂起的任务数量是否为0?
如果为0按下面步骤执行:
不为0按照第8部开始执行。
a、如果为0,禁止抢占,恢复中断。
b、判断是否支持优先级继承,如果支持互斥量的最高等待优先级设置为最低,同时把占有任务的优先级恢复到上面获取到得剩余互斥量的最高等待优先级。
当然如果当前占有任务的优先级和要恢复的一样,就不用调用改变优先级接口,否则调用互斥量改变优先级接口。
c、禁止中断,恢复抢占。
恢复中断。
d、判断是否会发生抢占,如果发生则重新调度。
不能发生则返回。
8、如果不为0,则挂起的任务要被恢复到就绪列表中。
要恢复的任务则相当于要占有刚才释放的互斥量,因此要进行类似获取互斥量的操作,要判断优先级是否继承。
进行下面的操作。
a、获取挂起链表中的第一个任务。
b、判断该互斥量是否支持优先级继承。
c、如果支持优先级继承,保护互斥量的先前占有任务。
互斥量记录恢复任务的优先级和阈值。
判断恢复任务占有的互斥量数量是否大于0。
如果大于0,将互斥量插入互斥量链表的末尾。
如果占有的互斥量等于0,则直接插入链表头,并且恢复任务保护自己的原始优先级和阈值。
然后将恢复任务占有的互斥量数量加1,同时设置互斥量最高等待优先级为最低。
然后从d开始执行。
d、如果不支持优先级继承,将互斥量的计数值设置为1。
记录互斥量的拥有者。
9、判断挂起的任务数是否只有一个,如果只有一个设置互斥量挂起任务链表头指针为0。
如果不只有一个,则从互斥量链表删除恢复任务节点。
10、设置cleanup为null
11、禁止抢占。
恢复中断。
12、如果恢复任务启动了定时器停止定时器。
13、设置恢复任务的挂起状态为success
14、检测互斥量是否支持优先级继承。
如果支持优先级继承则做如下操作:
a、互斥量挂起任务数量是否大于0?
如果大于执行b。
等于0执行d。
b、检测任务挂起的数量是否大于1,大于1则调用互斥量优先级排序接口改变优先级。
就是要把优先级最高的任务放置到排序链表头位置。
然后向下执行c。
c、如果等于1,禁止中断。
判断互斥量的挂起任务是否为0,如果不为0,则设置互斥量最高等待优先级为挂起任务的第一个任务。
因为第一个任务已经调用互斥量优先级排序接口,其优先级已经被设置为最高。
恢复中断。
d、恢复占有任务的优先级,恢复到占有任务占有的剩余互斥量中等待优先级最高的优先级。
15、恢复任务,是否发生抢占。
16、发生抢占,则重新调度。
17、不发生抢占,返回结束。
.
4.1.3互斥量优先级改变接口
这个接口会在优先级继承时用到,改变占有任务的优先级,主要是任务获取互斥量时若被挂起且挂起任务优先级高于占有任务,则要改变占有任务的优先级到挂起任务。
任务释放互斥量时,占有任务要恢复到原始优先级或者是占有任务占有的剩余互斥量的最高等待优先级时,也会调用此接口。
此接口需要注意的几点问题:
1、要看要改变的任务状态是否处于就绪状态。
2、如果任务不处于就绪态,直接改变其优先级即可。
3、如果任务处于就绪态,则此任务要从就绪态中删除,同时设置下一个要执行的任务。
如果和该任务处于同一个优先级的任务有多个则这个任务将会被恢复到同一个优先级链表的末尾。
如果该任务优先级链表中只有一个任务,则此任务会被恢复到同一个优先级链表的头位置。
4、此函数接口不会导致任务发生抢占。
只是会改变下一个要被执行的任务,当然下一个被执行的任务有可能是刚刚被改变优先级完成后的任务,因为最后还要调度任务恢复接口。
5、如果当前运行的任务是被改变优先级的任务,则此任务会在执行完成后去执行其它的任务,不会马上执行别的任务。
会继续向下执行。
6、总之,要改变的任务在就绪链表中要先被从链表中删除,然后改变其优先级。
最后再恢复这个任务。
在这个过程中要从新设置下一个要被执行的任务。
如果当前正在执行的任务是要被改变优先级的任务,即使优先级被改低,当前任务也会被继续执行,不会发生抢占。
因为改变任务优先级的接口没有去判断是否抢占,还是继续执行当前任务。
4.2计数信号量
4.2.1获取计数信号量
一、注意问题:
1、信号量计数值大于0,就可以获取到。
2、信号量计数值等于0,则不能获取到,获取的任务就要被挂起。
3、在中断中释放信号量问题:
当一个任务获取信号时,如果不能获取到则要被挂起,在执行挂起过程中,中断是被禁止的。
挂起完成后,中断被恢复,此时可以被中打断。
在tx_tsus.c文件中,挂起完成后,执行下面的操作:
/*CheckforapreemptionconditionthatmighthaveoccurredfromanISR.*/
if((_tx_thread_current_ptr!
=_tx_thread_execute_ptr)
&&(_tx_thread_system_state==0))
{
/*Returncontroltothesystem.*/
_tx_thread_system_return();
}
在执行上面的代码过程中可能被中断打断。
此时被中断打断在执行上下文恢复的过程中就会发生任务抢占,会去执行if内部的语句去执行另外一个任务,因为_tx_thread_current_ptr和_tx_thread_execute_ptr的值不一样,但是上下文恢复完后,调用任务调度接口的时候上面的两个值又被设置成了一样。
这样在中断中释放信号量的时候,被挂起的任务又被恢复。
同理_tx_thread_current_ptr和_tx_thread_execute_ptr的值最终也被设置成了一样。
所以被恢复的任务将不会再执行if语句内部的程序。
继续执行被挂起的任务,且获取信号量成功。
If语句内部的程序是切换到另外一个任务去执行。
正常的情况下如果任务挂起时不被中断打断就会执行if内部语句去切换到另外一个任务。
但是被中断打断了,恢复任务上下文的时候就会切换到另外一个任务去了。
即,条件发生了变化,再次发生释放信号量的时候,就继续执行被挂起的任务了。
4.2.2释放计数信号量
4.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.源码中的capacity是说明消息队列能发送多少个指明消息大小的队列元素。
2.消息队列的大小只用消息元素大小的整数倍,多余的剩余字节则不会被使用,所以创建消息队列的时候要指定消息队列的大小为消息元素
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- THREADX 操作系统 模块 详解 第一 部分
![提示](https://static.bdocx.com/images/bang_tan.gif)