windows7多任务是指指在一个操作系统中多

单/多任务操作系统【技术永存吧】_百度贴吧
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&签到排名:今日本吧第个签到,本吧因你更精彩,明天继续来努力!
本吧签到人数:0可签7级以上的吧50个
本月漏签0次!成为超级会员,赠送8张补签卡连续签到:天&&累计签到:天超级会员单次开通12个月以上,赠送连续签到卡3张
关注:64贴子:
单/多任务操作系统
说出来难以想象,Windows原来就是虚拟机!看来我说的“同学没用过虚拟机”是错误的了问题是。。。Linux,OS/2,Mac OS x是怎么实现的呢?
单用户单任务操作系统:是指一台计算机同时只能有一个用户在使用,该用户一次只能提交一个作业,一个用户独自享用系统的全部硬件和软件资源。(如:MS-DOS、PC-DOS、CP/M)单用户多任务操作系统:是指一台计算机同时只能有一个用户使用,但该用户一次可以运行或提交多个作业。(如:windows)(window2000和XP都可以有多个用户,为什么还是单用户呢?因为windows虽然可以设置多个用户,但是同一时候只能让一个用户独享系统的所有资源,所以windows只能是单用户操作系统。因为“多用户”的定义是:允许多个用户通过各自的终端,使用同一台主机,共享主机系统的各类资源)多用户与多任务操作系统:是指一台计算机可以同时有多个用户同时使用,并且同时可以执行由多个用户提交的多个任务(如Unix/Linux等)基本信息所谓虚拟机实际上指的是由Windows在内存中创建的逻辑微机,由它来运行应用程序。当Windows接受到由鼠标器、键盘、定时器信号或某些I/O操作产生的&事件&后,为该任务分配CPU时间。每个任务(应用程序)使用由Windows分配的短暂的时间片(Timeslice)轮流使用CPU,由于CPU对每个时间片的处理速度非常快,在用户看来好像这些任务在同时执行。但是,在使用Windows 3.X时,可能有不少用户都有过这样的经历,即若遇到某一任务占有 CPU而不释放,用户就束手无策了,或者无可奈何地等待,或者重新启动机器,多任务被迫中止 ,那些正在进行的任务中的信息无法保留,造成工作损失。在Windows 95中,这种情况得到了很大改善,这是因为虽然Windows 3.X和Windows 95都支持多任务,但它们所采用的处理方式是不一样的。特点听语音在Windows 3.X中,采用的是协同式多任务方式,运行的是16位应用程序,而这些16位的应用程序在Windows 3.X多任务环境下使用的是同一个虚拟机,在一个时间片结束的时候,系统要求这个任务要&主动&地把计算机控制权交还给Windows的调度程序。这种多任务工作方式对于符合&协同式运行&规则编写的程序,一般不会出现什么问题。但是如果运行的是某些设计得不好的程序,就可能出现某个应用程序不把控制权交还或者需要很长时间才能交还的情况,这时Windows 3.X的调度程序将会等待下去,用户可以看到沙漏一直出现在屏幕上,用户此时只能等待。最坏的情况就是造成死机。Windows 95则不同,它是一个32位的操作系统,在多任务工作方式时,它是将每个32位应用程序及DOS应用程序分别放在各自的虚拟机中运行,内存中的每台虚拟机都相当于一台完整的微机,由虚拟机管理器(Virtual Machine Manager)负责分配给每个虚拟机一定的资源。当多个任务同时运行时,Windows能够根据需要把控制权收回并转给其它的应用程序而不管当前的应用程序是否释放CPU,这就是所谓的抢先式多任务工作方式。当Windows95要抢先正在执行的某个应用程序时,它首先挂起处理该程序的虚拟机,使它在后台运行,然后把系统控制权交给其它应用程序的虚拟机,使这个应用程序能够被优先处理。使用抢先式多任务工作方式使得32位Windows应用程序和DOS应用程序能够平等地共享C PU资源,消除了Windows 3.X单个任务执行时独占系统资源的现象,提高了应用程序的执行效率和速度,也使用户有可能摆脱前文所提到的困境。在Windows 95运行时,用户可以根据鼠标指针的变化了解各个任务所处的状态。当鼠标指针在某个窗口变成沙漏指针时,用户不能对该窗口进行操作。但可以把鼠标指针移到该窗口以外,这时鼠标指针有可能变成箭头或箭头与沙漏的组合,表示用户此时可以进行抢先操作而不必像在Windows 3.X中那样处在无奈的等待中。当发现某个任务意外死锁或终止时,我们可以利用Windows 95的这种抢先式多任务的特性,先将其它任务正常结束,再想办法处理出了问题的程序,以减少工作损失。
因为win1.0---win9x都是但用户的
win1.0--win3.x叫“多任务协作式操作系统”win9x--win10.0叫“多任务抢占式操作系统”linux 2.6-叫 “多任务抢占式操作系统”linux 2.6+叫“有抢占式内核的多任务抢占式操作系统”
第一句话:Windows不是虚拟机
提示,文章有错误Windows )开始Windows是毋庸置疑的多用户系统每启动一个用户,win32子系统就创建一个 winlogon.exe 进程负责这个用户终端允许多个用户同时运行win9x的“用户”根本不是用户,虽然叫“用户”,但不具备用户的各种特征,所以不是多任务操作系统
Excuse me?
大错特错虚拟机 顾名思义 是虚拟一个机器操作系统的身份是什么?操作系统 其实像一个服务器!处理来自应用程序和中断控制器的请求他并没有虚拟一个机器,来运行应用程序,应用程序是谁执行的,是CPU执行的,只是特权级为R3应用程序访问一个内存地址,访问一个寄存器,是谁访问的?是CPU访问的,这个过程有操作系统的影子么?!没有!你再想一想平时用的虚拟机,甚至可以想一想java虚拟机JVM在这些虚拟机上运行的操作系统/应用程序是直接访问CPU的寄存器和主板上的内存条了吗不是!他们访问的都是虚拟出来的寄存器,虚拟出来的内存这个硬件要是按照他的说法,我还可以说CPU也是虚拟机,同一类型的CPU提供同一套指令集,那也为应用程序提供了一个同一的接口啊,你咋不说CPU是虚拟机呢提供一个软件层就是虚拟机了?我还说SSL是一个软件层,很多协议基于这个软件层,你咋不说OpenSSL、NSS、GnuTLS……是虚拟机呢
贴吧热议榜
使用签名档&&
保存至快速回贴今天我们就来为大家讲解什么是多用户操作系统,让大家更加了解电脑这方面的基础知识
多用户操作系统指的是什么?多用操作系统包括哪几大操作系统?多用户操作系统和单用户操作系统的最大区别在哪里?今天脚本之家就来为大家讲解什么是多用户操作系统,让大家更加了解电脑这方面的基础知识。
一、什么是多用户?
当然想知道多用户操作系统的话,先了解什么是多用户,什么是单用户,这里还是以电脑系统为例,多用户就是多个用户在一台电脑上可以建立多个用户,对于windows 7系统就是多用户。而如果1台电脑只能使用一个用户的话,就称之为单用户,比如win98就是单用户。
二、多用户操作系统的含义:
多任务操作系统一般来讲就是分时操作系统,若干终端联机使用同一台电脑。终端机只是一个输入输出设备(比如只有键盘、显示器、打印机),没有运算与存储能力。每个用户通过各自的终端机使用同一台计算机,计算机按设定好的时间片轮转为各个终端服务,使得每个用户感觉只有自己一人在使用计算机。多任务操作系统具有很强的交互性能。
三、什么是多任务操作系统?
假如用户在同一时间可以运行多个应用程序(每个应用程序被称作一个任务),则这样的操作系统被称为多任务操作系统。如果一个用户在同一时间只能运行一个应用程序,则对应的操作系统称为单任务操作系统。而多任务也就是同时做多件事比如一边听歌一边玩游戏,比如windowsxp就是多任务操作系统。
四、多用户操作系统主要包括哪些?
现在常用的Windows操作系统都是多用户的操作系统,使用最广泛的的win7是多用户操作系统,其次还包括如UNIX、Linux操作系统属多用户多任务操作系统。关于单用户操作系统包括Microsoft 的MS DOS,windows 9X(95,98,ME),windows 2000,windows 2003,windows XP,这些都为单用户操作系统。51单片机多任务操作系统的原理与实现
> 51单片机多任务操作系统的原理与实现
51单片机多任务操作系统的原理与实现
  前言本文引用地址:  想了很久,要不要写这篇文章?最后觉得对感兴趣的人还是很多,写吧.我不一定能造出玉,但我可以抛出砖.  包括我在内的很多人都对使用呈悲观态度,因为的片上资源太少.但对于很多要求不高的系统来说,使用可以使代码变得更直观,易于维护,所以在上仍有操作系统的生存机会.  流行的uCos,Tiny51等,其实都不适合在2051这样的片子上用,占资源较多,唯有自已动手,以不变应万变,才能让51也有操作系统可用.这篇贴子的目的,是教会大家如何现场写一个OS,而不是给大家提供一个OS版本.提供的所有代码,也都是示例代码,所以不要因为它没什么功能就说LAJI之类的话.如果把功能写全了,一来估计你也不想看了,二来也失去灵活性没有价值了.  下面的贴一个示例出来,可以清楚的看到,OS本身只有不到10行源代码,编译后的目标代码60字节,任务切换消耗为20个机器周期.相比之下,KEIL内嵌的TINY51目标代码为800字节,切换消耗100~700周期.唯一不足之处是,每个任务要占用掉十几字节的堆栈,所以任务数不能太多,用在128B内存的51里有点难度,但对于52来说问题不大.这套代码在36M主频的STC12C4052上实测,切换任务仅需2uS.  #include  #define&MAX_TASKS&2&//任务槽个数.必须和实际任务数一至  #define&MAX_TASK_DEP&12&//最大栈深.最低不得少于2个,保守值为12.  unsigned&char&idata&task_stack[MAX_TASKS][MAX_TASK_DEP];&//任务堆栈.  unsigned&char&task_&//当前活动任务号  //任务切换函数(任务调度器)  void&task_switch(){  task_sp[task_id]&=&SP;  if(++task_id&==&MAX_TASKS)  task_id&=&0;  SP&=&task_sp[task_id];  }  //任务装入函数.将指定的函数(参数1)装入指定(参数2)的任务槽中.如果该槽中原来就有任务,则原任务丢失,但系统本身不会发生错误.  void&task_load(unsigned&int&fn,&unsigned&char&tid)  {  task_sp[tid]&=&task_stack[tid]&+&1;  task_stack[tid][0]&=&(unsigned&int)fn&&&0  task_stack[tid][1]&=&(unsigned&int)fn&&&&8;  }  //从指定的任务开始运行任务调度.调用该宏后,将永不返回.  #define&os_start(tid)&{task_id&=&tid,SP&=&task_sp[tid];}  /*======================以下为测试代码======================*/  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);  }  这样一个简单的多任务系统虽然不能称得上真正的操作系统,但只要你了解了它的原理,就能轻易地将它扩展得非常强大,想知道要如何做吗?  一.什么是操作系统?  人脑比较容易接受&类比&这种表达方式,我就用&公交系统&来类比&操作系统&吧.  当我们要解决一个问题的时候,是用某种处理手段去完成它,这就是我们常说的&方法&,计算机里叫&程序&(有时候也可以叫它&算法&).  以出行为例,当我们要从A地走到B地的时候,可以走着去,也可以飞着去,可以走直线,也可以绕弯路,只要能从A地到B地,都叫作方法.这种从A地到B的需求,相当于计算机里的&任务&,而实现从A地到B地的方法,叫作&任务处理流程&  很显然,这些走法中,并不是每种都合理,有些傻子都会采用的,有些是傻子都不采会用的.用计算机的话来说就是,有的任务处理流程好,有的任务处理流程好,有的处理流程差.  可以归纳出这么几种真正算得上方法的方法:  有些走法比较快速,适合于赶时间的人;有些走法比较省事,适合于懒人;有些走法比较便宜,适合于穷人.  用计算机的话说就是,有些省CPU,有些流程简单,有些对系统资源要求低.  现在我们可以看到一个问题:  如果全世界所有的资源给你一个人用(单任务独占全部资源),那最适合你需求的方法就是好方法.但事实上要外出的人很多,例如10个人(10个任务),却只有1辆车(1套资源),这叫作&资源争用&.  如果每个人都要使用最适合他需求的方法,那司机就只好给他们一人跑一趟了,而在任一时刻里,车上只有一个乘客.这叫作&顺序执行&,我们可以看到这种方法对系统资源的浪费是严重的.  如果我们没有法力将1台车变成10台车来送这10个人,就只好制定一些机制和约定,让1台车看起来像10台车,来解决这个问题的办法想必大家都知道,那就是制定公交线路.  最简单的办法是将所有旅客需要走的起点与终点串成一条线,车在这条线上开,乘客则自已决定上下车.这就是最简单的公交线路.它很差劲,但起码解决客人们对车争用.对应到计算机里,就是把所有任务的代码混在一起执行.  这样做既不优异雅,也没效率,于是司机想了个办法,把这些客户叫到一起商量,将所有客人出行的起点与终点罗列出来,统计这些线路的使用频度,然后制定出公交线路:有些路线可以合并起来成为一条线路,而那些不能合并的路线,则另行开辟行车车次,这叫作&任务定义&.另外,对于人多路线,车次排多点,时间上也优先安排,这叫作&任务优先级&.  经过这样的安排后,虽然仍只有一辆车,但运载能力却大多了.这套车次/路线的按排,就是一套&公交系统&.哈,知道什么叫操作系统了吧?它也就是这么样的一种约定.  操作系统:  我们先回过头归纳一下:  汽车&系统资源.主要指的是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来处理的,缓存也非常有限,甚至没有缓存.一旦信息到达,CPU必须在极短的时间内响应,否则信息就会丢失.  就拿串口通信来举例,在标准的PC架构里,巨大的内存允许将信息保存足够长的时间.而对于MCU来说内存有限,例如51仅有128字节内存,还要扣除掉寄存器组占用掉的8~32个字节,所以通常都仅用几个字节来缓冲.当然,你可以将数据的接收与处理的过程合并,但对于一个操作系统来说,不推荐这么做.  假定以115200bps通信速率向MCU传数据,则每个字节的传送时间约为9uS,假定缓存为8字节,则串口处理任务必须在70uS内响应.  这两个问题都指向了同一种解决思路:操作系统必须轻量轻量再轻量,最好是不占资源(那当然是做梦啦).  可用于MCU的操作系统很多,但适合51(这里的51专指无扩展内存的51)几乎没有.前阵子见过一个&圈圈操作系统&,那是我所见过的操作系统里最轻量的,但仍有改进的余地.  很多人认为,51根本不适合使用操作系统.其实我对这种说法并不完全接受,否则也没有这篇文章了.  我的看法是,51不适合采用&通用操作系统&.所谓通用操作系统就是,不论你是什么样的应用需求,也不管你用什么芯片,只要你是51,通通用同一个操作系统.  这种想法对于PC来说没问题,对于嵌入式来说也不错,对AVR来说还凑合,而对于51这种&贫穷型&的MCU来说,不行.  怎样行?量体裁衣,现场根据需求构建一个操作系统出来!  看到这里,估计很多人要翻白眼了,大体上两种:  1.操作系统那么复杂,说造就造,当自已是神了?  2.操作系统那么复杂,现场造一个会不会出BUG?  哈哈,看清楚了?问题出在&复杂&上面,如果操作系统不复杂,问题不就解决了?  事实上,很多人对操作系统的理解是片面的,操作系统不一定要做得很复杂很全面,就算仅个多任务并行管理能力,你也可以称它操作系统.  只要你对多任务并行的原理有所了解,就不难现场写一个出来,而一旦你做到了这一点,为各任务间安排通信约定,使之发展成一个为你的应用系统量身定做的操作系统也就不难了.  为了加深对操作系统的理解,可以看一看&&演变&&这份PPT,让你充分了解一个并行多任务是如何一步步从顺序流程演变过来的.里面还提到了很多人都在用的&状态机&,你会发现操作系统跟状态机从原理上其实是多么相似.会用状态机写程序,都能写出操作系统.  三.我的第一个操作系统  直接进入主题,先贴一个操作系统的示范出来.大家可以看到,原来操作系统可以做得么简单.  当然,这里要申明一下,这玩意儿其实算不上真正的操作系统,它除了并行多任务并行外根本没有别的功能.但凡事都从简单开始,搞懂了它,就能根据应用需求,将它扩展成一个真正的操作系统.  好了,代码来了.  将下面的代码直接放到KEIL里编译,在每个task?()函数的&task_switch();&那里打上断点,就可以看到它们的确是&同时&在执行的.  #include  #define&MAX_TASKS&2&//任务槽个数.必须和实际任务数一至  #define&MAX_TASK_DEP&12&//最大栈深.最低不得少于2个,保守值为12.  unsigned&char&idata&task_stack[MAX_TASKS][MAX_TASK_DEP];//任务堆栈.  unsigned&char&task_&//当前活动任务号
