欢迎来到淘文阁 - 分享文档赚钱的网站! | 帮助中心 好文档才是您的得力助手!
淘文阁 - 分享文档赚钱的网站
全部分类
  • 研究报告>
  • 管理文献>
  • 标准材料>
  • 技术资料>
  • 教育专区>
  • 应用文书>
  • 生活休闲>
  • 考试试题>
  • pptx模板>
  • 工商注册>
  • 期刊短文>
  • 图片设计>
  • ImageVerifierCode 换一换

    2022年2022年给DIY超轻量级多任务操作系统 .pdf

    • 资源ID:34875411       资源大小:163.21KB        全文页数:17页
    • 资源格式: PDF        下载积分:4.3金币
    快捷下载 游客一键下载
    会员登录下载
    微信登录下载
    三方登录下载: 微信开放平台登录   QQ登录  
    二维码
    微信扫一扫登录
    下载资源需要4.3金币
    邮箱/手机:
    温馨提示:
    快捷下载时,用户名和密码都是您填写的邮箱或者手机号,方便查询和重复下载(系统自动生成)。
    如填写123,账号就是123,密码也是123。
    支付方式: 支付宝    微信支付   
    验证码:   换一换

     
    账号:
    密码:
    验证码:   换一换
      忘记密码?
        
    友情提示
    2、PDF文件下载后,可能会被浏览器默认打开,此种情况可以点击浏览器菜单,保存网页到桌面,就可以正常下载了。
    3、本站不支持迅雷下载,请使用电脑自带的IE浏览器,或者360浏览器、谷歌浏览器下载即可。
    4、本站资源下载后的文档和图纸-无水印,预览文档经过压缩,下载后原文更清晰。
    5、试题试卷类文档,如果标题没有明确说明有答案则都视为没有答案,请知晓。

    2022年2022年给DIY超轻量级多任务操作系统 .pdf

    本文来自 http:/ ,要不要写这篇文章 ?最后觉得对操作系统感兴趣的人还是很多,写吧 .我不一定能造出玉 ,但我可以抛出砖 . 包括我在内的很多人都对51 使用操作系统呈悲观态度,因为 51 的片上资源太少 .但对于很多要求不高的系统来说 ,使用操作系统可以使代码变得更直观,易于维护 ,所以在 51 上仍有操作系统的生存机会 . 流行的 uCos,Tiny51等,其实都不适合在 2051 这样的片子上用 ,占资源较多 ,唯有自已动手 ,以不变应万变 ,才能让 51也有操作系统可用 .这篇贴子的目的 ,是教会大家如何现场写一个OS, 而不是给大家提供一个OS版本.提供的所有代码 ,也都是示例代码 ,所以不要因为它没什么功能就说LAJI之类的话 .如果把功能写全了 ,一来估计你也不想看了 ,二来也失去灵活性没有价值了. 下面的贴一个示例出来 ,可以清楚的看到 ,OS本身只有不到10 行源代码 ,编译后的目标代码60字节,任务切换消耗为 20 个机器周期 .相比之下 ,KEIL内嵌的 TINY51目标代码为 800字节,切换消耗 100700 周期.唯一不足之处是 ,每个任务要占用掉十几字节的堆栈,所以任务数不能太多 ,用在 128B内存的 51 里有点难度 ,但对于 52 来说问题不大 .这套代码在 36M 主频的 STC 12C4052上实测 ,切换任务仅需 2uS. #include #define MAX_TASKS 2 / 任务槽个数 .必须和实际任务数一至#define MAX_TASK_DEP 12 /最大栈深 .最低不得少于 2 个,保守值为 12. unsigned char idata task_stackMAX_TASKSMAX_TASK_DEP;/任务堆栈 . unsigned char task_id; /当前活动任务号/ 任务切换函数 (任务调度器 ) void task_switch() task_sptask_id = SP; if(+task_id = MAX_TASKS) task_id = 0; SP = task_sptask_id; / 任务装入函数 .将指定的函数 (参数 1)装入指定 (参数 2)的任务槽中 .如果该槽中原来就有任务 ,名师资料总结 - - -精品资料欢迎下载 - - - - - - - - - - - - - - - - - - 名师精心整理 - - - - - - - 第 1 页,共 17 页 - - - - - - - - - 则原任务丢失 ,但系统本身不会发生错误 . void task_load(unsigned int fn, unsigned char tid) task_sptid = task_stacktid + 1; task_stacktid0 = (unsigned int)fn & 0 xff; task_stacktid1 = (unsigned int)fn 8; / 从指定的任务开始运行任务调度.调用该宏后 ,将永不返回 . #define os_start(tid) task_id = tid,SP = task_sptid;return; /*=以下为测试代码 =*/ void task1() static unsigned char i; while(1) i+; task_switch();/ 编译后在这里打上断点 void task2() static unsigned char j; while(1) j+=2; task_switch();/ 编译后在这里打上断点 void main() /这里装载了两个任务 ,因此在定义 MAX_TASKS 时也必须定义为2 task_load(task1, 0);/ 将 task1 函数装入 0 号槽 task_load(task2, 1);/ 将 task2 函数装入 1 号槽 os_start(0); 这样一个简单的多任务系统虽然不能称得上真正的操作系统,但只要你了解了它的原理,就能轻易地将它扩展得非常强大,想知道要如何做吗 ? 名师资料总结 - - -精品资料欢迎下载 - - - - - - - - - - - - - - - - - - 名师精心整理 - - - - - - - 第 2 页,共 17 页 - - - - - - - - - 所附文件下载 : 从单任务到多任务并行系统的演变ourdev_378093.rar(文件大小 :115K)(原文件名 :演变.rar)一个最简单的多任务并行系统ourdev_378094.rar(文件大小 :19K)(原文件名 :mtask.rar)一.什么是操作系统? 人脑比较容易接受 类比这种表达方式 ,我就用 公交系统 来类比 操作系统 吧. 当我们要解决一个问题的时候,是用某种处理手段去完成它,这就是我们常说的 方法 ,计算机里叫 程序(有时候也可以叫它 算法 ). 以出行为例 ,当我们要从 A 地走到 B 地的时候 ,可以走着去 ,也可以飞着去 ,可以走直线 ,也可以绕弯路 ,只要能从 A 地到 B 地,都叫作方法 .这种从 A 地到 B 的需求 ,相当于计算机里的 任务 ,而实现从 A 地到 B 地的方法 ,叫作任务处理流程 很显然 ,这些走法中 ,并不是每种都合理 ,有些傻子都会采用的 ,有些是傻子都不采会用的.用计算机的话来说就是 ,有的任务处理流程好 ,有的任务处理流程好 ,有的处理流程差 . 可以归纳出这么几种真正算得上方法的方法: 有些走法比较快速 ,适合于赶时间的人 ;有些走法比较省事 ,适合于懒人 ;有些走法比较便宜 ,适合于穷人 . 用计算机的话说就是 ,有些省 CPU, 有些流程简单 ,有些对系统资源要求低 . 现在我们可以看到一个问题: 如果全世界所有的资源给你一个人用(单任务独占全部资源 ),那最适合你需求的方法就是好方法.但事实上要外出的人很多,例如 10 个人(10 个任务 ),却只有 1 辆车(1 套资源 ),这叫作资源争用. 如果每个人都要使用最适合他需求的方法,那司机就只好给他们一人跑一趟了,而在任一时刻里,车上只有一个乘客.这叫作 顺序执行 ,我们可以看到这种方法对系统资源的浪费是严重的. 如果我们没有法力将1 台车变成 10 台车来送这 10 个人,就只好制定一些机制和约定,让 1台车看起来像 10 台车,来解决这个问题的办法想必大家都知道,那就是制定公交线路 . 最简单的办法是将所有旅客需要走的起点与终点串成一条线,车在这条线上开 ,乘客则自已决定上下车 .这就是最简单的公交线路.它很差劲 ,但起码解决客人们对车争用.对应到计算机里 ,就是把所有任务的代码混在一起执行. 这样做既不优异雅 ,也没效率 ,于是司机想了个办法,把这些客户叫到一起商量,将所有客人出行的起点与终点罗列出来,统计这些线路的使用频度,然后制定出公交线路:有些路线可以合并起来成为一条线路 ,而那些不能合并的路线,则另行开辟行车车次 ,这叫作 任务定义 .另外,对于人多路线 ,车次排多点 ,时间上也优先安排 ,这叫作 任务优先级 . 名师资料总结 - - -精品资料欢迎下载 - - - - - - - - - - - - - - - - - - 名师精心整理 - - - - - - - 第 3 页,共 17 页 - - - - - - - - - 经过这样的安排后 ,虽然仍只有一辆车 ,但运载能力却大多了 .这套车次 /路线的按排 ,就是一套公交系统 .哈,知道什么叫操作系统了吧?它也就是这么样的一种约定. 操作系统 : 我们先回过头归纳一下 : 汽车系统资源 .主要指的是 CPU, 当然还有其它 ,比如内存 ,定时器 ,中断源等 . 客户出行任务正在走的路线进程一个一个的运送旅客顺序执行同时运送所有旅客多任务并行按不同的使用频度制定路线并优先跑较繁忙的路线任务优先级计算机内有各种资源 ,单从硬件上说 ,就有 CPU, 内存,定时器 ,中断源 ,I/O 端口等 .而且还会派生出来很多软件资源 ,例如消息池 . 操作系统的存在 ,就是为了让这些资源能被合理地分配. 最后我们来总结一下 ,所谓操作系统 ,以我们目前权宜的理解就是:为解决计算机资源争用而制定出的一种约定 . 二.51 上的操作系统对于一个操作系统来说 ,最重要的莫过于并行多任务.在这里要澄清一下 ,不要拿当年的DOS来说事 ,时代不同了 .况且当年 IBM和小比尔着急将PC搬上市,所以才抄袭 PLM( 好象是叫这个名吧?记不太清 )搞了个今天看来很 粗制滥造 的 DOS出来.看看当时真正的操作系统-UNIX, 它还在纸上时就已经是多任务的了. 对于我们 PC来说,要实现多任务并不是什么问题,但换到 MCU却很头痛 : 1.系统资源少在 PC上,CPU主频以 G 为单位 ,内存以 GB为单位 ,而 MCU的主频通常只有十几M,内存则是Byts.在这么少的资源上同时运行多个任务,就意味着操作系统必须尽可能的少占用硬件资源. 2.任务实时性要求高PC 并不需要太关心实时性 ,因为 PC 上几乎所有的实时任务都被专门的硬件所接管,例如所有的声卡网卡显示上都内置有DSP以及大量的缓存 .CPU只需坐在那里指手划脚告诉这些板卡如何应付实时信息就行了. 而 MCU 不同 ,实时信息是靠CPU 来处理的 ,缓存也非常有限 ,甚至没有缓存 .一旦信息到名师资料总结 - - -精品资料欢迎下载 - - - - - - - - - - - - - - - - - - 名师精心整理 - - - - - - - 第 4 页,共 17 页 - - - - - - - - - 达,CPU必须在极短的时间内响应 ,否则信息就会丢失 . 就拿串口通信来举例 ,在标准的PC 架构里 ,巨大的内存允许将信息保存足够长的时间.而对于 MCU 来说内存有限 ,例如 51 仅有 128字节内存 ,还要扣除掉寄存器组占用掉的832个字节 ,所以通常都仅用几个字节来缓冲.当然,你可以将数据的接收与处理的过程合并,但对于一个操作系统来说 ,不推荐这么做 . 假定以 115200bps通信速率向 MCU 传数据 ,则每个字节的传送时间约为9uS,假定缓存为 8字节,则串口处理任务必须在70uS内响应 . 这两个问题都指向了同一种解决思路:操作系统必须轻量轻量再轻量,最好是不占资源 (那当然是做梦啦 ). 可用于 MCU 的操作系统很多 ,但适合 51(这里的 51 专指无扩展内存的51)几乎没有 .前阵子见过一个 圈圈操作系统 ,那是我所见过的操作系统里最轻量的,但仍有改进的余地 . 很多人认为 ,51 根本不适合使用操作系统.其实我对这种说法并不完全接受,否则也没有这篇文章了 . 我的看法是 ,51 不适合采用 通用操作系统 .所谓通用操作系统就是 ,不论你是什么样的应用需求,也不管你用什么芯片 ,只要你是 51,通通用同一个操作系统 . 这种想法对于 PC来说没问题 ,对于嵌入式来说也不错 ,对 AVR来说还凑合 ,而对于 51 这种贫穷型 的 MCU来说,不行. 怎样行 ?量体裁衣 ,现场根据需求构建一个操作系统出来! 看到这里 ,估计很多人要翻白眼了 ,大体上两种 : 1.操作系统那么复杂 ,说造就造 ,当自已是神了 ? 2.操作系统那么复杂 ,现场造一个会不会出BUG? 哈哈,看清楚了 ?问题出在 复杂上面,如果操作系统不复杂 ,问题不就解决了 ? 事实上 ,很多人对操作系统的理解是片面的,操作系统不一定要做得很复杂很全面,就算仅个多任务并行管理能力 ,你也可以称它操作系统. 只要你对多任务并行的原理有所了解,就不难现场写一个出来 ,而一旦你做到了这一点,为各任务间安排通信约定 ,使之发展成一个为你的应用系统量身定做的操作系统也就不难了. 为了加深对操作系统的理解,可以看一看 这份 PPT, 让你充分了解一个并行多任务是如何一步步从顺序流程演变过来的.里面还提到了很多人都在用的状态机 ,你会发现操作系统跟状态机从原理上其实是多么相似.会用状态机写程序 ,都能写出操作系统 . 三.我的第一个操作系统直接进入主题 ,先贴一个操作系统的示范出来.大家可以看到 ,原来操作系统可以做得么简名师资料总结 - - -精品资料欢迎下载 - - - - - - - - - - - - - - - - - - 名师精心整理 - - - - - - - 第 5 页,共 17 页 - - - - - - - - - 单. 当然,这里要申明一下 ,这玩意儿其实算不上真正的操作系统,它除了并行多任务并行外根本没有别的功能 .但凡事都从简单开始,搞懂了它 ,就能根据应用需求 ,将它扩展成一个真正的操作系统. 好了,代码来了 . 将下面的代码直接放到KEIL里编译,在每个 task?()函数的task_switch();那里打上断点 ,就可以看到它们的确是 同时在执行的 . #include #define MAX_TASKS 2 / 任务槽个数 .必须和实际任务数一至#define MAX_TASK_DEP 12 /最大栈深 .最低不得少于 2 个,保守值为 12. unsigned char idata task_stackMAX_TASKSMAX_TASK_DEP;/任务堆栈 . unsigned char task_id; /当前活动任务号/ 任务切换函数 (任务调度器 ) void task_switch() task_sptask_id = SP; if(+task_id = MAX_TASKS) task_id = 0; SP = task_sptask_id; / 任务装入函数 .将指定的函数 (参数 1)装入指定 (参数 2)的任务槽中 .如果该槽中原来就有任务 ,则原任务丢失 ,但系统本身不会发生错误 . void task_load(unsigned int fn, unsigned char tid) task_sptid = task_stacktid + 1; task_stacktid0 = (unsigned int)fn & 0 xff; task_stacktid1 = (unsigned int)fn 8; / 从指定的任务开始运行任务调度.调用该宏后 ,将永不返回 . #define os_start(tid) task_id = tid,SP = task_sptid;return; /*=以下为测试代码 =*/ 名师资料总结 - - -精品资料欢迎下载 - - - - - - - - - - - - - - - - - - 名师精心整理 - - - - - - - 第 6 页,共 17 页 - - - - - - - - - void task1() static unsigned char i; while(1) i+; task_switch();/ 编译后在这里打上断点 void task2() static unsigned char j; while(1) j+=2; task_switch();/ 编译后在这里打上断点 void main() / 这里装载了两个任务 ,因此在定义 MAX_TASKS 时也必须定义为 2 task_load(task1, 0);/将 task1 函数装入 0 号槽task_load(task2, 1);/将 task2 函数装入 1 号槽os_start(0); 限于篇幅我已经将代码作了简化,并删掉了大部分注释 ,大家可以直接下载源码包,里面完整的注解 ,并带 KEIL工程文件 ,断点也打好了 ,直接按 ctrl+f5 就行了 . 现在来看看这个多任务系统的原理: 这个多任务系统准确来说 ,叫作协同式多任务 . 所谓协同式 ,指的是当一个任务持续运行而不释放资源时,其它任务是没有任何机会和方式获得运行机会 ,除非该任务主动释放CPU. 在本例里 ,释放 CPU是靠 task_switch()来完成的 .task_switch()函数是一个很特殊的函数,我们可以称它为 任务切换器 . 要清楚任务是如何切换的 ,首先要回顾一下堆栈的相关知识. 有个很简单的问题 ,因为它太简单了 ,所以相信大家都没留意过: 我们知道 ,不论是 CALL还是 JMP ,都是将当前的程序流打断 ,请问 CALL和 JMP的区别是什么 ? 你会说 :CALL可以 RET,JMP 不行.没错,但原因是啥呢 ?为啥 CALL过去的就可以用RET跳回来,JMP过去的就不能用RET来跳回呢 ? 名师资料总结 - - -精品资料欢迎下载 - - - - - - - - - - - - - - - - - - 名师精心整理 - - - - - - - 第 7 页,共 17 页 - - - - - - - - - 很显然 ,CALL通过某种方法保存了打断前的某些信息,而在返回断点前执行的RET指令,就是用于取回这些信息 . 不用多说 ,大家都知道 ,某些信息 就是 PC指针,而某种方法 就是压栈 . 很幸运 ,在 51 里,堆栈及堆栈指针都是可被任意修改的,只要你不怕死 .那么假如在执行RET前将堆栈修改一下会如何?往下看 : 当程序执行 CALL后,在子程序里将堆栈刚才压入的断点地址清除掉,并将一个函数的地址压入,那么执行完 RET后,程序就跳到这个函数去了 . 事实上 ,只要我们在 RET前将堆栈改掉 ,就能将程序跳到任务地方去,而不限于 CALL 里压入的地址. 重点来了 . 首先我们得为每个任务单独开一块内存,这块内存专用于作为对应的任务的堆栈,想将 CPU交给哪个任务 ,只需将栈指针指向谁内存块就行了. 接下来我们构造一个这样的函数: 当任务调用该函数时 ,将当前的堆栈指针保存一个变量里,并换上另一个任务的堆栈指针.这就是任务调度器了 . OK 了,现在我们只要正确的填充好这几个堆栈的原始内容,再调用这个函数 ,这个任务调度就能运行起来了 . 那么这几个堆栈里的原始内容是哪里来的呢?这就是 任务装载 函数要干的事了 . 在启动任务调度前将各个任务函数的入口地址放在上面所说的任务专用的内存块 里就行了!对了,顺便说一下 ,这个 任务专用的内存块 叫作私栈,私栈的意思就是说,每个任务的堆栈都是私有的 ,每个任务都有一个自已的堆栈. 话都说到这份上了 ,相信大家也明白要怎么做了: 1.分配若干个内存块 ,每个内存块为若干字节: 这里所说的 若干个内存块 就是私栈 ,要想同时运行几少个任务就得分配多少块.而每个子内存块若干字节 就是栈深 .记住,每调一层子程序需要2 字节.如果不考虑中断 ,4 层调用深度 ,也就是 8 字节栈深应该差不多了 . unsigned char idata task_stackMAX_TASKSMAX_TASK_DEP 当然,还有件事不能忘 ,就是堆指针的保存处 .不然光有堆栈怎么知道应该从哪个地址取数据啊unsigned char idata task_spMAX_TASKS 上面两项用于装任务信息的区域,我们给它个概念叫 任务槽 .有些人叫它 任务堆 ,我觉得还是槽比较直观名师资料总结 - - -精品资料欢迎下载 - - - - - - - - - - - - - - - - - - 名师精心整理 - - - - - - - 第 8 页,共 17 页 - - - - - - - - - 对了,还有任务号 .不然怎么知道当前运行的是哪个任务呢? unsigned char task_id 当前运行存放在 1 号槽的任务时 ,这个值就是 1,运行 2 号槽的任务时 ,这个值就是 2. 2.构造任务调度函函数 : void task_switch() task_sptask_id = SP;/ 保存当前任务的栈指针if(+task_id = MAX_TASKS)/任务号切换到下一个任务task_id = 0; SP = task_sptask_id;/ 将系统的栈指针指向下个任务的私栈. 3.装载任务 : 将各任务的函数地址的低字节和高字节分别入在task_stack任务号 0和 task_stack任务号 1中: 为了便于使用 ,写一个函数 : task_load(函数名 , 任务号 ) void task_load(unsigned int fn, unsigned char tid) task_sptid = task_stacktid + 1; task_stacktid0 = (unsigned int)fn & 0 xff; task_stacktid1 = (unsigned int)fn 8; 4.启动任务调度器 : 将栈指针指向任意一个任务的私栈,执行 RET指令.注意,这可很有学问的哦 ,没玩过堆栈的人脑子有点转不弯 :这一 RET,RET 到哪去了 ?嘿嘿,别忘了在 RET前已经将堆栈指针指向一个函数的入口了 .你别把 RET看成 RET, 你把它看成是另一种类型的JMP就好理解了 . SP = task_sp 任务号 ; return; 做完这 4 件事后 ,任务并行执行就开始了 .你可以象写普通函数一个写任务函数,只需(目前可以这么说 )注意在适当的时候 (例如以前调延时的地方 )调用一下 task_switch(),以让出 CPU控制权给别的任务就行了 . 最后说下效率问题 . 名师资料总结 - - -精品资料欢迎下载 - - - - - - - - - - - - - - - - - - 名师精心整理 - - - - - - - 第 9 页,共 17 页 - - - - - - - - - 这个多任务系统的开销是每次切换消耗20个机器周期 (CALL和 RET都算在内了 ),贵吗?不算贵,对于很多用状态机方式实现的多任务系统来说,其实效率还没这么高 - case switch 和 if()可不像你想像中那么便宜 . 关于内存的消耗我要说的是,当然不能否认这种多任务机制的确很占内存.但建议大家不要老盯着编译器下面的那行字DATA = XXXbyte. 那个值没意义 ,堆栈没算进去 .关于比较省内存多任务机制 ,我将来会说到 . 概括来说 ,这个多任务系统适用于实时性要求较高而内存需求不大的应用场合,我在运行于36M 主频的 STC 12C4052上实测了一把 ,切换一个任务不到 3 微秒. 下回我们讲讲用 KEIL写多任务函数时要注意的事项. 下下回我们讲讲如何增强这个多任务系统,跑步进入操作系统时代 . 四.用 KEIL写多任务系统的技巧与注意事项C51编译器很多 ,KEIL 是其中比较流行的一种 .我列出的所有例子都必须在KEIL中使用 .为何?不是因为 KEIL好所以用它 (当然它的确很棒 ),而是因为这里面用到了KEIL的一些特性 ,如果换到其它编译器下 ,通过编译的倒不是问题,但运行起来可能是堆栈错位,上下文丢失等各种要命的错误,因为每种编译器的特性并不相同.所以在这里先说清楚这一点. 但是,我开头已经说了 ,这套帖子的主要目的是阐述原理,只要你能把这几个例子消化掉,那么也能够自已动手写出适合其它编译器的OS. 好了,说说 KEIL的特性吧 ,先看下面的函数 : sbit sigl = P17; void func1() register char data i; i = 5; do sigl = !sigl; while(-i); 你会说 ,这个函数没什么特别的嘛 !呵呵,别着急 ,你将它编译了 ,然后展开汇编代码再看看: 193: void func1() 194: register char data i; 195: i = 5; C:0 x00C3 7F05 MOV R7,#0 x05 196: do 197: sigl = !sigl; 名师资料总结 - - -精品资料欢迎下载 - - - - - - - - - - - - - - - - - - 名师精心整理 - - - - - - - 第 10 页,共 17 页 - - - - - - - - - C:0 x00C5 B297 CPL sigl(0 x90.7) 198: while(-i); C:0 x00C7 DFFC DJNZ R7,C:00C5 199: C:0 x00C9 22 RET 看清楚了没 ?这个函数里用到了R7, 却没有对 R7进行保护 ! 有人会跳起来了 :这有什么值得奇怪的 ,因为上层函数里没用到R7啊.呵呵,你说的没错 ,但只说对了一半 :事实上 ,KEIL编译器里作了约定 ,在调子函数前会尽可能释放掉所有寄存器.通常性况下,除了中断函数外 ,其它函数里都可以任意修改所有寄存器而无需先压栈保护(其实并不是这样,但现在暂时这样认为 ,饭要一口一口吃嘛 ,我很快会说到的 ). 这个特性有什么用呢?有!当我们调用任务切换函数时,要保护的对象里可以把所有的寄存器排除掉了 ,就是说 ,只需要保护堆栈即可 ! 现在我们回过头来看看之前例子里的任务切换函数: void task_switch() task_sptask_id = SP;/ 保存当前任务的栈指针if(+task_id = MAX_TASKS)/任务号切换到下一个任务task_id = 0; SP = task_sptask_id;/ 将系统的栈指针指向下个任务的私栈. 看到没 ,一个寄存器也没保护 ,展开汇编看看 ,的确没保护寄存器 . 好了,现在要给大家泼冷水了 ,看下面两个函数 : void func1() register char data i; i = 5; do sigl = !sigl; while(-i); void func2() register char data i; i = 5; do func1(); while(-i); 名师资料总结 - - -精品资料欢迎下载 - - - - - - - - - - - - - - - - - - 名师精心整理 - - - - - - - 第 11 页,共 17 页 - - - - - - - - - 父函数 fun2()里调用 func1(),展开汇编代码看看 : 193: void func1() 194: register char data i; 195: i = 5; C:0 x00C3 7F05 MOV R7,#0 x05 196: do 197: sigl = !sigl; C:0 x00C5 B297 CPL sigl(0 x90.7) 198: while(-i); C:0 x00C7 DFFC DJNZ R7,C:00C5 199: C:0 x00C9 22 RET 200: void func2() 201: register char data i; 202: i = 5; C:0 x00CA 7E05 MOV R6,#0 x05 203: do 204: func1(); C:0 x00CC 11C3 ACALL func1(C:00C3) 205: while(-i); C:0 x00CE DEFC DJNZ R6,C:00CC 206: C:0 x00D0 22 RET 看清楚没 ?函数 func2()里的变量使用了寄存器R6,而在 func1 和 func2 里都没保护 . 听到这里 ,你可能又要跳一跳了 :func1()里并没有用到R6,干嘛要保护 ?没错,但编译器是怎么知道 func1()没用到 R6的呢?是从调用关系里推测出来的. 一点都没错 ,KEIL会根据函数间的直接调用关系为各函数分配寄存器,既不用保护 ,又不会冲突,KEIL好棒哦!等一下 ,先别高兴 ,换到多任务的环境里再试试: void func1() register char data i; i = 5; do sigl = !sigl; while(-i); void func2() register char data i; i = 5; do sigl = !sigl; while(-i); 名师资料总结 - - -精品资料欢迎下载 - - - - - - - - - - - - - - - - - - 名师精心整理 - - - - - - - 第 12 页,共 17 页 - - - - - - - - - 展开汇编代码看看 : 193: void func1() 194: register char data i; 195: i = 5; C:0 x00C3 7F05 MOV R7,#0 x05 196: do 197: sigl = !sigl; C:0 x00C5 B297 CPL sigl(0 x90.7) 198: while(-i); C:0 x00C7 DFFC DJNZ R7,C:00C5 199: C:0 x00C9 22 RET 200: void func2() 201: register char data i; 202: i = 5; C:0 x00CA 7F05 MOV R7,#0 x05 203: do 204: sigl = !sigl; C:0 x00CC B297 CPL sigl(0 x90.7) 205: while(-i); C:0 x00CE DFFC DJNZ R7,C:00CC 206: C:0 x00D0 22 RET 看到了吧 ?哈哈,这回神仙也算不出来了.因为两个函数没有了直接调用的关系,所以编译器认为它们之间不会产生冲突,结果分配了一对互相冲突的寄存器,当任务从 func1()切换到 func2()时,func1()中的寄存器内容就给破坏掉了.大家可以试着去编译一下下面的程序: sbit sigl = P17; void func1() register char data i; i = 5; do sigl = !sigl; task_switch(); while(-i); void func2() register char data i; i = 5; 名师资料总结 - - -精品资料欢迎下载 - - - - - - - - - - - - - - - - - - 名师精心整理 - - - - - - - 第 13 页,共 17 页 - - - - - - - - - do sigl = !sigl; task_switch(); while(-i); 我们这里只是示例 ,所以仍可以通过手工分配不同的寄存器避免寄存器冲突,但在真实的应用中,由于任务间的切换是非常随机的,我们无法预知某个时刻哪个寄存器不会冲突,所以分配不同寄存器的方法不可取.那么,要怎么办呢 ? 这样就行了 : sbit sigl = P17; void func1() static char data i; while(1) i = 5; do sigl = !sigl; task_switch(); while(-i); void func2() static char data i; while(1) i = 5; do sigl = !sigl; task_switch(); while(-i); 将两个函数中的变量通通改成静态就行了.还可以这么做 : sbit sigl = P17; void func1() register char data i; while(1) i = 5; do sigl = !sigl; while(-i); task_switch(); 名师资料总结 - - -精品资料欢迎下载 - - - - - - - - - - - - - - - - - - 名师精心整理 - - - - - - - 第 14 页,共 17 页 - - - - - - - - - void func2() register char data i; while(1) i = 5; do sigl = !sigl; while(-i); task_switch(); 即,在变量的作用域内不切换任务,等变量用完了 ,再切换任务 .此时虽然两个任务仍然会互相破坏对方的寄存器内容,但对方已经不关心寄存器里的内容了. 以上所说的 ,就是变量覆盖 的问题 .现在我们系统地说说关于 变量覆盖 . 变量分两种 ,一种是全局变量 ,一种是局部变量 (在这里 ,寄存器变量算到局部变量里). 对于全局变量 ,每个变量都会分配到单独的地址. 而对于局部变量 ,KEIL会做一个 覆盖优化 ,即没有直接调用关系的函数的变量共用空间.由于不是同时使用 ,所以不会冲突 ,这对内存小的 51 来说,是好事 . 但现在我们进入多任务的世界了,这就意味着两个没有直接调用关系的函数其实是并列执行的,空间不能共用了 .怎么办呢 ?一种笨办法是关掉覆盖优化功能.呵呵,的确很笨 . 比较简单易行一个解决办法是,不关闭覆盖优化 ,但将那些在作用域内需要跨越任务(换句话说就是在变量用完前会调用task_switch()函数的 )变量通通改成静态 (static)即可.这里要对初学者提一下 ,静态你可以理解为 全局 ,因为它的地址空间一直保留,但它又不是全局 ,它只能在定义它的那个花括号对 里访问 . 静态变量有个副作用,就是即使函数退出了,仍会占着内存 .所以写任务函数的时候,尽量在变量作用域结束后才切换任务,除非这个变量的作用域很长(时间上长 ),会影响到其它任务的实时性.只有在这种情况下才考虑在变量作用域内跨越任务,并将变量申明为静态 . 事实上 ,只要编程思路比较清析 ,很少有变量需要跨越任务的.就是说 ,静态变量并不多 . 说完了 覆盖我们再说说 重入 . 所谓重入 ,就是一个函数在同一时刻有两个不同的进程复本.对初学者来说可能不好理解,我举个例子吧 : 有一个函数在主程序会被调用,在中断里也会被调用 ,假如正当在主程序里调用时,中断发生了,会发生什么情况 ? void func1() static char data i; i = 5; do 名师资料总结 - - -精品资料欢迎下载 - - - - - - - - - - - - - - - - - - 名师精心整理 - - - - - - - 第 15 页,共 17 页 - - - - - - - - - sigl = !sigl; while(-i); 假定 func1()正执行到 i=3 时,中断发生 ,一旦中断调用到func1()时,i 的值就被破坏了 ,当中断结束后 ,i = 0. 以上说的是在传统的单任务系统中,所以重入的机率不是很大 .但在多任务系统中 ,很容易发生重入 ,看下面的例子 : void func1() . delay(); . void func2() . delay(); . void delay() static unsigned char i;/注意这里是申明为static,不申明 static 的话会发生覆盖问题.而申明为static 会发生重入问题 .麻烦啊for(i=0;i10;i+) task_switch(); 两个并行执行的任务都调用了delay(),这就叫重入 .问题在于重入后的两个复本都依赖变量i来控制循环 ,而该变量跨越了任务 ,这样,两个任务都会修改i 值了. 重入只能以防为主 ,就是说尽量不要让重入发生,比如将代码改成下面的样子: #define delay() static unsigned char i; for(i=0;i10;i+) task_switch();/i 仍定义为 static,但实际上已经不是同一个函数了 ,所以分配的地址不同 . void func1() . delay(); . void func2() . delay(); . 用宏来代替函数 ,就意味着每个调用处都是一个独立的代码复本,那么两个delay 实际使用的内存地址也就不同了 ,重入问题消失 . 名师资料总结 - - -精品资料欢迎下载 - - - - - - - - - - - - - - - - - -

    注意事项

    本文(2022年2022年给DIY超轻量级多任务操作系统 .pdf)为本站会员(Che****ry)主动上传,淘文阁 - 分享文档赚钱的网站仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对上载内容本身不做任何修改或编辑。 若此文所含内容侵犯了您的版权或隐私,请立即通知淘文阁 - 分享文档赚钱的网站(点击联系客服),我们立即给予删除!

    温馨提示:如果因为网速或其他原因下载失败请重新下载,重复下载不扣分。




    关于淘文阁 - 版权申诉 - 用户使用规则 - 积分规则 - 联系我们

    本站为文档C TO C交易模式,本站只提供存储空间、用户上传的文档直接被用户下载,本站只是中间服务平台,本站所有文档下载所得的收益归上传人(含作者)所有。本站仅对用户上传内容的表现方式做保护处理,对上载内容本身不做任何修改或编辑。若文档所含内容侵犯了您的版权或隐私,请立即通知淘文阁网,我们立即给予删除!客服QQ:136780468 微信:18945177775 电话:18904686070

    工信部备案号:黑ICP备15003705号 © 2020-2023 www.taowenge.com 淘文阁 

    收起
    展开