立即注册 找回密码

微雪课堂

搜索

《μC/OS-II卧槽宝典(上)》 【连载 第三章 风水轮流转-1】

2016-9-26 15:13| 发布者: waveshare-admin| 查看: 2142| 评论: 1

摘要: 本章探讨怎样实现多任务,并重点研究切换任务需要做什么。 关于:在嵌入式系统中,为何要基于操作系统开发程序?它相对于典型的前后台程序有什么优点? 什么是任务?什么是多任务?等等。这类问题,本章不作额外说明 ...
第三章 风水轮流转
【承前启后的废话】
本章探讨怎样实现多任务,并重点研究切换任务需要做什么。
关于:在嵌入式系统中,为何要基于操作系统开发程序?它相对于典型的前后台程序有什么优点?
什么是任务?什么是多任务?等等。这类问题,本章不作额外说明。
笔者假定读者已经理解这些基本问题。如不理解,请自行百度、谷歌。
大多数书籍讲述多任务的方法是直接告诉读者系统怎么做,如μC/OS-II的任务由什么组成,怎样调度等等。
笔者不打算这么做,而是打算像做一道证明题那样,一步步告诉读者需要怎么做,为什么要这么做。
为让初学者清楚“怎样实现多任务”,笔者决定让自己做回“初学者”,一步步研究到底如何实现多任务。
“怎样实现多任务”的“子问题”是“怎样实现多任务的切换”,这也是一个关键问题。
“怎样实现多任务的切换”的“降阶问题”是“怎样实现两个任务的切换”。
所以,本章先从“怎样实现两个任务的切换”这个特殊化、简单化的问题着手研究,之后层层深入。
最后,再分析“μC/OS-II的任务切换”及“怎样实现多任务”。
-------------------------------------------------------------------------------------------------------------------------------------------
① 你不能理解某个问题,通常意味着,你还无法理解更简单的问题;你不能解决某个问题,一般可以说,你还没能解决更简单的问题。
   笔者认为,将问题“降阶”、“特殊化”应该是一个数理工作者(或工程师)惯用的“武器”。
   将问题降阶、特殊化的目的是为了更快的解决问题,同时,在研究降阶问题往往能给我们一些启示,这些启示并非直接研究一般问题就能够轻易得到。
-------------------------------------------------------------------------------------------------------------------------------------------
· 怎样实现两个任务的切换
【例化问题】
为方便研究“怎样实现两个任务的切换”,我们对它进行“例化”,给出具体问题。
如果,在一个系统中,它运行着A、B两个任务,执行顺序如下:
(1)运行A任务,延时50ms。
(2)运行B任务,延时50ms。
(3)运行A任务,延时50ms。
(4)运行B任务,延时50ms。
问:系统应如何处理,才能成功切换任务?(只须给出与“切换任务”相关的信息)
-------------------------------------------------------------------------------------------------------------------------------------------
① 这里所指的系统,只含有一个单核单线程CPU,单核单线程CPU在任意时刻只能执行一条指令。
-------------------------------------------------------------------------------------------------------------------------------------------
【理论分析】
下面,我们对以上第(1)至(4)步进行分析:
(1):不需作其它处理。(直接运行A任务,因为,到此看不出需要处理什么。)
(2):运行B任务前,将CPU的PC值更新为B任务地址,不需作其它处理。(理由同上)
(3):运行A任务前,将CPU寄存器更新为上一次运行中断时的CPU寄存器。(为使A任务“接着原来”运行)
      然而,“上一次运行中断时的CPU寄存器”并没有被保存,这样:
(2)就需修改为:运行B任务前,将A任务运行中断时的CPU寄存器值存到全局变量④中。
(4):运行B任务前,将CPU寄存器更新为上一次运行中断时的CPU寄存器。(为使A任务“接着原来”运行)
      然而,“上一次运行中断时的CPU寄存器”并没有被保存,这样:
(3)就需修改为:运行A任务前,将B任务运行中断时的CPU寄存器值存到全局变量中,
                  并将CPU寄存器更新为上一次运行中断时的CPU寄存器。
