您现在所在的是:

单片机论坛

回帖:1个,阅读:1017 [上一页] [1] [下一页]
854
coolnie
文章数:0
年度积分:50
历史总积分:854
注册时间:2008/2/29
发站内信
发表于:2008/8/27 14:47:00
#0楼
摘  要:从keil c51的内存空间管理方式入手,着重讨论实时操作系统在任务调度时的重入问题,分析一些解决重入的基本方式与方法:分析实时操作系统任务调度的占先性,提出非占先的任务调度是能更适合于keil c51的一种调度方式。为此,构造这一实时操作系统,并有针对性地介绍此系统的堆管理方法、任务的建立以厦任务的切换等。
关键词:51单片机 实时操作系统 任务重八调度
   目前,大多数的产品开发是在基于一些小容量的单片机上进行的。51系列单片机,是我国目前使用最多的单片机系列之一,有非常广大的应用环境与前景,多年来的资源积累,使51系列单片机仍是许多开发者的首选。针对这种情况,近几年涌现出许多基于51内核的扩展芯片,功能越来越齐全,速度越来越快,也从一个侧面说明了51系列单片机在国内的生命力。
   多年来我们一直想找一个合适的实时操作系统,作为自己的开发基础。根据开发需求,整合一些常用的嵌入式构件,以节约开发时间,尽最大可能地减少开发工作量;另外,要求这个实时操作系统能非常容易地嵌入到小容量的芯片中。毕竟,大系统是少数的,而小应用是多数而广泛的。显而易见,μc/os—ii是不太适合于以上要求的,而keil c所带的rtx tiny不带源代码,不具透明性,至于其full版本就更不用说了。
1 keii c51与重入问题
   说到实时操作系统,就不能不考虑重入问题。对于pc机这样的大内存处理器而言,这似乎并不是一个很麻烦的问题,借用μc/os—ii rtos的说法,即要求在重入的函数内,使用局部变量。但5l系列单片机堆栈空间很小,仅局限在256字节之内,无法为每个函数都分配一个局部堆空间。正是由于这个原因,keil c51使用了所谓的可覆盖技术:
   ①局部变量存储在全局ram空间(不考虑扩展外部存储器的情况);
   ②在编译链接时,即已经完成局部变量的定位;
   ③如果各函数之间没有直接或间接的调用关系,则其局部变量空间便可覆盖。
   正是由于以上的原因,在keil c51环境下,纯粹的函数如果不加处理(如增加一个模拟栈),是无法重人的。那么在keil c5l环境下,如何使其函数具有可重人性呢?下面分析在实时操作系统下面,任务的基本结构与模式:
