赞
踩
在学习单片机上使用uscos系统时,思考的一些问题和总结,自己也画了一些图来描述这些想法。本文主要分析问题和模型结构,不会过多涉及具体代码。
在思考这个问题之前,需要首先回答什么是实时操作系统,如今的嵌入式系统大多是实时操作系统,实时意味着系统必须及时并快速地响应外部事件,如中断等。想象一个打印系统,多份文件正在排队等待打印,此时有一个高优先级的资料进入打印队列,为了让这份资料优先打印,需要怎么设计该系统:
粗略一看,两种方案的核心思想都是一样的,都有根据优先级排队的意思,第一种方案实现还更简洁明了,但如果放到一个实际需求中去分析,就是另一种情况了。
此时我需要提前检查墨水和纸张含量,以保证后续的打印工作,因为我不想等到已经没墨没纸了,再去添加。在第一种方案下,我需要等待当前的打印工作完成,并进入到下一个循环的检查工作时,才能响应我的需求,如果该文件过大,或许要等半个小时才能结束!整个过程好像一个单线程的工作方式,一旦进入某个环节就必须完成它,对外界变化的响应时间是不确定的。而在第二种方案下,我可以提高“检查墨水和纸张工作”的优先级,将它作为整体的任务加入到任务栈中,系统可以在打印完当前页面后,进行任务调度,先进行我要求的任务,响应时间是基本固定且迅速的。
再比如,此时我需要打印的某份资料是彩色的,需要去增加彩色墨水盒。在第一种方案下,系统必须等待我加入彩色墨水盒的动作完成并确认,才能恢复整体工作。这一动作看上去没啥问题,但在添加墨水盒的过程中,打印机没有做任何事情,不能暂时跳过当前任务,去安排下一个黑白文件的打印,利用率是低下的。而第二种方案,在我加入墨水的过程中,系统可以安排很多其他的无关任务而不会停顿。
从上面两个例子可以总结出两种系统的优缺点:
回到最开始的思考,现在可以知道,实时系统必须是一个多任务系统。
在处理一个大而复杂的问题时,我们通常倾向于“分而治之”,即把一个大问题分解成多个相对简单、容易解决的小问题,这些小问题也可以叫做小任务,通过运行多个小任务,可以最终达到大任务的目的。μC/OS-Ⅱ就是一个能对这些小任务小问题进行管理和调度的多任务系统,如下图,每一个任务都有三部分组成:任务控制块、任务堆栈、任务程序代码。
其中,任务控制块记录了任务的各个属性,并存放了各种指针;任务堆栈用来保存任务的工作环境和数据;程序代码即任务的具体执行部分。
不同的任务通过各自的任务控制块相链接,形成一条任务控制块链。在μC/OS-Ⅱ中,最多能运行64个不同的任务,优先级从0~63,数字越小优先级越高,即任务控制块链的长度最多为64。且μC/OS-Ⅱ明确规定:不能定义优先级相同的任务,这样才能保证系统运行的稳定。这里面包括了系统任务和用户自定义的任务,其中系统任务只预定义了两个,这两个任务分别占据了最低的两个优先级(最低优先级或称任务数由用户自定义的OS_LOWEST_PRIO值决定):
那么问题来了,任务控制块链是如何产生的呢?
实际上,μC/OS-Ⅱ中共有两条任务控制块链表,均为双向链表结构,一条是空任务控制块链表OSTCBFreeList,一条是实实在在的任务控制块链表OSTCBList。这里所说的空链表并不是指链表上没有元素,而是系统在调用OSInit()进行初始化时,会建立一条长度为OS_MAX_TASKS(用户自定义值)的链表,这上面的每一个元素都是空的任务控制块,仅有数据结构,没有数据,如图:
而另一条任务控制块链表在初始化时没有元素,会在创建任务的过程中向链表添加任务控制块元素。本质就是一个替换的过程,先在一个容器中预留足够的空盒子,每创建一个任务,就把该任务装进一个空盒子里,并取出该盒子放到另一个容器中,具体描述如下:
除了任务控制块链表,系统还定义了一个任务优先级数组OSPrioTbl[ ]。该数组以按优先级顺序一次存放了各个任务控制块的指针,这样在访问某个任务的任务控制块时,就不必遍历整条任务控制块链表了(空间替换时间),上述的这些结构都可以用下图来表示。
这也解释了为什么我们在对任务进行操作时,通常使用任务的优先级来进行索引,这种方式可以快速定位到该任务的任务控制块,再对控制块及其相连接的结构进行操作。
在此基础上,我们也可以很容易猜想到删除任务的原理,即:把任务控制块链表OSTCBList的某个任务控制块的数据还原为空,再逆向操作,将其归还到空任务控制块链表OSTCBFreeList中。这个过程相当于将任务控制器进行“吊销”,但任务本身还是存在于内存中的(未激活)。
由此可见,任务控制块结构是实现多任务管理的核心,任何针对任务的操作都离不开任务控制块,关于任务控制块内部的具体内容之后有时间可以继续分享。
我们在第一个问题中已经了解,在系统的运行过程中,会不断地对就绪表中的任务进行获取和处理,这些任务自身就是个循环体,在不断地重复运行,确保系统不会停下来。在第二个问题中也提到,多个任务通过任务控制块相连,实现任务的快速定位和操作。那么,就绪表和任务控制块链表是什么关系?
先来看就绪表的结构:
就绪表使用一个数组OSRdyTbl[ ]来表示,数组长度为8,表示8个优先级组,优先级按自然索引顺序依次减小。每一组都是一个INT8U类型的整数,可以表示成八个二进制位(1/0),即八个优先级,其中低位为高优先级,某一位置1表示该优先级的任务已经就绪,置0表示该优先级没有任务就绪。为了进一步知道哪一个优先级组中存在就绪任务,μC/OS-Ⅱ引入了一个INT8U类型的变量OSRdyGrp,如OSRdyGrp=10100101表示OSRdyTbl[0]、OSRdyTbl[2]、OSRdyTbl[5]、OSRdyTbl[7]优先级组中存在任务就绪,具体哪一个优先级有任务还得具体看看OSRdyTbl[ ]的值。
了解了就绪表的结构,进一步就要知道就绪表如何操作和运行。就绪表主要包括三个操作:登记、注销和查找(从就绪任务中找到最高优先级任务的标识,当然标识就是“优先级“):
不过要让多任务运转起来,还需要有任务切换的过程,即任务调度。这包含两个步骤,一是寻找当前的最高优先级就绪任务,二就是进行任务切换。在这里有个细节:发生任务调度的原因,不是因为任务彻底完成了(任务是一个无尽的循环),而是因为进入了延时、等待事件或中断等。当调度发生时,需要获取到下一个任务的任务控制块指针OSTCBHighRdy,和当前需要被中止的任务控制块指针OSTCBCur(第二节有提到过),这些经过之前的介绍已经可以做到,接下来使用任务切换宏OS_TASK_SW()进行任务切换,其底层是函数OSCtxSw(),主要做了这两件事:
这样就完成了就绪表和任务控制块的联动,也解释了μC/OS-Ⅱ系统多任务的运行流程。
总结上面的三大问题,多任务的运行流程就可以用这样的过程来描述:
以上分享如有问题和错误,欢迎讨论和指正,关于任务之间的协调和通信,以后有机会继续分享。
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。