5.3 协作式多任务操作系统
- 格式:ppt
- 大小:2.46 MB
- 文档页数:25
计算机操作系统中的多任务处理与调度算法随着计算机应用场景的多样化,多任务处理成为操作系统中必不可少的一部分。
在计算机操作系统中,多任务处理涉及到如何有效地利用计算机资源,使多个任务并发执行,以提高系统的运行效率和资源利用率。
为了实现多任务处理,操作系统需要采用相应的调度算法来决定任务的执行顺序和时间片分配,以确保系统的公平性、高效性和稳定性。
一、多任务处理的概念与分类多任务处理是指操作系统能够同时运行多个任务,使得这些任务在用户看来就像在同时执行一样。
根据任务之间的关系,多任务处理可以分为协作式和抢占式两种模式。
协作式多任务处理模式要求各个任务之间主动地协调与合作,每个任务必须主动释放CPU资源,否则其他任务无法执行。
这种模式的优点是实现简单,但是一旦有任务崩溃或者无限循环,将会导致整个系统崩溃。
抢占式多任务处理模式则由操作系统来决定任务的执行优先级和时间片分配,可以主动抢占正在执行的任务,使其他任务有机会执行。
这种模式的优点是能够保证系统的稳定性和响应性,但实现相对复杂。
二、常见的调度算法1. 先来先服务(FCFS)先来先服务是一种简单的调度算法,按照任务到达的顺序进行调度,即先到达的任务优先执行,直到该任务执行完毕或者发生阻塞。
优点是公平,缺点是无法应对执行时间较长的任务,可能导致其他任务的等待时间较长。
2. 最短作业优先(SJF)最短作业优先调度算法是根据任务的执行时间来进行调度,执行时间最短的任务先执行。
优点是能够减少平均等待时间,提高系统的执行效率,缺点是对执行时间较长的任务不公平。
3. 时间片轮转(RR)时间片轮转调度算法将任务划分为等长的时间片,每个任务按照时间片的顺序轮流执行,每个任务执行一个时间片后切换至下一个任务,循环往复。
优点是公平,能够保证每个任务都能获得一定的执行时间,同时也能快速响应用户的操作。
缺点是对于执行时间较长的任务,会造成一定的延迟。
4. 优先级调度优先级调度算法根据任务的优先级来进行调度,优先级高的任务先执行。
操作系统中的多任务处理
在操作系统中,多任务处理是指操作系统能够同时管理和执行多个任务的能力。
这种能力使得计算机系统能够更高效地利用资源,提高系统的响应速度和性能。
多任务处理有两种主要的方式:并发和并行。
并发是指在同一时间段内执行多
个任务,而并行是指在同一时刻执行多个任务。
在操作系统中,通常会采用并发的方式来实现多任务处理。
为了实现多任务处理,操作系统会使用一些技术和机制来管理和调度任务。
其
中最常见的技术包括进程管理、线程管理和调度算法。
进程是程序的执行实例,每个进程都有自己的独立地址空间和资源。
操作系统
通过进程管理来创建、删除和调度进程,确保它们能够正确地运行。
线程是在进程内部执行的轻量级任务,多个线程可以共享进程的资源,提高系统的并发性能。
线程管理负责创建和调度线程,保证它们能够协同工作完成任务。
调度算法是操作系统用来选择下一个要执行的任务的规则。
常见的调度算法包
括先来先服务、最短作业优先、时间片轮转和优先级调度等。
这些算法可以根据任务的特性和系统的需求来选择合适的调度方式,确保系统能够高效地完成任务。
通过多任务处理,操作系统能够更好地响应用户的请求,提高系统的利用率和
性能。
同时,多任务处理也能够提高系统的稳定性和可靠性,确保系统能够持续运行。
总的来说,操作系统中的多任务处理是一种重要的技朧,它可以提高系统的效
率和性能,提升用户体验,是现代计算机系统不可或缺的一部分。
通过合理地管理和调度任务,操作系统能够更好地满足用户的需求,提高系统的运行效率和稳定性。
操作系统的多任务处理操作系统是计算机硬件与应用程序之间的关系管理者,它负责调度和管理计算机资源,为应用程序提供必要的支持和服务。
而多任务处理作为操作系统的一个重要特性,在提高计算机效率和资源利用率方面具有重要作用。
本文将探讨操作系统的多任务处理机制及其应用。
一、多任务处理概述多任务处理是指操作系统能够同时执行多个任务,使得用户感觉像是多个任务在同时进行。
它分为并行处理和时间片轮转两种方式。
1. 并行处理并行处理是指在多处理器或多核处理器系统中,多个任务能够同时执行。
每个处理器或核心负责一个任务,通过并行计算、并发执行提高了整个系统的计算速度和效率。
2. 时间片轮转时间片轮转是指操作系统按照时间片(一小段时间)轮流分配给各个任务,使得多个任务可以交替执行。
每个任务在一个时间片内执行一段时间,然后让出CPU资源给其他任务,通过快速切换任务的方式,让用户感觉多个任务在同时进行。
二、多任务处理的实现机制为了实现多任务处理,操作系统需要具备以下几个重要的机制:1. 进程管理进程管理是指操作系统对进程的创建、调度、状态转换和销毁等操作。
操作系统为每个任务分配一个独立的进程,并利用进程调度算法按照一定的优先级和策略进行调度,确保每个任务都能够得到公平的执行机会。
2. 任务切换多任务处理需要操作系统具备快速任务切换的能力,以实现任务间的流畅转换。
当一个任务的时间片用尽或发生阻塞时,操作系统会迅速切换到下一个任务,保证多个任务都能够得到执行。
3. 资源分配操作系统需要合理地分配和管理CPU、内存、外设等计算机资源,以满足多个任务对资源的需求。
通过资源分配策略,操作系统能够为每个任务提供所需的资源,并确保资源的公平分配和高效利用。
三、多任务处理的应用多任务处理在操作系统中广泛应用于各种场景,提供了更加灵活和高效的计算环境。
1. 多用户环境在多用户环境下,多任务处理允许多个用户同时进行各自的操作和任务。
每个用户可以独立地运行自己的应用程序,而不会干扰其他用户的操作。
操作系统的多任务与多线程支持操作系统是计算机系统中最为核心的软件之一,它负责管理和控制计算机中的硬件资源以及运行程序的执行。
多任务和多线程是操作系统的两个重要特性,它们在提高计算机系统性能、资源利用率和用户体验等方面发挥着重要作用。
一、多任务支持多任务是指操作系统能够同时运行多个程序,并且给用户的感觉是这些程序在同时进行。
操作系统通过轮询或者中断的方式在不同程序之间进行切换,为每个程序分配一定的执行时间片,给用户一种同时运行多个程序的错觉。
多任务支持使得用户能够方便地在计算机上同时运行多个应用程序,例如同时打开多个浏览器窗口、编辑文档和播放音乐等。
同时,多任务也提高了计算机系统的资源利用率,因为在一个时间片内,操作系统可以将执行权交给其他程序,使得系统中的计算资源得到充分利用。
在多任务系统中,操作系统通过调度算法来决定每个程序的执行顺序和时间片大小。
常见的调度算法有先来先服务(FCFS)、时间片轮转、优先级调度等。
这些算法根据不同的系统需求和优先级策略来进行选择。
二、多线程支持多线程是指在一个程序内部,能够同时执行多个子任务或者称之为线程的部分。
多线程在一个进程内共享同一块内存空间,各个线程之间可以共享数据和资源,使得程序的并发度增加,进而提高系统的吞吐量和响应速度。
多线程支持使得程序在执行过程中能够以更高效的方式处理并发任务,因为线程之间切换的开销要远远小于进程之间的切换。
此外,多线程也能够简化程序的编写,通过将程序拆分为多个线程来处理不同的任务,使得程序的结构更加清晰和模块化。
在多线程系统中,操作系统需要提供线程的管理和调度功能。
通过线程调度算法,操作系统能够决定哪些线程先被执行、如何切换线程以及如何调整不同线程之间的优先级。
常见的线程调度算法有抢占式调度、协同式调度和时间片轮转等。
三、多任务与多线程的关系多任务和多线程是操作系统中相关但又具有不同概念和作用的特性。
多任务是指操作系统能够同时运行多个程序,而多线程是指一个程序内部可以同时执行多个线程。
51单片机多任务操作系统的原理与实现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 <>#define MAX_TASKS 2 须和实际任务数一至#define MAX_TASK_DEP 12 低不得少于2个,保守值为12.unsigned char idata task_stack[MAX_TASKS][MAX_TASK_DEP];unsigned char 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 & 0xff;task_stack[tid][1] = (unsigned int)fn >> 8;}用该宏后,将永不返回.#define os_start(tid) {task_id = tid,SP = task_sp[tid];return;}/*======================以下为测试代码======================*/void task1(){static unsigned char i;while(1){i++;task_switch(); 么是操作系统?人脑比较容易接受"类比"这种表达方式,我就用"公交系统"来类比"操作系统"吧.当我们要解决一个问题的时候,是用某种处理手段去完成它,这就是我们常说的"方法",计算机里叫"程序"(有时候也可以叫它"算法").以出行为例,当我们要从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_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 & 0xff;task_stack[tid][1] = (unsigned int)fn >> 8;}用该宏后,将永不返回.#define os_start(tid) {task_id = tid,SP = task_sp[tid];return;}/*==================以下为测试代码=====================*/void task1(){static unsigned char i;while(1){i++;task_switch();现在来看看这个多任务系统的原理:这个多任务系统准确来说,叫作"协同式多任务".所谓"协同式",指的是当一个任务持续运行而不释放资源时,其它任务是没有任何机会和方式获得运行机会,除非该任务主动释放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;}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 & 0xff;task_stack[tid][1] = (unsigned int)fn >> 8;}4.启动任务调度器:将栈指针指向任意一个任务的私栈,执行RET指令.注意,这可很有学问的哦,没玩过堆栈的人脑子有点转不弯:这一RET,RET到哪去了嘿嘿,别忘了在RET前已经将堆栈指针指向一个函数的入口了.你别把RET看成RET,你把它看成是另一种类型的JMP就好理解了.SP = task_sp[任务号];return;做完这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 = !sigl;}while(--i);}你会说,这个函数没什么特别的嘛!呵呵,别着急,你将它编译了,然后展开汇编代码再看看:193: void func1(){194: register char data i;195: i = 5;C:0x00C3 7F05 MOV R7,#0x05196: do{197: sigl = !sigl;C:0x00C5 B297 CPL sigl198: }while(--i);C:0x00C7 DFFC DJNZ R7,C:00C5199: }C:0x00C9 22 RET看清楚了没这个函数里用到了R7,却没有对R7进行保护!有人会跳起来了:这有什么值得奇怪的,因为上层函数里没用到R7啊.呵呵,你说的没错,但只说对了一半:事实上,KEIL编译器里作了约定,在调子函数前会尽可能释放掉所有寄存器.通常性况下,除了中断函数外,其它函数里都可以任意修改所有寄存器而无需先压栈保护(其实并不是这样,但现在暂时这样认为,饭要一口一口吃嘛,我很快会说到的).这个特性有什么用呢有!当我们调用任务切换函数时,要保护的对象里可以把所有的寄存器排除掉了,就是说,只需要保护堆栈即可!现在我们回过头来看看之前例子里的任务切换函数:void task_switch(){task_sp[task_id] = SP;}看到没,一个寄存器也没保护,展开汇编看看,的确没保护寄存器.好了,现在要给大家泼冷水了,看下面两个函数: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);}父函数fun2()里调用func1(),展开汇编代码看看: 193: void func1(){194: register char data i;195: i = 5;C:0x00C3 7F05 MOV R7,#0x05 196: do{197: sigl = !sigl; C:0x00C5 B297 CPL sigl 198: }while(--i);C:0x00C7 DFFC DJNZ R7,C:00C5 199: }C:0x00C9 22 RET200: 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:00CC206: }C:0x00D0 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);}展开汇编代码看看:193: void func1(){194: register char data i;195: i = 5;C:0x00C3 7F05 MOV R7,#0x05 196: do{197: sigl = !sigl; C:0x00C5 B297 CPL sigl 198: }while(--i);C:0x00C7 DFFC DJNZ R7,C:00C5 199: }C:0x00C9 22 RET200: void func2(){201: register char data i;202: i = 5;C:0x00CA 7F05 MOV R7,#0x05203: do{204: sigl = !sigl;C:0x00CC B297 CPL sigl205: }while(--i);C:0x00CE DFFC DJNZ R7,C:00CC206: }C:0x00D0 22 RET看到了吧哈哈,这回神仙也算不出来了.因为两个函数没有了直接调用的关系,所以编译器认为它们之间不会产生冲突,结果分配了一对互相冲突的寄存器,当任务从func1()切换到func2()时,func1()中的寄存器内容就给破坏掉了.大家可以试着去编译一下下面的程序:sbit sigl = P1^7;void func1(){register char data i;i = 5;do{sigl = !sigl;task_switch();} while (--i);}void func2(){register char data i;i = 5;do{sigl = !sigl;task_switch();}while(--i);}我们这里只是示例,所以仍可以通过手工分配不同的寄存器避免寄存器冲突,但在真实的应用中,由于任务间的切换是非常随机的,我们无法预知某个时刻哪个寄存器不会冲突,所以分配不同寄存器的方法不可取.那么,要怎么办呢这样就行了:sbit sigl = P1^7;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 = P1^7;void func1(){register char data i;while(1){i = 5;do{sigl = !sigl;}while(--i);task_switch();}}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{sigl = !sigl;}while(--i);}假定func1()正执行到i=3时,中断发生,一旦中断调用到func1()时,i的值就被破坏了,当中断结束后,i == 0.以上说的是在传统的单任务系统中,所以重入的机率不是很大.但在多任务系统中,很容易发生重入,看下面的例子:void func1()....delay();....}void func2(){....delay();....}void delay(){static unsigned char i;申明为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();}void func1(){....delay();....}void func2(){....delay();....用宏来代替函数,就意味着每个调用处都是一个独立的代码复本,那么两个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 是说,如果你在不用中断时任务栈深定为8的话,现在就要定为8+4 = 12了.另外说句废话,中断里处理的事一定要少,做个标记就行了,剩下的事交给对应的任务去处理.现在小结一下:切换任务时要保证没有寄存器跨越任务,否则产生任务间寄存器覆盖. 使用静态变量解决切换任务时要保证没有变量跨越任务,否则产生任务间地址空间(变量)覆盖. 使用静态变量解决两个不同的任务不要调用同时调用同一个函数,否则产生重入覆盖. 使用重入申明解决。
什么是多任务处理?多任务处理是计算机科学领域的一个重要概念,它指的是在同一时间内处理多个任务的能力。
在现代计算机系统中,多任务处理是一项基本功能,它使得计算机可以同时执行多个程序,从而提高系统的效率和性能。
多任务处理技术是操作系统的核心组成部分,它允许用户在同一时间内进行多项工作,如同时打开多个应用程序、在浏览器中浏览网页、听音乐等等。
下面将依次介绍多任务处理的相关概念、分类和优势。
一、多任务处理的概念多任务处理是指计算机系统能够同时执行多个任务的能力。
在实际应用中,这些任务可以包括用户的各种操作、程序的运行以及系统的管理等。
通过多任务处理,计算机能够快速响应用户的操作并保持高效运行。
二、多任务处理的分类1. 抢占式多任务处理抢占式多任务处理是一种操作系统调度方式,它会根据一定的策略自动中断当前执行的任务,并切换到下一个待执行的任务。
这种方式可以保证任务的快速响应和高效运行。
2. 协作式多任务处理协作式多任务处理是指任务的切换需要由任务自身配合完成,而不是由操作系统主动调度。
任务在执行完成后主动将控制权交给其他任务,这种方式适用于处理简单任务和资源受限的环境。
三、多任务处理的优势1. 提高系统的效率和性能多任务处理能够充分利用计算机系统的资源,将多个任务分配到不同的处理器或者时间片中执行,从而提高系统的效率和性能。
2. 增加用户的工作效率多任务处理允许用户同时进行多项工作,如在处理文档的同时查看邮件,这样可以提高用户的工作效率,节省时间和精力。
3. 提高系统的稳定性和可靠性通过多任务处理,计算机系统可以更加灵活地分配资源和处理任务,当一个任务出现异常时,其他任务仍然可以正常运行,从而提高了系统的稳定性和可靠性。
尽管多任务处理技术带来了很多好处,但也存在一些挑战和限制。
例如,在多任务处理过程中,不同任务之间的资源竞争可能导致系统性能下降,需要合理分配和管理系统资源。
此外,任务的调度和切换也需要一定的时间和开销,可能会影响系统的响应速度。
操作系统的多任务处理多任务处理是现代操作系统的重要特性之一,它使得计算机可以同时执行多个任务。
通过合理的任务切换和资源分配,操作系统能够提高计算机的利用率和效率。
本文将介绍操作系统的多任务处理原理、策略和应用。
一、多任务处理原理多任务处理是指在一个计算机系统中同时执行多个任务的能力。
操作系统通过任务调度算法和进程管理来实现多任务处理。
在单核处理器系统中,操作系统通过时间片轮转等算法按照一定的时间片轮询切换任务,使得任务在人眼看来是同时执行的。
而在多核处理器系统中,操作系统可以将多个任务分配给多个处理器核心并行执行。
二、多任务处理策略1. 抢占式调度抢占式调度是指操作系统可以随时中断正在执行的任务,将处理器分配给其他高优先级任务的调度策略。
当有更高优先级的任务就绪时,操作系统可以立即切换到该任务并执行,以保证高优先级任务的及时响应。
2. 合作式调度合作式调度是指任务执行必须主动释放处理器资源,才能让其他任务执行的调度策略。
在合作式调度中,每个任务都需要遵守一定的规则,如不可长时间占用处理器资源,否则会影响其他任务的执行。
3. 多级反馈队列调度多级反馈队列调度算法将任务分为多个优先级队列,每个队列具有不同的时间片大小。
当任务执行完成后,如果没有新任务到达,则继续执行该队列的下一个任务;如果有新任务到达,则将该任务插入到更高优先级队列中。
这种调度策略既能保证高优先级任务优先执行,又能公平地分配处理器资源。
三、多任务处理的应用1. 多媒体播放操作系统的多任务处理能力使得计算机可以同时播放多个媒体文件,如音频、视频等。
用户可以在观看视频的同时听音乐,提高了用户体验。
2. 并行计算通过多任务处理和多核处理器,操作系统可以将大型计算任务分解为多个子任务,然后利用不同核心并行执行这些任务,提高计算速度和效率。
3. 虚拟化技术多任务处理为虚拟化技术的实现提供了基础。
操作系统可以将物理资源虚拟化为多个虚拟机,每个虚拟机可以独立运行不同的任务,实现资源的有效利用和管理。
简单协作式多任务操作系统设计引言根据老师的要求,在这次的课程设计实验中,我们需要设计出满足老师要求的简单协作多任务操作系统。
本次实验中,我们需要设计的是简单协作式多任务操作系统,uC/OS-II是规模最小的系统之一,它的实质是一个微型内核,它是基于优先级的可剥夺型内核,系统中的所有的任务都是一个唯一的优先级别,它适合应用在实时性要求强的场合。
根据我们自身的能力和实际情况,我们发现,我们可以采用C语言来编写我们所需要的程序。
uC/OS-II的一个特点是它区分用户空间和系统空间,所以,它适合应用在比较简单的处理器上,所以,我们能够在自己的电脑上进行程序编写。
在uC/OS-II里,每个任务都有一个任务控制块,这是一个比较复杂的数据结构;任务控制块(即TCB)在uC/OS-II里是用来记录任务堆栈指针、任务的当前状态、任务的优先级别等一些与任务管理有关的属性的表。
而在uC/OS-II 中,会用两条链表来管理TCB:一条是空任务块链表(没有分配任务),一条是任务块链表(其中所有任务控制块已经分配给任务)。
同时,每个任务都有一个自身的堆栈,它们是独立属于任务本身的,即为自用堆栈。
堆栈的作用这里也简单的说明一下,即保护现场,传递函数,设置局部变量,保存任务上下文等。
通过指针的指向来控制堆栈的使用,先指向控制块,从栈中提取上下文,送入到CPU中运行,运行完毕后指针指向控制块提取下一个任务,依次循环执行。
任务上下文的切换实质是断点数据的切换,也就是处理器(CPU)的堆栈指针的切换,被中止的运行任务堆栈指针要保护到该任务的TCB中,待运行任务的任务堆栈指针要由该任务控制块转存到处理器的SP中。
uC/OS-II也提供了任务管理的各种函数调用,包括创建任务,删除任务,改变任务的优先级,任务挂起和恢复等,由于我们这一次的实验要求比较低,所以,其中的函数调用我们是用不到的,具体使用的函数,我们会在下面的程序设计中进行说明。
操作系统的分类及特点分析操作系统是计算机系统中重要的一部分,它负责管理计算机的硬件和软件资源,为用户和应用程序提供一个可靠、有效的工作环境。
根据其功能、性能和使用方式的不同,操作系统可以分为多种不同的分类。
本文将对操作系统的分类及特点进行分析。
一、基于功能的分类1. 批处理操作系统批处理操作系统主要用于处理大量作业的计算机系统。
它可以自动地按照用户事先设定的顺序和规则,无需人工干预就能够连续地执行多个作业。
这种操作系统具有高度的自动化和高效率的特点,因此适用于对处理速度要求较高的场合。
常见的批处理操作系统有IBM的OS/360和微软的MS-DOS等。
2. 分时操作系统分时操作系统允许多个用户通过终端同时使用计算机系统。
它通过将CPU的时间片划分为多个较短的时间段,每个用户轮流使用CPU,给人一种同时使用计算机的错觉。
这种操作系统可以有效地提高计算机的资源利用率,适用于多用户共享的大型计算机系统。
比较知名的分时操作系统有UNIX和Windows Server等。
3. 实时操作系统实时操作系统要求在规定的时间内完成任务的处理,对响应时间要求非常高。
它主要用于控制、通信、测量和嵌入式系统等领域,如航空航天、工业自动化、医疗设备等。
实时操作系统可以分为硬实时操作系统和软实时操作系统,前者的响应时间非常严格,而后者则具有更高的灵活性。
二、基于处理器的分类1. 单任务操作系统单任务操作系统只能一次执行一个任务。
它主要用于早期的个人计算机和嵌入式系统等,因为这些系统的资源较为有限,无法同时处理多个任务。
典型的单任务操作系统有DOS和嵌入式实时操作系统。
2. 多任务操作系统多任务操作系统能够同时执行多个任务,可以在同一时刻处理多个任务请求,提高了系统资源的利用率。
多任务操作系统可以分为抢占式多任务操作系统和协作式多任务操作系统。
前者通过强制性地暂停一个任务,并分配给其他紧急任务来提高系统的响应能力,而后者则依赖于任务间的协作来管理和分配资源。