自编STM32轻量级操作系统(二)------任务调度

你好,这里是风筝的博客,

欢迎和我一起交流。


上一章:自编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轻量级操作系统(三)------内存管理


评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值