vold taska(void*ptr){
uint8 val_a;
//其他一些变量定义
do{
//实际的用户任务处理代码
}while(1);
}
void taskb(void*ptr){
uint8 valb;
//其他一些变量定义
do{
funcl();
//其他实际的用户任务处理代码
)while(1);
void funcl(){
ulnt8 v al_fa;
//其他变量的定义
//函数的处理代码
}
   在上面的代码中,taska与taskb并不存在直接或间接的调用关系,因而其局部变量val_a与val_b便是可以被互相覆盖的,即其可能都被定位于某一个相同的ram空间。这样,当taska运行一段时间,改变了val_a后,taskb取得cpu控制权并运行时,便可能会改变val_b。由于其指向相同的ram空间,导致taska重新取得cpu控制权时,val—a的值已经改变,从而导致程序运行不正确,反过来亦然。另一方面,funcl()与taskb有直接的调用关系,因而其局部变量val_fa与val_b不会被互相覆盖,但也不能保证其局部变量val_fa不会与taska或其他任务的局部变量形成可覆盖关系。
   将val_a、val_b以及val_fa等局部变量定义为静态变量(加上static指示符)可以解决这一问题。但问题是,定义大量的static类型变量,将导致ram空间的大量占用,有可能直接导致ram空间不够用。尤其是在一些小容量的单片机内,一般只有128或256字节,大量的静态变量定义,在如此小的ram资源状况下显然就不太合适了。由此而有了另一种的解决方法,如下代码所示:
void taskc(void){
uint8 x,v;
whlk(1){
os_enter_critical();
x=getx(); (1)
y=gety(); (2)
//任务的其他代码
os_exit_critical(); (3)
0ssleep(100); (4)
}
}
   以上代码taskc中使用了临界保护的方法来保护代码不被中断占先,确实有效地解决了ram空间太小,不宜大量定义静态变量的问题。然而如果每个任务都采用此种结构,任务一开始,就关闭中断,将使实时性得不到保证。事实证明,这种延时是相当可观的。用一个实例来说明,如果想在系统中使用一个动态刷新的led显示器,就难以保证显示的稳定与连续,哪怕在系统中是使用一个单独的定时器来做这一工作(进入临界区后,ea=0)。其次,这种结构事实上将占先的任务调度转化为非占先的任务调度。实际上如果在(3)与(4)之间没有碰巧发生中断并导致一个任务调度,那就可以理解为是任务主动放弃cpu的控制。如果在(3)和(4)之间碰巧产生了一个中断并导致了一个任务调度,只是执行了一次多余的任务调度而已,而且并不希望在(3)之后发生2次甚至多次的任务调度,相信读者也有这一愿望。
   除此之外,还可以发现任务的一个特点:当任务从(1)重新开始时,局部变量x和y是一个什么值并不在乎,即x和y即使在(3)之后改变了,也已经不再重要,不会影响程序的正确性。其实这一特点也是大部分任务,至少是太部分任务的大部分局部变量的一个共性——如果任务在整个执行过程中,不会(被占先)放弃cpu控制权,则其局部变量大多数并不需要进行特别的保护,即其作用域只是任务的当次执行,针对上面的代码,就是临界保护区内的代码区域。
2 实时操作系统要不要占先
   由上面的分析,如果要保持一个函数可重人,就得使用静态变量,系统的ram资源将是一个严峻的考验;如果使用临界区来保护运行环境,系统的实时性又得不到保证,而且有将占先式任务调度转为非占先任务调度之虞。显然,使用静态变量简单,但有更多的不适用性,对将来功能的调整也是一个阻碍,一般不被采用。那么,就只能从环境保护上来下功夫了,但是果真只能以进入临界区牺牲系统的实时性来保证任务不被占先?下面看看临界保护这一方法的基本思路:
   ①在一个任务中,如果局部变量在其作用域内不被占先切换,则这些变量在任务被剥夺了cpu控制权后,不关心其值也不会影响任务的正确执行;
   ②使用临界区保护,可以达到上面所提到的要求;
   ③由此导致的实时性能与占先切换的减弱可以接受。由此可知,不被占先是任务保护局部变量的关键。既然如此,何不舍弃占先式的任务调度?这不失为一个好的出发点。针对keil c51,非占先式任务调度,可能是一种更好的方法,更能协调51系列单片机的既定资源。下面编写这样一个系统:
   ①使用非占先式任务调度;
   ②可以在小容量的芯片中使用,开发目标是,即使是8051这样小的芯片,也可使用这个实时操作系统;
   ③支持优先级调度,尽可能保证其实时性。
3 实时操作系统的实现
   基于以上的分析与目的,近日完成了这个操作系统。在堆栈上借用rtx的管理方法,即当前任务使用全部的堆空间,如图1所示。
图
3.1 堆栈的初始化与任务的创建
   堆栈的初始化实际是初始化0staskstackbotton数组,并将当前任务指定为空闲任务,下一个运行任务指定为最高优先级任务,即优先级为零的任务。初始化时,将sp的值存人ostaslkstackbotton[o],sp+2的值存入ostaskstackbotton[1],依此类推。而任务是调用0sta-skcreate函数建立的。实际上只是将任务(假设为n号任务)的地址填人到对应ostaskstackbotton[n]所指向的位置,并将sp向后移动2个字节,如图2所示。
图
   为什么要以这样一种规律而不是其他的方式呢?这是由于在任务建立后,还未进行任务调度之前,各任务的堆栈实际上是它们自身的地址,因而其堆栈深度为2,为了程序的简便而直接填入。
void main(void){
osinit(); /*初始化ostaskstackbcbotton队列*/
tmod=(tmod&0xfo)│ 0xol;
tl0=0xbf;
th0=0xfc;
tro=1;
eto=1;
tfo=o:
ostaskcreate(taska,null,0);
ostaskcreate(taskb.null,1);
ostaskcreate(taskc,null,2);
osstart();
   上面这段代码中,所有任务建立后,便调用osstart()开始任务调度。osstart()是一个宏定义,如下所示:
#deflne osstart() d0{\
ostaskcreate(taskidle,null,os_max_tasks);\
ea=l:\
return;\
}while(o)
   首先,它创建了一个空闲任务并打开中断,然后便返回。返回到哪里了呢?我们知道,空闲任务是优先级最低的任务,当调ostaskcreate建立时,会将其地址填人到sp的位置,并把sp向后移动2个字节(见图2及说明),因而此时处在堆栈顶端的,一定是空闲任务taslddle。这就使得这里的return一定会返回到空闲任务。至此,系统进入正常运行状态。

3.2 任务的切换

   任务的切换分两种情况,在当前任务优先级低于下一个取得cpu控制权的任务时,将下一个取得cpu控制权的任务的栈顶到当前任务的栈顶之间的内容向ram空间的高端搬移,以空出全部的ram空间作下一个任务的堆空间,同时更新对应的ostaskstackbotton,使其指向新的正确任务的堆栈栈底。如果当前任务的优先级高于下一个任务的优先级,则作相反的搬移,如图3与图4所示。
图
   所有任务必须主动调用ossleep,放弃cpu的控制权。任务调用ossleep后,将选择优先级最高的就绪任务运行。
结 语
   系统完成后,内核的代码量在400多个字节左右,占用1个定时器----------------------------------------------
此篇文章从博客转发
原文地址: Http://blog.gkong.com/more.asp?id=60554&Name=coolnie
645
工控产品
文章数:0
年度积分:50
历史总积分:645
注册时间:2008/8/19
发站内信
发表于:2008/8/27 15:39:00
#1楼
此楼内容不符合板块规定,不予显示! 查看原帖内容>>

关于我们 | 联系我们 | 广告服务 | 本站动态 | 友情链接 | 法律声明 | 非法和不良信息举报

工控网客服热线:0755-86369299
版权所有 工控网 Copyright©2024 Gkong.com, All Rights Reserved

62.4004