以上内容实际上是分析过程的“草稿”,读者如果能耐心看完,相信,能够明白所以然。
为更清楚的说明问题,将“草稿”进一步整理如下:
(1):不需作其它处理。
(2):运行B任务前,将A任务运行中断时的CPU寄存器值存到全局变量中。
(3):运行A任务前,将B任务运行中断时的CPU寄存器值存到全局变量中,
并将CPU寄存器更新为A任务上一次运行中断时的数据。
(4):运行B任务前,将CPU寄存器更新为上一次运行中断时的CPU寄存器。
说明:如果第(4)步后,系统任务还没结束,需要再切换任务,那么,第(4)步需改为:
运行B任务前,将A任务运行中断时的CPU寄存器值存到全局变量中,
并将CPU寄存器更新为B任务上一次运行中断时的数据。
再次将“草稿”进一步整理如下:
(1):不需作其它处理。
(2):运行B任务前,将A任务运行中断时的CPU寄存器值存到全局变量中。
(3):运行A任务前,将B任务运行中断时的CPU寄存器值存到全局变量中,
并将CPU寄存器更新为A任务上一次运行中断时的数据。
(4):运行B任务前,将A任务运行中断时的CPU寄存器值存到全局变量中,
并将CPU寄存器更新为B任务上一次运行中断时的数据。
通过上面的分析,我们知道切换任务时,需要做的是:(本文称此为Solution 1
·将当前任务的CPU寄存器值存到RAM中。
·将当前任务更新为即将运行任务
·将CPU寄存器更新为即将运行任务上一次运行中断时的数据。
-------------------------------------------------------------------------------------------------------------------------------------------
① 即调用A任务里的首个需要运行的函数。
② 任务的地址即相应的首个运行函数的函数名地址。如,void taskA() {},将taskA赋值给PC。
③ CPU寄存器包括了PC、SP、寄存器组等。
④ 用过μC/OS-II的读者,知道μC/OS-II将CPU寄存器值存到栈中,即存放在局部变量里。但,在这里,由于没有特殊处理,则只能存到全局变量里。
⑤ 这个在分析过程中并没有提到,但,它是隐含需要的,因为:
·本次切换的第三步要更新CPU寄存器,就需要知道“即将运行任务”是哪个。
·下次切换的第一步、第三步,系统也需要知道到“当前任务”。
就本简单问题而言,可以这么说,由于 A、B轮流切换,所以,系统需要记录当前任务,以便知道切换到哪个任务。
-------------------------------------------------------------------------------------------------------------------------------------------
【实践测试】
实际上,将以上理论付诸于实践,并不能实现多任务。
原因是:A任务被挂起时,SP指向某个RAM地址(如0x2000)。切换到B任务,B任务依然要使用SP,然而这个SP值并不是B任务在被挂起时的SP值,而是A任务被挂起时的SP值(如,前面提到的0x2000)。
根据前面章节的研究,我们知道局部变量被分配在与SP相关的RAM中,即栈中
若代码并不“刻意”改变SP的值,则整个栈是连续的。也就是,有且只有一个堆栈。
但,如果要实现多任务,则需要有多个堆栈支持,使得各个任务的RAM数据不会相互影响。
系统可以这么处理:(本文称此为Solution 2
·建立任务
·为任务分配相应的堆栈,即确定栈顶地址及堆栈大小。
·切换任务时
·将SP指向即将运行任务的栈顶地址(“刻意”改变SP值,使得各个任务使用相应的堆栈)。
·将当前任务的SP保存到全局变量中②。
我们综合Solution 1Solution 2,可以得到:
·建立任务时
·为任务分配相应的堆栈,即确定栈顶地址及堆栈大小。(来自Solution 2)
·切换任务时
·将当前任务的CPU寄存器值存到RAM中。(来自Solution 1)
·将当前任务的SP③保存到全局变量中。(来自Solution 2)
·将当前任务更新为即将运行任务。(来自Solution 2)
·将SP指向即将运行任务的栈顶地址。(来自Solution 2)
·将CPU寄存器更新为即将运行任务上一次运行中断时的数据。(来自Solution 1)
Solution 3付诸于实践,前文提到的简单问题得以解决
-------------------------------------------------------------------------------------------------------------------------------------------
① OSTaskCreate、OSTaskCreateExt。
② 这个在分析过程中并没有提到,但,它是隐含需要的,因为:下次切换,系统要“将SP指向即将运行任务的栈顶地址”,所以,需要保存SP。
   为何要保存为全局变量,是因为,只有这样才能找回它。否则,如果分配在局部变量中,难以或者说无法找回它。(加标识有可能找回)
③ 细心的读者将发现:这一步的“SP”已包含在上一步的“CPU寄存器”中,所以,这一步可以省去。确实是这样。
但因为,在μC/OS-II中,分为两步:将CPU寄存器保存在栈中,将SP保存在TCB控制块中。所以,在这,笔者并没有省去这步。以方便后续分析。
④ 仅以上处理是不够的,但,笔者在此只讨论了最基本的、需要处理的东西,以让读者容易明白。
-------------------------------------------------------------------------------------------------------------------------------------------


190

顶一下

刚表态过的朋友 (190 人)

相关阅读

发表评论

最新评论

引用 游客 2017-2-26 19:29
嗯,通俗易懂。但是字体呀!字体呀!用笔记本看伤眼。

查看全部评论(1)

μCOS-II

微雪官网|产品资料|手机版|小黑屋|微雪课堂. ( 粤ICP备05067009号 )

GMT+8, 2019-11-12 06:23 , Processed in 0.018408 second(s), 19 queries .

Powered by Discuz! X3.2 © 2001-2013 Comsenz Inc & Style Design

返回顶部