你好,这里是风筝的博客,
欢迎和我一起交流。
上一章:自编STM32轻量级操作系统(一)------操作系统实现 讲了那么多,终于到了实战环节。现在来开始动手码程序。
根据之前分析的,我们先写下分析流程:
上帝(操作系统)正在开心的玩耍着,看了看时间,一天结束了,夜晚来了(系统定时器中断),哦,要搞事情了,人间将要掀起一场腥风血雨!!!
此时人间(用户进程),小A是武林盟主(pc指针指向它),派头最大,小B、小C、都是它的手下。上帝(系统)拿出天书(任务就绪表)一看,哟,小B不错嘛,一身正气(优先级最高),好了,大手一挥(任务调度),小A你可以下台了,小B,you up,武林盟主(pc指针)让你当。上帝继续快乐的玩耍。
不过第二天正午,小B就坐不住了,他在想:要是晚上我也让上帝(系统)剥夺武林盟主 (pc指针)之位,那多丢脸啊以后怎么混啊,他就像上帝祷告了:身体不适要求休息几天时间(进程休眠),武林盟主(pc指针)先让给别人当吧。
上帝没办法,行吧,哪时回来就和我说下,马上让你官复原职。然后只能重新在天书(任务就绪表)上找继承人,行了,看小C你一脸渴望,就你了!
上面随便举的例子,差不多就这样吧,就按这样说的,我们来构建一个“人间天堂”!
第一步,咳咳,我们先来造人吧!
首先,最多能造多少人(任务)呢?32人吧.........
#define OS_MAX_Task 32u //最大任务数(优先级数)
然后每个人都有自己的属性(TCB任务控制块)吧,比如有的人帅,有的人很帅,有的人非常帅!
这里注意哦,结构体里的第一个成员一定要是这个栈顶指针,这样,我们写汇编的时候,可以通过这个结构体指针直接访问到这个栈顶指针。
上面的都是构建人物属性呢,下面开始真正的造人(任务创建):
刚开始的时候,每个任务都还没有自己的栈的数据,所以我们就先手工写出一些数据。其中LR寄存器是每个子函数返回时的寄存器,由于我们任务里是死循环,不会返回,所以这个地方随便写。
void Task_End(void)
{
while(1);
}
接着。打造天书(任务就绪表)一份!
分别为:设置优先级、从任务就绪表中删除、寻找最高优先级。
这里有一个很巧妙的方法:OSRdyTbl就是任务真值表,他是一个unsigned int类型的变量(最好加上volatile关键字),把他展开成二进制,会有32位,他的位数代表他的优先级,位数上置为一代表该优先级有效。同时规定第0位优先级最高,第31位优先级最低。
注意,寻找最高优先级的算法的运算时间会随着任务数量的改变而改变,因此时间是不可预测的,所以实时性并不高,但是确实最简单的。
接着构建我们的大boss上帝(系统初始化)吧:
这里面,就会初始化系统时钟,设置主堆栈(记得之前说过的双堆栈吗),同时创建一个空闲任务,为了防止某一时刻都没有一个任务为就绪态时,pc运行这个空闲任务:
当前任务优先级OS_PrioCur更新为最高优先级,同时指向最高级任务的tcb p指针_TCBHightRdy指向为最高优先级堆栈栈顶。
最后进入OSStartHighRdy这个汇编函数,从此,操作系统要起飞咯、
NVIC_INT_CTRL EQU 0xE000ED04; 中断控制寄存器
NVIC_SYSPRI14 EQU 0xE000ED22; 系统优先级寄存器(优先级14).
NVIC_PENDSV_PRI EQU 0xFF; PendSV优先级(最低).
NVIC_PENDSVSET EQU 0x10000000; PendSV触发值
可能有的同学看到汇编就想晕菜了,没事,注释都写好了,可以看。其实我的汇编也属于能看不能写的程序,所以很大程度都是基于UCOS源码来理解。
设置PendSV中断优先级,这个要设为最低。然后初始化PSP用户堆栈(进程堆栈?)为0,至于为什么设为0,这个待会我会说。然后触发PendSV异常。
在PendSV异常里,我们就会进程任务切换了(武林盟主的换届大会,hhhh)
我们来着重分析这个非常重要的换届大会!!!!!
64行,汇编命令为判断R0(即PSP)是非为0,如果是0,则跳转到OS_CPU_PendSV_Handler_nosave执行。现在知道刚才我们PSP为什么设为0了吧?
上一章我们说的任务切换步骤为:先把任务B的寄存器保存到自己的任务堆栈中,再把任务A的数据从自己的堆栈中做出栈处理。所以流程为,先入栈,再出栈。
现在我们是从main函数里第一次运行,一个任务都没跑嘞,不需要做入栈处理,所以跳过。
但是我们先按正常逻辑来分析,假设PSP不为0.即任务正常切换的状态:
67行,手动入栈,也许有同学会问,为什么不是push命令呢?
记得之前说过的双堆栈吗?现在来大体说下MSP和PSP的用法:
程序开始(或者复位)运行时,使用的是MSP,但是在86行那里,会设置为退出中断之后使用PSP(进程堆栈),在权威指南里也写有:权威指南真是个好东西
所以我们中断返回之后,就会一直使用PSP,在程序运行时,堆栈和出栈也是通过PSP保存到任务堆栈空间,但是,进入中断后又会使用MSP,没办法,规定的,所以为了防止任务堆栈破坏系统堆栈,所以才使用了双堆栈。
好了,继续分析,因为是在异常中断里,使用的是MSP,如果使用push将是对MSP操作,显然不是我们需要的,所以只能手动对PSP入栈了。
将R4到R7入栈,为什么是R4到R11呢?没错,小伙子还会抢答了,就是权威指南!!
指南里面说,会有三股奔腾的暗流,呵呵,我们只关心第一条,管你腥风血雨,我亦巍然不动。
看这个表可以看出,除了R4到R11,其他的寄存器系统已经帮我们入栈了。所以剩下的R4到R11要我们自己动手。
好了,继续分析。
69开始这三行,任务入栈之后要把栈顶指针保存下来吧?就是这里,p_TCB_Cur指向TCB结构体,他的地址就是第一个成员的地址,知道之前说的了把。 保存之后以后出栈能方便了。
接下来出栈也是一样的分析,就不过多累述了。
好了,接着构建白天黑夜(滴答定时器作为系统时钟):
其中,System_Ticks是我自己定义的一个宏,表示每1000/System_Ticks ms进入一次中断。这里我定义为100,即每10ms进入一次中断。
进入夜晚(中断)后要干什么的?当然是搞事情了!
这里,先对每一个任务遍历一遍,查看是否有任务的休眠时间结束,如果有,重新添加到任务就绪表中。最后进行系统调度。
这就是系统调度函数,查找出最高优先级任务,如果不是当前任务,才会执行真正的调度,以免浪费时间。
现在我们在看看OSCtxSw这个汇编函数:
嘿嘿,别怕,很简单。只是引起PendSV 异常,执行调度而已。。。。
最后,我们再写延时函数,就大功告成了!!!
很简单的C语言函数,设置延时参数同时引发调度即可。
写了那么多,基础的这章算是写到这了,不过有点失策,系统临界区没有写上........
就先这样吧,希望你们都能看得懂,下一章把临界区补上,再把内存管理写一下。
这是LED的实验例程,两个LED会闪烁:LED实验例程下载
下一章:自编STM32轻量级操作系统(三)------内存管理