Chinaunix首页 | 论坛 | 博客
  • 博客访问: 650994
  • 博文数量: 195
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 894
  • 用 户 组: 普通用户
  • 注册时间: 2015-09-21 16:48
文章分类

全部博文(195)

文章存档

2024年(1)

2023年(1)

2022年(3)

2021年(16)

2020年(4)

2019年(5)

2018年(10)

2017年(72)

2016年(36)

2015年(47)

我的朋友

分类: 嵌入式

2020-07-15 17:56:57


上帝(操作系统)正在开心的玩耍着,看了看时间,一天结束了,夜晚来了(系统定时器中断),哦,要搞事情了,人间将要掀起一场腥风血雨!!!

此时人间(用户进程),小A是武林盟主(pc指针指向它),派头最大,小B、小C、都是它的手下。上帝(系统)拿出天书(任务就绪表)一看,哟,小B不错嘛,一身正气(优先级最高),好了,大手一挥(任务调度),小A你可以下台了,小B,you up,武林盟主(pc指针)让你当。上帝继续快乐的玩耍。

不过第二天正午,小B就坐不住了,他在想:要是晚上我也让上帝(系统)剥夺武林盟主 (pc指针)之位,那多丢脸啊以后怎么混啊,他就像上帝祷告了:身体不适要求休息几天时间(进程休眠),武林盟主(pc指针)先让给别人当吧。

上帝没办法,行吧,哪时回来就和我说下,马上让你官复原职。然后只能重新在天书(任务就绪表)上找继承人,行了,看小C你一脸渴望,就你了!

上面随便举的例子,差不多就这样吧,就按这样说的,我们来构建一个“人间天堂”!

第一步,咳咳,我们先来造人吧!

首先,最多能造多少人(任务)呢?32人吧.........

#define OS_MAX_Task        32u        //最大任务数(优先级数)

这里注意哦,结构体里的第一个成员一定要是这个栈顶指针,这样,我们写汇编的时候,可以通过这个结构体指针直接访问到这个栈顶指针。

上面的都是构建人物属性呢,下面开始真正的造人(任务创建):

刚开始的时候,每个任务都还没有自己的栈的数据,所以我们就先手工写出一些数据。其中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语言函数,设置延时参数同时引发调度即可。

写了那么多,基础的这章算是写到这了,不过有点失策,系统临界区没有写上........

————————————————
版权声明:本文为CSDN博主「风筝丶」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/guet_kite/article/details/76034771


附件是自己在手里头的芯片编写的一个简单程序

阅读(1256) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~