分享给小伙伴们:
我来说两句……
最新技术贴
微信公众号二
微信公众号一百度题库旨在为考生提供高效的智能备考服务,全面覆盖中小学财会类、建筑工程、职业资格、医卫类、计算机类等领域。拥有优质丰富的学习资料和备考全阶段的高效服务,助您不断前行!
京ICP证号&&
京网文[3号&&
Copyright (C) 2017 Baidu写在前面的话:这篇文章值得看十遍。分享一下
51单片机多任务操作系统的原理与实现
--&一个超轻量级的操作系统
想了很久,要不要写这篇文章?最后觉得对操作系统感兴趣的人还是很多,写吧.我不一定能造出玉,但我可以抛出砖.
包括我在内的很多人都对51使用操作系统呈悲观态度,因为51的片上资源太少.但对于很多要求不高的系统来说,使用操作系统可以使代码变得更直观,易于维护,所以在51上仍有操作系统的生存机会.&
流行的uCos,Tiny51等,其实都不适合在2051这样的片子上用,占资源较多,唯有自已动手,以不变应万变,才能让51也有操作系统可用.这篇贴子的目的,是教会大家如何现场写一个OS,而不是给大家提供一个OS版本.提供的所有代码,也都是示例代码,所以不要因为它没什么功能就说LAJI之类的话.如果把功能写全了,一来估计你也不想看了,二来也失去灵活性没有价值了.&
下面的贴一个示例出来,可以清楚的看到,OS本身只有不到10行源代码,编译后的目标代码60字节,任务切换消耗为20个机器周期.相比之下,KEIL内嵌的TINY51目标代码为800字节,切换消耗100~700周期.唯一不足之处是,每个任务要占用掉十几字节的堆栈,所以任务数不能太多,用在128B内存的51里有点难度,但对于52来说问题不大.这套代码在36M主频的STC12C4052上实测,切换任务仅需2uS.&
#include&&reg51.h&&&
#define&MAX_TASKS&2&&&&&&&&&&//任务槽个数.必须和实际任务数一至&&
#define&MAX_TASK_DEP&12&&&&&&//最大栈深.最低不得少于2个,保守值为12.&&
unsigned&char&idata&task_stack[MAX_TASKS][MAX_TASK_DEP];&&//任务堆栈.&&
unsigned&char&task_&&&&&&&&//当前活动任务号&&
//任务切换函数(任务调度器)&&
void&task_switch(){&&
&&&&&&&&task_sp[task_id]&=&SP;&&
&&&&&&&&if(++task_id&==&MAX_TASKS)&&
&&&&&&&&&&&&&&&&task_id&=&0;&&
&&&&&&&&SP&=&task_sp[task_id];&&
//任务装入函数.将指定的函数(参数1)装入指定(参数2)的任务槽中.如果该槽中原来就有任务,则原任务丢失,但系统本身不会发生错误.&&
void&task_load(unsigned&int&fn,&unsigned&char&tid)
&&&&&&&&task_sp[tid]&=&task_stack[tid]&+&1;&&
&&&&&&&&task_stack[tid][0]&=&(unsigned&int)fn&&&0&
&&&&&&&&task_stack[tid][1]&=&(unsigned&int)fn&&&&8;&&
//从指定的任务开始运行任务调度.调用该宏后,将永不返回.&&
#define&os_start(tid)&{task_id&=&tid,SP&=&task_sp[tid];}&&
/*======================以下为测试代码======================*/&&
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);&&
这样一个简单的多任务系统虽然不能称得上真正的操作系统,但只要你了解了它的原理,就能轻易地将它扩展得非常强大,想知道要如何做吗?
一.什么是操作系统?
人脑比较容易接受&类比&这种表达方式,我就用&公交系统&来类比&操作系统&吧.&
当我们要解决一个问题的时候,是用某种处理手段去完成它,这就是我们常说的&方法&,计算机里叫&程序&(有时候也可以叫它&算法&).&
以出行为例,当我们要从A地走到B地的时候,可以走着去,也可以飞着去,可以走直线,也可以绕弯路,只要能从A地到B地,都叫作方法.这种从A地到B的需求,相当于计算机里的&任务&,而实现从A地到B地的方法,叫作&任务处理流程&&
很显然,这些走法中,并不是每种都合理,有些傻子都会采用的,有些是傻子都不采会用的.用计算机的话来说就是,有的任务处理流程好,有的任务处理流程好,有的处理流程差.&
可以归纳出这么几种真正算得上方法的方法:&
有些走法比较快速,适合于赶时间的人;有些走法比较省事,适合于懒人;有些走法比较便宜,适合于穷人.&
用计算机的话说就是,有些省CPU,有些流程简单,有些对系统资源要求低.&
现在我们可以看到一个问题:&
如果全世界所有的资源给你一个人用(单任务独占全部资源),那最适合你需求的方法就是好方法.但事实上要外出的人很多,例如10个人(10个任务),却只有1辆车(1套资源),这叫作&资源争用&.&
如果每个人都要使用最适合他需求的方法,那司机就只好给他们一人跑一趟了,而在任一时刻里,车上只有一个乘客.这叫作&顺序执行&,我们可以看到这种方法对系统资源的浪费是严重的.&
如果我们没有法力将1台车变成10台车来送这10个人,就只好制定一些机制和约定,让1台车看起来像10台车,来解决这个问题的办法想必大家都知道,那就是制定公交线路.&
最简单的办法是将所有旅客需要走的起点与终点串成一条线,车在这条线上开,乘客则自已决定上下车.这就是最简单的公交线路.它很差劲,但起码解决客人们对车争用.对应到计算机里,就是把所有任务的代码混在一起执行.&
这样做既不优异雅,也没效率,于是司机想了个办法,把这些客户叫到一起商量,将所有客人出行的起点与终点罗列出来,统计这些线路的使用频度,然后制定出公交线路:有些路线可以合并起来成为一条线路,而那些不能合并的路线,则另行开辟行车车次,这叫作&任务定义&.另外,对于人多路线,车次排多点,时间上也优先安排,这叫作&任务优先级&.&
经过这样的安排后,虽然仍只有一辆车,但运载能力却大多了.这套车次/路线的按排,就是一套&公交系统&.哈,知道什么叫操作系统了吧?它也就是这么样的一种约定.&
操作系统:&
我们先回过头归纳一下:&
汽车&&&&&&&&&&&&&&&&&&&&&&系统资源.主要指的是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来处理的,缓存也非常有限,甚至没有缓存.一旦信息到达,CPU必须在极短的时间内响应,否则信息就会丢失.&
就拿串口通信来举例,在标准的PC架构里,巨大的内存允许将信息保存足够长的时间.而对于MCU来说内存有限,例如51仅有128字节内存,还要扣除掉寄存器组占用掉的8~32个字节,所以通常都仅用几个字节来缓冲.当然,你可以将数据的接收与处理的过程合并,但对于一个操作系统来说,不推荐这么做.&
假定以115200bps通信速率向MCU传数据,则每个字节的传送时间约为9uS,假定缓存为8字节,则串口处理任务必须在70uS内响应.&
这两个问题都指向了同一种解决思路:操作系统必须轻量轻量再轻量,最好是不占资源(那当然是做梦啦).&
可用于MCU的操作系统很多,但适合51(这里的51专指无扩展内存的51)几乎没有.前阵子见过一个&圈圈操作系统&,那是我所见过的操作系统里最轻量的,但仍有改进的余地.&
很多人认为,51根本不适合使用操作系统.其实我对这种说法并不完全接受,否则也没有这篇文章了.&
我的看法是,51不适合采用&通用操作系统&.所谓通用操作系统就是,不论你是什么样的应用需求,也不管你用什么芯片,只要你是51,通通用同一个操作系统.&
这种想法对于PC来说没问题,对于嵌入式来说也不错,对AVR来说还凑合,而对于51这种&贫穷型&的MCU来说,不行.&
怎样行?量体裁衣,现场根据需求构建一个操作系统出来!&
看到这里,估计很多人要翻白眼了,大体上两种:&
1.操作系统那么复杂,说造就造,当自已是神了?&
2.操作系统那么复杂,现场造一个会不会出BUG?&
哈哈,看清楚了?问题出在&复杂&上面,如果操作系统不复杂,问题不就解决了?&
事实上,很多人对操作系统的理解是片面的,操作系统不一定要做得很复杂很全面,就算仅个多任务并行管理能力,你也可以称它操作系统.&
只要你对多任务并行的原理有所了解,就不难现场写一个出来,而一旦你做到了这一点,为各任务间安排通信约定,使之发展成一个为你的应用系统量身定做的操作系统也就不难了.&
为了加深对操作系统的理解,可以看一看&&演变&&这份PPT,让你充分了解一个并行多任务是如何一步步从顺序流程演变过来的.里面还提到了很多人都在用的&状态机&,你会发现操作系统跟状态机从原理上其实是多么相似.会用状态机写程序,都能写出操作系统.
三.我的第一个操作系统
直接进入主题,先贴一个操作系统的示范出来.大家可以看到,原来操作系统可以做得么简单.&
当然,这里要申明一下,这玩意儿其实算不上真正的操作系统,它除了并行多任务并行外根本没有别的功能.但凡事都从简单开始,搞懂了它,就能根据应用需求,将它扩展成一个真正的操作系统.&
好了,代码来了.&
将下面的代码直接放到KEIL里编译,在每个task?()函数的&task_switch();&那里打上断点,就可以看到它们的确是&同时&在执行的.
#include&&reg51.h&&
#define&MAX_TASKS&2&&&&&&&&&&//任务槽个数.必须和实际任务数一至&
#define&MAX_TASK_DEP&12&&&&&&&//最大栈深.最低不得少于2个,保守值为12.
unsigned&char&idata&task_stack[MAX_TASKS][MAX_TASK_DEP];//任务堆栈.&
unsigned&char&task_&&&&&&&&&//当前活动任务号&
//任务切换函数(任务调度器)&
void&task_switch()
&&&&&&&&task_sp[task_id]&=&SP;&
&&&&&&&&if(++task_id&==&MAX_TASKS)&
&&&&&&&&&&&&&&&&task_id&=&0;&
&&&&&&&&SP&=&task_sp[task_id];&
//任务装入函数.将指定的函数(参数1)装入指定(参数2)的任务槽中.如果该槽中原来就有任务,则原任务丢失,但系统本身不会发生错误.&
void&task_load(unsigned&int&fn,&unsigned&char&tid)
&&&&&&&&task_sp[tid]&=&task_stack[tid]&+&1;&
&&&&&&&&task_stack[tid][0]&=&(unsigned&int)fn&&&0&
&&&&&&&&task_stack[tid][1]&=&(unsigned&int)fn&&&&8;&
//从指定的任务开始运行任务调度.调用该宏后,将永不返回.&
#define&os_start(tid)&{task_id&=&tid,SP&=&task_sp[tid];}&
/*==================以下为测试代码=====================*/&
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来跳回呢?&
很显然,CALL通过某种方法保存了打断前的某些信息,而在返回断点前执行的RET指令,就是用于取回这些信息.&
不用多说,大家都知道,&某些信息&就是PC指针,而&某种方法&就是压栈.&
很幸运,在51里,堆栈及堆栈指针都是可被任意修改的,只要你不怕死.那么假如在执行RET前将堆栈修改一下会如何?往下看:&
当程序执行CALL后,在子程序里将堆栈刚才压入的断点地址清除掉,并将一个函数的地址压入,那么执行完RET后,程序就跳到这个函数去了.&
事实上,只要我们在RET前将堆栈改掉,就能将程序跳到任务地方去,而不限于CALL里压入的地址.&
重点来了......&
首先我们得为每个任务单独开一块内存,这块内存专用于作为对应的任务的堆栈,想将CPU交给哪个任务,只需将栈指针指向谁内存块就行了.&
接下来我们构造一个这样的函数:&
当任务调用该函数时,将当前的堆栈指针保存一个变量里,并换上另一个任务的堆栈指针.这就是任务调度器了.
OK了,现在我们只要正确的填充好这几个堆栈的原始内容,再调用这个函数,这个任务调度就能运行起来了.&
那么这几个堆栈里的原始内容是哪里来的呢?这就是&任务装载&函数要干的事了.&
在启动任务调度前将各个任务函数的入口地址放在上面所说的&任务专用的内存块&里就行了!对了,顺便说一下,这个&任务专用的内存块&叫作&私栈&,私栈的意思就是说,每个任务的堆栈都是私有的,每个任务都有一个自已的堆栈.&
话都说到这份上了,相信大家也明白要怎么做了:&
1.分配若干个内存块,每个内存块为若干字节:&
这里所说的&若干个内存块&就是私栈,要想同时运行几少个任务就得分配多少块.而&每个子内存块若干字节&就是栈深.记住,每调一层子程序需要2字节.如果不考虑中断,4层调用深度,也就是8字节栈深应该差不多了.&
unsigned&char&idata&task_stack[MAX_TASKS][MAX_TASK_DEP]&
当然,还有件事不能忘,就是堆指针的保存处.不然光有堆栈怎么知道应该从哪个地址取数据啊&
unsigned&char&idata&task_sp[MAX_TASKS]&
上面两项用于装任务信息的区域,我们给它个概念叫&任务槽&.有些人叫它&任务堆&,我觉得还是&槽&比较直观&
对了,还有任务号.不然怎么知道当前运行的是哪个任务呢?&
unsigned&char&task_id&
当前运行存放在1号槽的任务时,这个值就是1,运行2号槽的任务时,这个值就是2....&
2.构造任务调度函函数:&
void&task_switch()
&&&&&&&&task_sp[task_id]&=&SP;&&&&&&&//保存当前任务的栈指针&
&&&&&&&&if(++task_id&==&MAX_TASKS)&&&&//任务号切换到下一个任务
&&&&&&&&&&&&&&&&task_id&=&0;&
&&&&&&&&SP&=&task_sp[task_id];&&&&&&//将系统的栈指针指向下个任务的私栈.&
3.装载任务:&
将各任务的函数地址的低字节和高字节分别入在&
task_stack[任务号][0]和task_stack[任务号][1]中:&
为了便于使用,写一个函数:&&task_load(函数名,&任务号)&
void&task_load(unsigned&int&fn,&unsigned&char&tid)
&&&&&&&&task_sp[tid]&=&task_stack[tid]&+&1;&
&&&&&&&&task_stack[tid][0]&=&(unsigned&int)fn&&&0&
&&&&&&&&task_stack[tid][1]&=&(unsigned&int)fn&&&&8;&
4.启动任务调度器:&
将栈指针指向任意一个任务的私栈,执行RET指令.注意,这可很有学问的哦,没玩过堆栈的人脑子有点转不弯:这一RET,RET到哪去了?嘿嘿,别忘了在RET前已经将堆栈指针指向一个函数的入口了.你别把RET看成RET,你把它看成是另一种类型的JMP就好理解了.&
SP&=&task_sp[任务号];&
做完这4件事后,任务&并行&执行就开始了.你可以象写普通函数一个写任务函数,只需(目前可以这么说)注意在适当的时候(例如以前调延时的地方)调用一下task_switch(),以让出CPU控制权给别的任务就行了.&
最后说下效率问题.&
这个多任务系统的开销是每次切换消耗20个机器周期(CALL和RET都算在内了),贵吗?不算贵,对于很多用状态机方式实现的多任务系统来说,其实效率还没这么高---&case&switch和if()可不像你想像中那么便宜.&
关于内存的消耗我要说的是,当然不能否认这种多任务机制的确很占内存.但建议大家不要老盯着编译器下面的那行字&DATA&=&XXXbyte&.那个值没意义,堆栈没算进去.关于比较省内存多任务机制,我将来会说到.&
概括来说,这个多任务系统适用于实时性要求较高而内存需求不大的应用场合,我在运行于36M主频的STC12C4052上实测了一把,切换一个任务不到3微秒.&
下回我们讲讲用KEIL写多任务函数时要注意的事项.&
下下回我们讲讲如何增强这个多任务系统,跑步进入操作系统时代.
四.用KEIL写多任务系统的技巧与注意事项
C51编译器很多,KEIL是其中比较流行的一种.我列出的所有例子都必须在KEIL中使用.为何?不是因为KEIL好所以用它(当然它的确很棒),而是因为这里面用到了KEIL的一些特性,如果换到其它编译器下,通过编译的倒不是问题,但运行起来可能是堆栈错位,上下文丢失等各种要命的错误,因为每种编译器的特性并不相同.所以在这里先说清楚这一点.&
但是,我开头已经说了,这套帖子的主要目的是阐述原理,只要你能把这几个例子消化掉,那么也能够自已动手写出适合其它编译器的OS.&
好了,说说KEIL的特性吧,先看下面的函数:&
sbit&sigl&=&P1^7;&
void&func1()
&&&&&&&&register&char&data&i;&
&&&&&&&&i&=&5;&
&&&&&&&&do{
&&&&sigl&=&!&
&&&&&&&&&}while(--i);&
你会说,这个函数没什么特别的嘛!呵呵,别着急,你将它编译了,然后展开汇编代码再看看:&
&&&193:&void&func1(){&&
&&&194:&&&&&&&&&register&char&data&i;&&
&&&195:&&&&&&&&&i&=&5;&&
C:0x00C3&&&&7F05&&&&&MOV&&&&&&R7,#0x05&
&&&196:&&&&&&&&&do{&&
&&&197:&&&&&&&&&&&&&&&&&sigl&=&!&&
C:0x00C5&&&&B297&&&&&CPL&&&&&&sigl(0x90.7)&
&&&198:&&&&&&&&&}while(--i);&&
C:0x00C7&&&&DFFC&&&&&DJNZ&&&&&R7,C:00C5&
&&&199:&}&&
C:0x00C9&&&&22&&&&&&&RET&&&&&&&
看清楚了没?这个函数里用到了R7,却没有对R7进行保护!&
有人会跳起来了:这有什么值得奇怪的,因为上层函数里没用到R7啊.呵呵,你说的没错,但只说对了一半:事实上,KEIL编译器里作了约定,在调子函数前会尽可能释放掉所有寄存器.通常性况下,除了中断函数外,其它函数里都可以任意修改所有寄存器而无需先压栈保护(其实并不是这样,但现在暂时这样认为,饭要一口一口吃嘛,我很快会说到的).&
这个特性有什么用呢?有!当我们调用任务切换函数时,要保护的对象里可以把所有的寄存器排除掉了,就是说,只需要保护堆栈即可!&
现在我们回过头来看看之前例子里的任务切换函数:&
void&task_switch()
&&&&&&&&task_sp[task_id]&=&SP;&&&&&//保存当前任务的栈指针&
&&&&&&&&if(++task_id&==&MAX_TASKS)&&&//任务号切换到下一个任务&
&&&&&&&&&&&&&&&&task_id&=&0;&
&&&&&&&&SP&=&task_sp[task_id];&&&&&//将系统的栈指针指向下个任务的私栈.&
看到没,一个寄存器也没保护,展开汇编看看,的确没保护寄存器.&
好了,现在要给大家泼冷水了,看下面两个函数:&
void&func1()
&&&&&&&&register&char&data&i;&
&&&&&&&&i&=&5;&
&&&&&&&&do{&
&&&&&&&&&&&&&&&&sigl&=&!&
&&&&&&&&&}while(--i);&
void&func2()
&&&&&&&&register&char&data&i;&
&&&&&&&&i&=&5;&
&&&&&&&&do{&
&&&&&&&&&&&&&&&&func1();&
&&&&&&&&&}while(--i);&
父函数fun2()里调用func1(),展开汇编代码看看:&
&&&193:&void&func1(){&&
&&&194:&&&&&&&&&register&char&data&i;&&
&&&195:&&&&&&&&&i&=&5;&&
C:0x00C3&&&&7F05&&&&&MOV&&&&&&R7,#0x05&
&&&196:&&&&&&&&&do{&&
&&&197:&&&&&&&&&&&&&&&&&sigl&=&!&&
C:0x00C5&&&&B297&&&&&CPL&&&&&&sigl(0x90.7)&
&&&198:&&&&&&&&&}while(--i);&&
C:0x00C7&&&&DFFC&&&&&DJNZ&&&&&R7,C:00C5
&&&199:&}&&
C:0x00C9&&&&22&&&&&&&RET&&&&&&&
&&&200:&void&func2(){
&&&201:&&&&&&&&&register&char&data&i;&&
&&&202:&&&&&&&&&i&=&5;&&
C:0x00CA&&&&7E05&&&&&MOV&&&&&&R6,#0x05&
&&&203:&&&&&&&&&do{&
&&&204:&&&&&&&&&&&&&&&&&func1();&&
C:0x00CC&&&&11C3&&&&&ACALL&&&&func1(C:00C3)&
&&&205:&&&&&&&&&}while(--i);&
C:0x00CE&&&&DEFC&&&&&DJNZ&&&&&R6,C:00CC
&&&206:&}&&
C:0x00D0&&&&22&&&&&&&RET&&&&&&&
看清楚没?函数func2()里的变量使用了寄存器R6,而在func1和func2里都没保护.&
听到这里,你可能又要跳一跳了:func1()里并没有用到R6,干嘛要保护?没错,但编译器是怎么知道func1()没用到R6的呢?是从调用关系里推测出来的.&
一点都没错,KEIL会根据函数间的直接调用关系为各函数分配寄存器,既不用保护,又不会冲突,KEIL好棒哦!!等一下,先别高兴,换到多任务的环境里再试试:&
void&func1()
&&&&&&&&register&char&data&i;&
&&&&&&&&i&=&5;&
&&&&&&&&do{
&&&&&&&&&&&&&&&&sigl&=&!&
&&&&&&&&&}while(--i);&
void&func2()
&&&&&&&&register&char&data&i;&
&&&&&&&&i&=&5;&
&&&&&&&&do{&
&&&&&&&&&&&&&&&&sigl&=&!&
&&&&&&&&&}while(--i);&
展开汇编代码看看:&
&&&193:&void&func1(){&&
&&&194:&&&&&&&&&register&char&data&i;&&
&&&195:&&&&&&&&&i&=&5;&&
C:0x00C3&&&&7F05&&&&&MOV&&&&&&R7,#0x05&
&&&196:&&&&&&&&&do{
&&&197:&&&&&&&&&&&&&&&&&sigl&=&!&
C:0x00C5&&&&B297&&&&&CPL&&&&&&sigl(0x90.7)&
&&&198:&&&&&&&&&}while(--i);&
C:0x00C7&&&&DFFC&&&&&DJNZ&&&&&R7,C:00C5&
&&&199:&}&&
C:0x00C9&&&&22&&&&&&&RET&
&&&200:&void&func2(){&&
&&&201:&&&&&&&&&register&char&data&i;&
&&&202:&&&&&&&&&i&=&5;&&
C:0x00CA&&&&7F05&&&&&MOV&&&&&&R7,#0x05
&&&203:&&&&&&&&&do{&&
&&&204:&&&&&&&&&&&&&&&&&sigl&=&!&
C:0x00CC&&&&B297&&&&&CPL&&&&&&sigl(0x90.7)
&&&205:&&&&&&&&&}while(--i);&&
C:0x00CE&&&&DFFC&&&&&DJNZ&&&&&R7,C:00CC&
&&&206:&}&
C:0x00D0&&&&22&&&&&&&RET&
看到了吧?哈哈,这回神仙也算不出来了.因为两个函数没有了直接调用的关系,所以编译器认为它们之间不会产生冲突,结果分配了一对互相冲突的寄存器,当任务从func1()切换到func2()时,func1()中的寄存器内容就给破坏掉了.大家可以试着去编译一下下面的程序:&
sbit&sigl&=&P1^7;&
void&func1()
&&&&&&&&register&char&data&i;&
&&&&&&&&i&=&5;&
&&&&&&&&do{&
&&&&&&&&&&&&&&&&sigl&=&!&
&&&&&&&&&&&&&&&&task_switch();&
&&&&&&&&&}&while&(--i);&
void&func2()
&&&&&&&&register&char&data&i;&
&&&&&&&&i&=&5;&
&&&&&&&&do{&
&&&&&&&&&&&&&&&&sigl&=&!&
&&&&&&&&&&&&&&&&task_switch();&
&&&&&&&&&}while(--i);&
我们这里只是示例,所以仍可以通过手工分配不同的寄存器避免寄存器冲突,但在真实的应用中,由于任务间的切换是非常随机的,我们无法预知某个时刻哪个寄存器不会冲突,所以分配不同寄存器的方法不可取.那么,要怎么办呢?&
这样就行了:&
sbit&sigl&=&P1^7;&
void&func1()
&&&&&&&&static&char&data&i;&
&&&&&&&&while(1){
&&&&&&&&&&&&&&&&&&i&=&5;&
&&&&&&&&&&&&&&&&&&do{&
&&&&&&&&&&&&&&&&&&&&&&&&&&sigl&=&!&
&&&&&&&&&&&&&&&&&&&&&&&&&&task_switch();&
}while(--i);
&&&&&&&&&&}&
void&func2()
&&&&&&&&static&char&data&i;&
&&&&&&&&while(1){&
&&&&&&&&&&&&&&&&&&i&=&5;&
&&&&&&&&&&&&&&&&&&do{&
&&&&&&&&&&&&&&&&&&&&&&&&&&sigl&=&!&
&&&&&&&&&&&&&&&&&&&&&&&&&&task_switch();&
&&&&&&&&&&&&&&&&&&&&}while(--i);&
&&&&&&&&&&}
将两个函数中的变量通通改成静态就行了.还可以这么做:&
sbit&sigl&=&P1^7;&
void&func1()
&&&&&&&&register&char&data&i;&
&&&&&&&&while(1){&
&&&&&&&&&&&&&&&&&&i&=&5;&
&&&&&&&&&&&&&&&&&&do{
&&&&&&&&&&&&&&&&&&&&&&&&&&sigl&=&!&
&&&&&&&&&&&&&&&&&&&&}while(--i);&
&&&&&&&&&&&&&&&&&&task_switch();&
&&&&&&&&&&}&
void&func2()
&&&&&&&&register&char&data&i;&
&&&&&&&&while(1){&
&&&&&&&&&&&&&&&&&&i&=&5;&
&&&&&&&&&&&&&&&&&&do{&
&&&&&&&&&&&&&&&&&&&&&&&&&&sigl&=&!&
&&&&&&&&&&&&&&&&&&&&}while(--i);&
&&&&&&&&&&&&&&&&&&task_switch();&
&&&&&&&&&&}&
即,在变量的作用域内不切换任务,等变量用完了,再切换任务.此时虽然两个任务仍然会互相破坏对方的寄存器内容,但对方已经不关心寄存器里的内容了.&
以上所说的,就是&变量覆盖&的问题.现在我们系统地说说关于&变量覆盖&.&
变量分两种,一种是全局变量,一种是局部变量(在这里,寄存器变量算到局部变量里).&
对于全局变量,每个变量都会分配到单独的地址.&
而对于局部变量,KEIL会做一个&覆盖优化&,即没有直接调用关系的函数的变量共用空间.由于不是同时使用,所以不会冲突,这对内存小的51来说,是好事.&
但现在我们进入多任务的世界了,这就意味着两个没有直接调用关系的函数其实是并列执行的,空间不能共用了.怎么办呢?一种笨办法是关掉覆盖优化功能.呵呵,的确很笨.&
比较简单易行一个解决办法是,不关闭覆盖优化,但将那些在作用域内需要跨越任务(换句话说就是在变量用完前会调用task_switch()函数的)变量通通改成静态(static)即可.这里要对初学者提一下,&静态&你可以理解为&全局&,因为它的地址空间一直保留,但它又不是全局,它只能在定义它的那个花括号对{}里访问.&
静态变量有个副作用,就是即使函数退出了,仍会占着内存.所以写任务函数的时候,尽量在变量作用域结束后才切换任务,除非这个变量的作用域很长(时间上长),会影响到其它任务的实时性.只有在这种情况下才考虑在变量作用域内跨越任务,并将变量申明为静态.&
事实上,只要编程思路比较清析,很少有变量需要跨越任务的.就是说,静态变量并不多.&
说完了&覆盖&我们再说说&重入&.
所谓重入,就是一个函数在同一时刻有两个不同的进程复本.对初学者来说可能不好理解,我举个例子吧:&
有一个函数在主程序会被调用,在中断里也会被调用,假如正当在主程序里调用时,中断发生了,会发生什么情况?&
void&func1()
&&&&&&&&static&char&data&i;&
&&&&&&&&i&=&5;&
&&&&&&&&do{&
&&&&&&&&&&&&&&&&sigl&=&!&
&&&&&&&&&}while(--i);&
假定func1()正执行到i=3时,中断发生,一旦中断调用到func1()时,i的值就被破坏了,当中断结束后,i&==&0.&
以上说的是在传统的单任务系统中,所以重入的机率不是很大.但在多任务系统中,很容易发生重入,看下面的例子:&
void&func1()
void&func2()
void&delay()
&&&&&&&&static&unsigned&char&i;//注意这里是申明为static,不申明static的话会发生覆盖问题.而申明为static会发生重入问题.麻烦啊&
&&&&&&&&for(i=0;i&10;i++)&
&&&&&&&&&&&&&&&&task_switch();&
两个并行执行的任务都调用了delay(),这就叫重入.问题在于重入后的两个复本都依赖变量i来控制循环,而该变量跨越了任务,这样,两个任务都会修改i值了.&
重入只能以防为主,就是说尽量不要让重入发生,比如将代码改成下面的样子:&
#define&delay()&{static&unsigned&char&i;&for(i=0;i&10;i++)&task_switch();}//i仍定义为static,但实际上已经不是同一个函数了,所以分配的地址不同.&
void&func1()
void&func2()
用宏来代替函数,就意味着每个调用处都是一个独立的代码复本,那么两个delay实际使用的内存地址也就不同了,重入问题消失.&
但这种方法带来的问题是,每调用一次delay(),都会产生一个delay的目标代码,如果delay的代码很多,那就会造成大量的rom空间占用.有其它办法没?&
本人所知有限,只有最后一招了:&
void&delay()&reentrant
&&&&&&&&unsigned&char&i;&
&&&&&&&&for(i=0;i&10;i++)&
&&&&&&&&&&&&&&&&task_switch();&
加入reentrant申明后,该函数就可以支持重入.但小心使用,申明为重入后,函数效率极低!&
最后附带说下中断.因为没太多可说的,就不单独开章了.
中断跟普通的写法没什么区别,只不过在目前所示例的多任务系统里因为有堆栈的压力,所以要使用using来减少对堆栈的使用(顺便提下,也不要调用子函数,同样是为了减轻堆栈压力)&
用using,必须用#pragma&NOAREGS关闭掉绝对寄存器访问,如果中断里非要调用函数,连同函数也要放在#pragma&NOAREGS的作用域内.如例所示:&
#pragma&SAVE&
#pragma&NOAREGS&&//使用using时必须将绝对寄存器访问关闭&
void&clock_timer(void)&interrupt&1&using&1&//使用using是为了减轻堆栈的压力&
#pragma&RESTORE&
阅读(...) 评论()}

我要回帖

更多关于 windows7多任务是指 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信