学习园地 编写自己的实时操作系统(2) 一MichaeI MeI koni an文一^ ~~ … ,………… 一………●… ……●…__……___……~…一 …l__…………张铁冰…… 译 — 编者按:本文是将Mi c h a e1 Me1 koni an先生撰写的如何编写实时操作系统的文章的后半部分。i Mi chae1 Me1 koni an是澳大利亚悉尼MIL/Dgs Bui1ding Auto∞atioi3的软件工程师,在过去的1 0 i : 年里一直从事嵌入武软件开发 本文是按照叙述型体例写的,本刊刊登时,无意改变文章的体例,i 好在原文能较清晰地表明作者的思路,读者可从中理解编写操作系统的许多实际f=l题、R T 0 S的特点、 需不需要自己编写和自己如何编写等。 2事件驱动进程 图1说明事件驱动进程的概念 在我们的实现 中,每个进程都有自己的输入序列,是用环形缓冲 区来实现的。有两个函数可以对这个缓冲区进行操 作:一个是PutEvent,其它进程可以调用这个函数将 一字符串 由于EVENT—TYPE对于各个进程很可能不 同,用户需要根据INPU r_EVENT_QUEUETYPE来 —为每个事件驱动进程定义一个独特的结构。而且, 每个进程有自己的GetEvent、PutEvent以及一个初 个事件插入到缓冲区中;一个是GetEvent.用来 己操作.其它进程是不可以调用的.参见列表2。 请注意每个进程的EVENT—TYPE结构是不一样 从缓冲区中提取事件 GetEvent只可以被该进程自 的 换句话说,进程自己决定自己所期望的事件格 式 例如,在IO—ProcessRequests进程中.我们希 望包括输出的个数和状态 在打印机进程中 我们 可以简单地建立一个PRN EVENT—TYPE,并定义为 一个很大的缓冲区,其中存放一个以NULL结束的 注释 I,2—3 应用程序提供的超时处理函数(侧如闶烁光 标): 4,5 应用程序通过调用TMR—Start和TMR Stop实 现对差别序列的读写操作; 6通过TMRPutEvent写; —7通过TMR—GctEvcnt读; 8,9,10应用程序的超时处理函数被TMR—Process 直接调用; 1 1 粗实线将应用程序代码和系统代码分开 图2事件驱动的LCD刷新进程——状态转换圉 图3软件定时器模块——数据/事件流圉 SETB IT0 SETB P1.0 NOP CLR P1 0 MOV IE, 81H i丽 负跳变请求中断 百=l,启动A/D转换 参考文献 l 何立民2 王福瑞MCS一51系列单片机应用系统设计 单片微机测控系统设计大全.北 北京:北京航空航天大学出版社.1996 R/H:O.只进行一次A/D转换 :京:北京航空航天大学出版社.1 999 3 MAX]M产品资料全集.美国美信集成产品 公司,2000 开放INT0中断 一 维普资讯 http://www.cqvip.com
学习园地 列表6执行计数器处理部分 Vol atiI e un si gn ed int LCD—始化函数。 ReIoadValue 一个简单的环形缓冲区结构使得我们可以异步 LCD TASK DEFAULT SPEED: 对缓冲区进行读和写。而且可以存放BUFFER SIZE 个数据。其它进程以及该进程本身部可以给缓冲区 中添加事件 事件将被插入在OutPtr的位置 OutPtr 会一直增加,直到缓冲区的拥有者进程开始处理事 件。当进程开始处理事件时,lnPtr被相应调整 当 OutPtr和InPtr相等时,缓冲区中的数据部被处理 完了。“Count”中包含了缓冲区中没有被处理的事 件的个数 列表3是lnitEventBuffer、GetEvent和PutEvent vol atiI e un si g n e d int LCD Ex e cCount e r LCD—TAS K_DEFAULTSPEED; void LCD—Process(voi d1{ ifdef EXACT TIMING disable();//暂时禁止中断 #endif if(LCD_ExecCounter—TASKDISABLED){ _fdef EXACT TIMING enable0; 抛ndif return; 的一个实现 我们很容易想象其它进程是如何澈 活输出的。它们只需要建立一个OUTPUT—EVENT— } ifdef EXACT TIMING TYPE的事件,然后调用OUTPUT—PutEvent就可以 了。正如列表4中一样 接下来是实现我们的输出控制器进程 这应该 恨容易,像列表5中一样。 请注意如果在以上函数中把“ ’改成“while”, 那幺进程的功能就完全不一样了。这样进程就不是 每次处理一个事件,而是一次处理缓冲区中所有的 事件。另外一个好的想法是折衷这丽个方法,让每 个进程每次处理 个事件 如果 能被其它进程所 改变就更好了。改变列表5,以实现这些功能不是 恨难,我把它留给读者来完成。 列表7分步内存检查进程 void MEMORYCbeck(void){ static unsigned int State=0: static unsigned im Csum; byte Ptr; ,,注意可能会和中断处理程序产生竞争 if(LCDExecCounter) —托Ise iff—LCD ExecCounter1 抛ndif { #ifdef EXACT T1MING enable(); gendif e“lrn rj 如果到了这里,就可以运行我们的进程了 重调执行计数器 LCD ExecCounter=LCD ReloedValue; rdef EXACT TIMING enable(); 抛ndif 这里是正常的执行计数器处理部丹 if(State一0){ Ⅳ接下来是应用程序部分 ,,一 清cS1.1ln为0 Csum=0: //函数结束 } } //计算内存指针 这里是固定频率的时钟中断处理程序 interrupt void TIMER.JRQ—lOms(void){ 其它部丹 #ifdef EXACTⅡMING Ptr (void XRAMSTART+State 10o): 每个 —循环做100十字节的CRC AddToCsum(&Csum,Ptr,10O); ifH+Sta【e—lO0)f if(Csum l_GetRAMCsum0) if((LCD ExecCounter!=TASK DISABLED)&& (LCD—ExecCounter)) 一错误。Csum出错。进行处理 PatticO; State=0: _I_CD ExecCount ̄; #eedif fi oth stuff } } 72 丰哼-j- ●t^一●___ 维普资讯 http://www.cqvip.com
W-习园地 -3周期性进程 我们可以从主循环里调用一些函数,但是,首 先要注意以下两个问题: 不要太频繁地调用进程 不必每20 ms调用 一可以动态调整进程的执行速度,参见列表6 关于代码的几点说明: +在绝对定时系统中.执行计数器是被一个固 定频率的中断服务程序来递减的:而在相对定时 次清看门狗的函数,实际上每1 s调用一次就足 +不要耽误太长时间而妨碍其它进程的执行。 对于第一个问题,一个方案是减慢进程的执行 系统中,进程自己递减执行计数器。在绝对定时系 统中.我们知道进程的执行频率(减去其它进程的 延迟)。如果设定LCD—TASK—FREQUENCY为100- 井用一个10 ms的中断,那幺我们知道进程每秒钟 够了c 速度。既可以是相对于主控制循环,也可以是绝对 性的减慢(我们称之为绝对定时) 对于任何一种情 况,都需要给每个进程定义两个变量:执行计数器 和重调值。执行计数器从重调值开始递减,当减到 执行一次.加上那些可能会在LCD执行计数器被递 减为0后,在前面执行的进程的时间。 +volatiIe关键字防止编译器进行优化,否则, 一些编译器(在绝对定时系统中)会假设。LCD— 0时进程执行;否则,进程的调用函数将直接返回。 我们用一个重调值变量而没有用常数,是因为这样 ExeeCounter会总是一个非零数值,特别是当10 ms 中断服务程序是在另外一个文件里。 f EXPIRED TIME旦EVEN£TYPE; case W N TASK IDLE: return; typedef struct{ bytelnPtr;/ 缓冲区头 / byte OutPtr;/ 缓冲区尾‘/ CSSe WIN IlASK CoNST: updateWinConst0; WinTaskS【ate++: break; case WIN TASK VARS: word Count;/ 缓冲区中超时的定时器个数 UpdateWiaVars0; WinTaskState+一: break; 列表IO软件定时器模块的编程接口 word TMR_case W N TASK GRAPH: InstallTimeoutHandler(wordtimer_handle,void UpdateWinGraph0; WinTaskState++; break; ( timeouLfunc)(word,dword)) word TMR Start(word timer handle,word timeout,dword parameter); word TMR—C0,Se WIN kSK土NIMATIONS: 0-JpdateWinAnim0){ ; Stclp(word timerhandl_所有工作都完成了 返回到空闲状态 WinTaskState=WINTASKIDLE; _—列表11软件定时器进程 EXPIRED TIMER EVENT_TYPE‘Timer; voidTMR_) Process(void1{ //如果还有些动画没有更新 在该状态中再 多停留一会儿 break; if((Timer=TMR_GetEventO)l_INVALID_TIME R){ if(Timer->TimeomHandlr【e=NULL1{ //直接调用用户的定时器超时处理函数 default: panic Tirner->TimeoutHandlra(Timer->ID,Timer->Parameetr);1 ) l ) } I l l ’ ! 维普资讯 http://www.cqvip.com
学习园地 TASK DISABLED由#define定义为最大可能 的无符号整数。若执行计数器设置为这一值将使得 这一进程无机会执行,除非有其它条件的干预c任 我们便不希望在一次循环内完成所有的检查.因为 这样要花很长的时间 对于这种进程一十有效的方 法是将它们建立成有限状态机,每次循环跳到下一 何其它进程都可以这样做。例如,一个重要的事件 可以停止打印进程的执行,直到以后再次设定(一 种简单的进程间通讯) +在绝对定时系统中.10 ms中断眠务程序无疑 有很多事情要做。然而.如果只有几个进程.处理 器也许能轻松应付这些比较和递减的操作c这样, 我们可以将这个中断设置为优先级低于一些关键性 的中断(或者允许其它中断打断这一中断的执行) 对于是否应该使用一些更好的方案(例如改变量序 个状态。每个状态的处理时间要小于允许的最长时 间段。列表7是内存检查函数的实现。 这段程序共执行100次 检查10 000字节的内 存。我们通过以上提到的执行计数器和重调值来完 成这个功能 列表8是这一方法的另一个变化 该 进程处于空闲状态,直到WinTaskState被设置为某 个非零状态 接下来的4个循环将更新窗口中相关 部分的信息。例如-要重画整个窗口,可以把 WinTaskState设置为WINTASK CONST,这样,将 —列[delta queue])来防止在时钟中断服务程序中做过 多的递减运算,大家还有些争议c由于系统中进程 的个数一般不会超过20-30,也许不太需要差别序 会使所有的常数得到刷新,然后是所有的变量信 息、所有的图形以及所有的动画。完成后,窗口更 新进程叉处于空闲状态,直到下一次WinTaskState 被设置为非0值 如果只想更新动画,可以方便地 将WinTaskState设置为WIN—TASK—列(参见软件定时器中差别序列的讨论)c 列表12闪烁光标的实瑚例子 ANIMATIONS //在应用程序初始化部分的某个地方= TIMER Insta11TimeoutHandler(TIMER ID CURS0R BLINK,CursorBlink); (在这种情况下,将不重绘屏幕上的常量、变量和 静态图像信息)。图2是LCD刷新进程的状态转换 图 启动330ms定时器 光标闪烁大约3次/s ID CURSOR BLINK 33 TimerStart(TIMER , ,0);_4软件定时器 软件定时器给系统增加了“真正的”多进程功 能。有很多的功能需要某个事件在某个确定的时间 发生(一次或周期性的)。例如: 一初始化结束 这是真正的超时处理函数 参数是光标的下一个状态:开或关 void cursorB1ink(w0rd handle,dw0rd parameter){ 个闪烁的光标 个输出状态需要被周期性地开/关; 一//在LCD上画,擦光标 CursorOnOf甘(byte)parameter); 显示给用户的某些信息在一段时间后需要 关闭或改变; 在一段时间的无动作后,需要点亮或关闭 LCD和键盘; 发给打印机的一十短时间复位信号; 一十闪烁的灯 我们真的需要定义这些小但是很重要的功能为 在绝对定时系统中,应该小心不要让固定频 周期性进程吗?绝大多数的这些功能部需要精确定 时,这使得10 ms中断有很多事情要做 主控制循 环将会变得很长而且混乱。所以我们必须找到一个 更好的办法。 您也许注意到在主控制循环中调用了TMR— P ro oe s s。这是系统中唯一一个非用户定义的进程。 在实现中,TMRPro c e ss本身是一十事件驱动进 —率的中断服务程序中断其它的进程。具体的实现方 法是依CPU和编译器的不同而不同,因为1 6位和 32位的读写操作可能是不可分割的操作。最简单的 办法是在对执行计数器进行操作时临时短时间禁止 中断(就像在列表6中调用disable和enable函数)。在 绝大多数情况下这样都是可以接受的。 周期性进程必须能保证在台理的时间段内返 回 这并不总是一件很容易的事情。例如,考虑一 个后台运行的内存检查程序 如果内存很大的话, 程,和其它用户定义的事件驱动进程没有什厶区 别。 在内部.定时器进程可能会使用像列表9中的 结构来定义定时器进程的输入事件序列c我们使用 74丰 -..・ ●t..一—■■● 维普资讯 http://www.cqvip.com
学习园地 Paramete r项来给模块增加一些灵话性。可以选择将 应用程序用作很多目的,或者干晚忽略这一项, 列表1 0中的软件定时器模块提供了供用户程 序调用的函数 在调用TMRStart和TMRStop前,每个定时 ——序调用TMRStop,如果定时器还没有超时,需要 —保证从改变量序列中移去谚定时器;如果超时,而 且TMRProcess还没有处理的话,需要从超时定时 —器环形缓冲区中将其移去。 在实现了软件定时器模块这些复杂的功能后, 实现一个闪烁光标的应用程序将会很简单.参见列 表l2 器必须安装一个用户定义的超时函数 这时是通过 调用TMIL_InstallTimeoutHandler来完成的 调用时 需要两个参数:一个独一无二的标识数和一个指 向超时函数的指针 谓用之后 便可以通过TMR Start和TMR—Stop来操作定时器了。 TMR Process的实现是很简单的。在内部使用 的是TMR PutEvent和TMRGetEvent函数 我们在 —5 总结 本文讨论的简单而常用的方法足够建立一个比 较复杂而且相对平错的应用程序 作者在此想指出 本文例子中的一些不足之处。我们不喜欢的几点: 前面的事件驱动进程的讨论中对这些函数已经很熟 悉了 参见列表ll ‘有太多的全局变量,像XXXJ: ̄xeeutionCounter 和XXXReloadValue,如果能将其归为一个数据结 —图3是一个简化了的RT/SASD表示的数据/事 件流图。 构会更好 每个进程在开始时部对执行计数器进行同样 的操作 如果能在一段代码中完成会更好。 我们希望每个进程有一个独一无二的标识。 如果不是用IOSCAN就更好了。 —您也许已经猜到了在系统中1 0 1TIs中断是唯一 的对定时器环形缓冲区的定时器提供者c由于可能 有上百个软件定时器在运行,因此.最好技到一个 好的办法来实现我们的系统 在每次10 ms中断时, 递减上百个定时器计数器不是一个很好的主意一因 EixecutionCounter=TASK —DISABLED,而是用OSStop(TASI(_ID_IO SCAN), 此 可以使用著名的改变量序 ̄(delta queue) 我们 依据定时器的剩余时间来将它们安排在改变量序列 中。只有下一个将要到期的定时器得以递减 例 如.如果有三个定时器.分别是10、60和200个时 值 我们不想对每个进程部用 d efi11e定义重调 值c我们更愿意从一个整洁的数据表格中读取这些 如果我们需要加快或减慢进程的执行.最好 不要直接操作进程的重调值/执行计数器,如果我 钟跳动.那幺.改变量序列将会像这样: 10 50(等于60减10) 140(等于200减6O) 只有1 0这个计数器被递减。 这种方法的一个微小的不足,是在增加或去除 一们有一个绒一的函数最好c例如.要减半I/O扫描 进程的执行速度,我们可以: OS—SetReloadVal(TASK—ID—IO—SCAN,OS— GetReEntryValue(TASKIDIO_—. SCAN) 2)) 这可以通过日1人一个单一进程结构来完成。这 个结构中包含进程功能的相关参数。 所有的RTOS 个定时器时.要多做一些工作 而且.如果两个 定时器同时到期也比较痹烦(有些改变量序列的实 现做了小小的欺骗 在其中一个定时器上加了一十 时钟跳动.以避免这种情况的出现)。 无论大小.部台有某种形式的进程表。如果我们的 讨论最后演变成某种类似于真正RTOS的程序,也 不是令人惊奇的一件事情 实际上,我们一直在讨论进程、输入序列、休 眠和空闲状态——这些概念通常只有在RTO S中才 那么,当第一个定时器被递减为0时谚怎么办 呢?当然,时钟中断服务程序不会直接调用超时处 理程序.因为这样可能会花很长时间。我们不允许 在禁止中断时执行长时间的操作。实际上.是从改 变量序列中移去这一项 然后调用TMR—PutEvcot 函数 这样会将超时的定时器加^到超时定时器环 形缓冲区中 有。所以,如果读者再多投入一点精力的话.加上 我们的讨论和例子的帮助,便可以建立自己“几乎 真正”的RTOS 简单而复杂 我们可以建立一个比较复杂的实时应用程序而 无需一个“真正 的RTOS。不幸的是,什么时候 用RTOS会有帮助,什/厶时候用RTOS只舍是一个负 我想把软件定时器的具体实现留给读者 进行 改变量序列的编程是一个很不错的锻炼脑筋的机 会 您需要注意一些有趣的细节。例如,当应用程 担,是候难确定的,要依具体的情况而定。(全文完) -__lIⅫ 。 I &E b。d s 75
因篇幅问题不能全部显示,请点此查看更多更全内容
Copyright © 2019- sarr.cn 版权所有 赣ICP备2024042794号-1
违法及侵权请联系:TEL:199 1889 7713 E-MAIL:2724546146@qq.com
本站由北京市万商天勤律师事务所王兴未律师提供法律服务