第15章 RTX51操作系统原理及实现
嵌入式操作系统RTX51Tiny的分析及应用 (1)

DEL ETED :任务不处于执行队列 。 TIMEOU T :任务由于时间片用完而处于 timeout 状 态 ,并等待再次运行 。该状态与 ready 状态相似 。 图 1 所示为任务状态转换图 。
(中南大学 物理科学与技术学院 ,湖南 长沙 410083)
摘 要 :在嵌入式开发中 ,嵌入式操作系统的应用是重要的一环 。文中对一种适用 51 单片机的实时多任务操作系统 ——— RTX51 Tiny 进行应用 。首先从任务管理和内存管理的角度来分析该操作系统 ,在此基础上 ,用 C 语言编写应用程序 traf2 fic 。应用实践得到以下结论 :该操作系统的应用程序启动简单 ,没有操作系统的启动过程 ;短小精悍 ,代码不足 50 行 ;运行 可靠 ,仿真 、硬件运行结果充分证实了这一点 。从而得到 RTX51 Tiny 操作系统对任务和堆栈管理的有效性及其代码精简 和运行可靠的基本特点 。 关键词 :嵌入式操作系统 ; 实时操作系统 ; RTX51 Tiny 中图分类号 : TP316 . 2 ; TP317 文献标识码 :A 文章编号 :1673 - 629X(2006) 06 - 0089 - 03
ROM 。对于 51 单片机这种资源较少的处理器使用受限 。 解决的办法是采用新的操作系统 ,例如 RTX51 。
1 RTX51 Tiny 分析 RTX51 是由德国 Keil 公司开发的 ,专门针对 8051 兼
容 MCU 所 作 的 多 任 务 实 时 操 作 系 统 。它 有 完 全 版 ( RTX51 Full) 和小型版 ( RTX51 Tiny) 。RTX51 Tiny 是一 个 RTX51 Full 的子集 ,它可以很容易地在没有任何外部 存储器的 51 系统上运转 。通常 ,由于 8 位单片机的处理 速度 、内部寄存器资源等因素的限制 ,由此定制的操作系 统功能有限 。通过分析发现 RTX51 Tiny 操作系统主要 包括了任务管理和内存管理 。RTX51 Tiny 在任务管理方 面 :仅支持时间片轮转任务切换和使用信号进行任务切 换 ,不支持抢先式的任务切换 ,不包括消息历程 ;在内存管 理方面 :没有存储器池分配程序 ,内核使用 keilc51 编译器 将内存管理简化为堆栈管理[2 ,3 ] 。 1. 1 RTX51 Tiny 的任务和事件
RTX51小型实时操作系统介绍[1]
![RTX51小型实时操作系统介绍[1]](https://img.taocdn.com/s3/m/0a15223e87c24028915fc390.png)
RTX51小型实时操作系统介绍(连载1)有二个不同的RTX51版本可以使用∶RTX51 Full使用多于四个任务优先权同时完成存在时间片轮转调度和抢先式的任务切换。
RTX51工作在与中断功能类似的状态下。
信号和消息可以通过邮箱系统在任务之间互相传递。
你可以从一个可分配存储区中分配和释放内存。
你可以强迫一个任务等待中断、超时或者是从另一个任务或中断发出的信号或消息。
RTX51 Tiny 是 RTX51的一个子集,它可以很容易地在没有任何外部存储器的单片8051系统上运行。
除了下列例外,RTX51 Tiny支持许多 RTX51中的特性。
RTX51 Tiny仅支持时间片轮转任务切换和使用信号进行任务切换。
不支持抢先式的任务切换。
不包括消息历程。
没有可分配存储区分配程序。
许多微处理器应用程序要求同时执行两个工作或任务。
对于这样的应用程序,一个实时操作系统(RTOS)允许灵活的分配系统资源(中央处理器、存储器、等等.)给各个任务。
RTX51是一个很容易使用的功能强大的实时操作系统。
R TX51可以运行于所有的 8051 派生机型。
你可以使用标准 C语言编写和编译一个程序并使用 C51 构造、编译他们,仅在指定任务标识符和优先权上有一点差别。
. RTX51程序也要求你在程序中用include命令引入实时管理的头文件并使用 BL51 linker/locator进行连接和选择适当的 RTX51库文件。
RTX51小型实时操作系统介绍(连载2)单任务程序一个标准的 C语言程序从主函数开始执行。
在一个嵌入式应用中,主函数通常是一段无限循环的代码,可以认为是一个连续执行的单独任务。
时间片轮转程序一种更高级的 C语言程序可以在不使用实时操作系统的情况下实现时间片轮转拟多任务系统。
在这种系统中、任务或功能被一段无限循环程序重复调用。
例如∶用 RTX51进行时间片轮转调度rtx51也能完成多重任务时间片轮转,而且允许准并行执行多个无限循环或任务。
RTX51操作系统

RTX51操作系统1.简介1.1 优势1.2 功能1.3 支持的硬件平台2.安装与配置2.1 系统需求2.2 与安装2.3 系统配置2.4 驱动程序安装2.5 开发环境配置3.RTX51基本概念3.1 任务(Task)及任务状态3.2 事件(Event)3.3 信号量(Semaphore)3.4 互斥信号量(Mutex)3.5 定时器(Timer)3.6 管程(Monitor)3.7 消息队列(Message Queue)4.任务管理4.1 创建与删除任务4.2 任务状态转换4.3 任务优先级与调度4.4 任务间通信5.中断处理5.1 中断优先级配置5.2 中断服务程序编写5.3 中断屏蔽与解屏蔽5.4 中断与任务通信6.事件管理6.1 事件定义与初始化6.2 事件等待与触发6.3 事件集合7.信号量管理7.1 信号量定义与初始化7.2 信号量的等待与释放7.3 信号量的使用注意事项8.互斥信号量管理8.1 互斥信号量定义与初始化 8.2 互斥信号量的使用流程8.3 互斥信号量的异常情况处理9.定时器管理9.1 定时器定义与初始化9.2 定时器的启动与停止9.3 定时器的回调函数编写10.管程管理10.1 管程的定义与初始化10.2 管程中的进程同步10.3 管程中的资源管理11.消息队列管理11.1 消息队列的定义与初始化11.2 消息发送与接收11.3 消息队列的使用注意事项12.调试与优化12.1 调试工具的使用12.2 性能优化技巧12.3 常见问题解决方法13.附件13.1 示例代码13.2 API文档13.3 参考资料注释:1.本文档涉及附件请参考附件部分。
2.本文所涉及的法律名词及注释请参考附件部分。
附件:1.示例代码 - 包含了一些使用RTX51操作系统的示例代码,供开发者参考。
2.API文档 - 文档详细介绍了RTX51操作系统的各个API函数及其使用方法。
3.参考资料 - 列出了一些相关的参考资料,以帮助开发者更深入地了解和使用RTX51操作系统。
RTX51嵌入式实时操作系统分析

(2) 任务创建 ,将自由堆栈空间的 2个字节分配给新创建 的任务 ;
(3) 任务删除 ,回收被删除任务的堆栈空间 ,并转换为自由 堆栈空间 。
堆栈管理如图 2所示 。
图 1 用户任务状态转换图
212 同步机制
RTX51 Tiny内核用以下事件进行任务间的通信和同步 。 超时 ( TIM EOUT) :由 os_wait函数调用引发的时间延时 ,
如果出现以下情况 ,则当前运行任务中断 : (1) 任务调用 os_wait函数并且指定事件没有发生 ; (2) 任务运行时间超过定义的时间片轮转超时时间 。 如果出现以下情况 ,则开始另一个任务 : (1) 没有其他的任务运行 ; (2) 将要开始的任务处于“READY”或“TIM EOUT”状态 。
213 调度策略
RTX51 Tiny采用时间片轮转算法 ,不支持任务优先级策 略 。时间片非常短 ,通常只有几个毫秒 。一个时间片的持续时 间也可以预先用配置变量 TIM ESHAR ING定义 。系统每次调度 时 ,把 CPU分配给一个就绪的任务 ,并令其执行一个时间片 ,构 成微观上轮流运行 、宏观上并行执行的多任务效果 。
以下情况会启动堆栈管理 : (1) 任务切换 ,将全部自由堆栈空间分配给正在运行的任务 ;
(1)任务 A 正在运行 ; (2)切换到任务 B 运行 ; (3)删除任务 C后 ,自由空间增加 ; (4)创建任务 D 后自由空间减少 2字节 图 2 堆栈管理
215 中断处理
RTX51 Tiny提供两种方法来处理中断 :一种是 C51中断函 数 ,包括软件中断和硬件中断 。对于硬件中断 ,用户不能再使用 定时器 T0。当中断发生的时候 ,程序跳到相应的中断处理函 数 。中断处理过程与正在运行的任务是相互独立的 ,即中断处 理过程在 RTX51系统内核之外和任务切换规则没有关联 。但 中断处理过程可以同 RTX51任务互发信号或交换数据 ;另一种 是 RTX51的任务中断 。如果中断发生 ,等待中断的任务就从 “等待 ”状态进入到“就绪 ”状态 ,并按照任务切换规则进行切 换 。这种中断处理是完全集成在 RTX51内部 ,与硬件中断事件 的处理以及信号 、消息的处理是完全相同的 。
操作系统原理与实现技术

操作系统原理与实现技术操作系统是计算机系统中的核心组成部分,它负责管理和协调计算机硬件和软件资源,提供良好的用户体验以及高效的任务调度和资源分配。
本文将介绍操作系统的基本原理和实现技术。
一、操作系统的基本原理操作系统的基本原理包括进程管理、内存管理、文件系统和设备管理等方面。
1. 进程管理进程是指计算机中正在运行的程序实例。
操作系统通过进程管理来分配和调度系统资源,确保多个进程之间的公平共享。
进程管理包括进程的创建、终止、进程间的通信和同步等。
2. 内存管理内存管理是操作系统的重要功能之一,它负责管理计算机的物理内存和虚拟内存。
内存管理主要包括内存的分配与回收、内存的保护与共享、虚拟内存的管理等。
3. 文件系统文件系统用于管理计算机中的数据存储,提供了对文件的访问、读写和操作等功能。
文件系统可以将文件以层次化的结构进行组织,并对文件进行管理和保护。
4. 设备管理设备管理是操作系统中的重要组成部分,它负责管理计算机的各种外部设备,如硬盘、键盘、打印机等。
设备管理包括设备的分配与释放、设备的控制与调度等。
二、操作系统的实现技术操作系统的实现技术包括中断处理、进程调度、内存管理和文件系统实现等。
1. 中断处理中断是指计算机在运行过程中的某个事件或条件触发了处理器的中断请求信号。
操作系统通过中断处理来响应和处理中断事件,保证系统的可靠性和稳定性。
2. 进程调度进程调度是指根据不同进程的优先级和调度算法,按照一定的策略将CPU资源分配给各个进程。
常见的进程调度算法有先来先服务、短作业优先、时间片轮转等。
3. 内存管理内存管理是操作系统的关键部分,主要包括物理内存的分配和回收、虚拟内存的管理、页面置换算法等。
常见的内存管理技术有分页式内存管理、分段式内存管理、段页式内存管理等。
4. 文件系统实现文件系统实现主要包括文件的组织和存储、文件的访问和权限管理、磁盘空间的分配和回收等。
常见的文件系统实现技术有FAT文件系统、NTFS文件系统、EXT文件系统等。
rtx51小型实时操作系统的应用体会

rtx51小型实时操作系统的应用体会2007年06月27日星期三 23:53最近在做设计的时候老遇到一些几个任务需要同时进行的情况,刚开始想自己去做一个多任务的程序(因为称不上系统),想用一个脉冲计数的方法来实现多任务切换,但是感觉实现起来比较麻烦。
无意中发现了51单片机下个rtx51实时操作系统(本人以前不知道的),后来就去找相关资料。
发现这方面的资料在网上很少,也很杂乱。
经过一段时间的学习,我有了个大概的了解。
下面给大家讲述一下:RTX51是一个用于8051系列处理器多任务实时操作系统。
RTX51可以简化那些复杂而且时间要求严格的工程的软件设计工作。
有二个不同的RTX51版本可以利用:RTX51 Full 使用四个任务优先权完成同时存在时间片轮转调度和抢先的任务切换 RTX51工作在与中断功能相似的状态下信号和信息可以通过邮箱系统在任务之间互相传递你可以从一存池中分配和释放内存你可以强迫一个任务等待中断超时或者是从另一个任务或中断发出的信号或信息RTX51 Tiny 是一个RTX51的子集它可以很容易地在没有任何外部存储器的单片8051系统上运转。
除了下列例外 RTX51 Tiny支持许多在RTX51中的特征。
RTX51 Tiny仅支持时间片轮转任务切换和使用信号进行任务切换不支持抢先式的任务切换。
不包括消息历程。
没有存储器池分配程序。
RTX51使用一个8051硬件计时器中断作为定时程序。
产生的周期性中断用于驱动 RTX51时钟。
RTX51不需要在你的程序中拥有一个主函数它将自动开始执行任务 0 如果你确实有一个主函数你必须利用 RTX51 Tiny中的 os_create_task函数或 RTX51中的os_start_system函数手工启动 RTX51。
下列例子显示一个只使用时间片轮转任务调度的简单的 RTX51应用程序在本程序里的二个任务是简单计数器回路 rtx51开始执行函数名为 job0的任务 0 。
51单片机多任务的原理及其实现

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了.另外说句废话,中断里处理的事一定要少,做个标记就行了,剩下的事交给对应的任务去处理.现在小结一下:切换任务时要保证没有寄存器跨越任务,否则产生任务间寄存器覆盖. 使用静态变量解决切换任务时要保证没有变量跨越任务,否则产生任务间地址空间(变量)覆盖. 使用静态变量解决两个不同的任务不要调用同时调用同一个函数,否则产生重入覆盖. 使用重入申明解决。
基于RTX51的多任务处理的实现

收稿日期:2009-06 基金项目:福建省自然科学基金计划资助项目(2006J0196);福建工程学院科研发展基金资助项目(GY -Z0607) 作者简介:张国安(1965—),男,教授,研究方向为工业控制、分布式计算。
基于RT X51的多任务处理的实现张国安,汤龙梅(福建工程学院计算机与信息科学系,福建福州350014) 摘要:讨论多任务实时操作系统RT X51的主要任务处理策略,对该操作系统的内核进行了简要分析。
在此基础上,设计实现了一个基于RTX51的多任务处理实例,它以一个MCU 为核心,包括智能仪器中常用的输入输出设备。
该实例可以很容易地移植到其他应用程序中,对复杂多任务系统设计具有典型意义。
关键词:RTX51;多任务;智能仪器;移植中图分类号:TP33 文献标识码:B 文章编号:1006-2394(2009)11-0003-04The Im ple m en t a ti on of M ulti 2t a sk i n g System Ba sed on RTX51ZHANG Guo 2an,T ANG Long 2mei(Computer and I nf or mati on Science Depart m ent,Fujian University of Technol ogy,Fuzhou 350014,China )Abstract:I n this paper,main task p r ocessing strategies of multi 2tasking operating syste m RT X51are discussed,with su mmary analysis of its internal kernel .On the basis of above results,a multi 2task p r ocessing exa mp le is designed and i m p le mented,which takes a MCU as core and includes common input and out put co mponents of intelligent instru 2ment .The exa mp le can be easily trans p lanted int o other app licati ons,s o it is of typ ical significance t o the design of co m 2p licated multi 2tasking syste m.Key words:RTX51;multi 2tasking;intelligent instru ment;trans p lant1 RTX51实时操作系统RT X51是KE I L 公司开发的用于MCS -51系列单片机的多任务实时操作系统。
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
目的在使得用户能够方便有效率地去操作硬件以达成双向之交互,
RTX51操作系统的任务
--定义任务
实时或者多任务应用是由一个或多个执行指定操作的任 务所构成的。RTX51 Tiny允许最多16个任务。
任务是简单的C函数,返回类型为void,有一个void参数列表。 使用_task_函数属性声明。格式如下:
操作系统基本知识
--人机交互
操作系统的人机交互功能是决定计算机系统友好性的一 个重要因素。
可供人机交互使用的设备主要有键盘显示、鼠标、各种模式识别设备
等。 与这些设备相应的软件就是操作系统提供人机交互功能的部分。
人机交互功能主要靠可输入输出的外部设备和相应的软件来完成。
人机交互部分的主要作用是控制有关设备的运行和理解并执行通
【例】 调用os_create_task()函数的例子
#include <rtx51tny.h>
#include <stdio.h> void new_task (void) _task_ 2
{
…. }
void tst_os_create_task (void) _task_ 0
{ …. if (os_create_task (2)) printf ("Couldn't start task 2\n"); ….. }
RTX51操作系统内核函数
char os_running_task_id(void)
功能:确定当前运行任务的id。 返回:当前运行任务的任务ID号,值的范围为0~15。
RTX51操作系统内核函数
char os_send_signal(unsigned char task_id)
功能:发送信号到task_id任务。 如果指定的任务已经在等待信号,则该函数调用将准备任务用于执行。
常由一组系统调用组成。
操作系统基本知识
--用户界面
用户界面(User Interface,UI)是系统和用户之间进行 交互和信息交换的媒介,它实现信息的内部形式与人类可 以接受形式之间的转换。
完成所希望借助硬件完成之工作。
用户界面定义广泛,包含了人机交互与图形用户接口,凡参与人 类与机械的信息交流的领域都存在着用户界面。
过人机交互设备传来的有关的各种命令和要求。
操作系统基本知识
--进程管理
不管是常驻程序或者应用程序,他们都是以进程为标准 的执行单位。
进程就是当前正在运行的程序。 进程管理指的是操作系统管理多个进程的准备、运行、挂起和退 出。 进程管理通常使用分时复用的调度机制,大部分的操作系统可以 通过为不同进程指定不同的优先级,从而改变为这些进程所分配
接口:操作接口和编程接口。
操作接口由一系列操作命令组成,用户通过操作接口可以方便地使用
计算机。
编程接口由一系列的系统调用组成各种程序可以使用这些系统调用让 操作系统为其服务,并通过操作系统来使用硬件和软件资源。
操作系统基本知识
--操作系统的作用
操作系统的作用主要体现在以下两方面:
屏蔽硬件物理特性和操作细节,为用户使用计算机提供了便利。 有效管理系统资源,提高系统资源使用效率。
RTX51操作系统内核函数
char os_delete_task(unsigned char task_id)
返回:0,表示任务成功停止和删除;-1,表示指定的任务不存在或者
功能:停止task_id指定的任务,将由task_id指定的任务从任务列表中删除。
task_id定义的任务没有启动。
RTX51操作系统内核函数
如果发生下面的情况,则启动其他任务:
没有运行其他任务; 将要启动的任务处于READY或者TIME_OUT状态;
RTX51操作系统内核函数
注:在调用这些内核函数时,必须包含头文件rtx51tny.h。
char isr_send_signal(unsigned char task_id)
{ isr_send_signal (8); //向任务8发送信号
}
RTX51操作系统内核函数
【例】 调用os_clear_signal()函数的例子
#include <rtx51tny.h> void tst_os_clear_signal (void) _task_ 8 { …. os_clear_signal (5); ….. } //清除任务5中的信号标志
操作系统基本知识
--操作系统的功能
操作系统位于底层硬件与用户之间,是两者沟通的桥梁。
用户可以通过操作系统的用户界面,输入命令。
操作系统则对命令进行解释,驱动硬件设备,实现用户要求。
操作系统基本知识
--操作系统的功能
以现代观点而言,一个完整的OS应该提供以下的功能:
资源管理
内存管理 程序控制
虚拟内存
人机交互 用户接口
进程管理
用户界面
操作系统基本知识
--资源管理
系统的设备资源和信息资源都是操作系统根据用户需求按一定的
策略来进行分配和调度的。 处理器管理或称处理器调度,是操作系统资源管理功能的另一个 重要内容。操作系统的设备管理功能主要是分配和回收外部设备 以及控制外部设备按用户程序的要求进行操作等。
第15章 RTX51操作系统原理及实现
何宾 2015.02
本章主要内容
操作系统的必要性
操作系统基本知识 RTX51操作系统的任务
RTX51操作系统内核函数
RTX51操作系统实现
操作系统的必要性
--单任务程序
在不使用操作系统的传统的单片机中,常使用单任务的程
序或者轮询的程序。
一个标准的C程序用main函数启动执行。在嵌入式应用中, main通常作为一个无限循环,被认为一个单任务,这个任务连 续的运行。
的时间片。
在进程管理中,优先调度优先级高的进程。
操作系统查找可用的存储空间; 配置与释放存储空间;
交换内存和外存的内容;
提供存储器访问的权限等功能。
操作系统基本知识
--虚拟内存
虚拟内存是计算机系统内存管理的一种技术。
它使得应用程序认为它拥有连续的可用的内存(一个连续完整
操作系统的必要性
--单任务程序
【例】 单任务程序C语言描述的例子
int counter; void main (void) { counter = 0; while (1) counter++; } //无限循环 //递增计数器
操作系统的必要性
--轮询程序
不用实时操作系统RTOS,解决单任务程序的一个方法就
//无限循环
//处理串行输入 //处理键盘输入 //调整控制器
//递增计数器
操作系统基本知识
操作系统是管理和控制计算机硬件与软件资源的计算机 程序,是直接运行在计算机硬件上的最基本的系统软件, 任何其他软件都必须在操作系统的支持下才能运行。
操作系统在硬件系统上运行,它常驻内存内,并提供给上层两种
RTX51操作系统内核函数
【例】 调用os_delete_task()函数的例子
#include <rtx51tny.h> #include <stdio.h> void tst_os_delete_task (void) _task_ 0 { …. if (os_delete_task (2)) printf ("Couldn't start task 2\n"); ….. }
RTX51操作系统的任务
--切换任务
RTX51 Tiny中,负责分配处理器给一个任务的那部分称 为调度器。根据下面的规则,RTX51 Tiny调度器定义所 运行的任务:
如果发生下面的情况,则打断当前正在运行的任务:
任务调用os_wait函数,并且没有产生指定的事件; 执行任务的时间大于定义的轮询超时时间;
void func(void) _task_ num func为任务的函数名; _task_是定义任务关键字;
num是任务ID号,取值从0~15,每一个任务必须有一个唯一的任务号。
RTX51操作系统的任务
--定义任务
【例】 定义任务 C语言描述的例子
void job0 (void) _task_ 0 { while(1) { counter0++; } } //计数器递增
RTX51操作系统内核函数
char os_create_task(unsigned char task_id)
功能:启动由task_id指定的任务。将该任务标记为准备状态,并且根据 RTX51 Tiny指定的规则执行该任务。 返回:0,表示成功启动任务;-1表示没有启动任务,或者不存在task_id 定义的任务。
信息管理是操作系统的一个重要的功能,主要是向用户提供一个
文件系统。
操作系统基本知识
--程序控制
操作系统控制用户程序的执行主要有以下一些内容:
调入相应的编译程序,将用某种程序设计语言编写的源程序编译 成计算机可执行的目标程序;
分配内存储等资源将程序调入内存并启动;
按用户指定的要求处理执行中出现的各种事件以及与操作员联系 请示有关意外事件的处理等。
否则,保存信号到任务的信号标志内。
的地址空间)。 而实际上,它通常是被分隔成多个物理内存碎片,还有部分暂 时存储在外部磁盘存储器上,在需要时进行数据交换。
操作系统基本知识
--用户接口
用户接口包括作业一级接口和程序一级接口。
它通常包括联机用户接口与脱机用户接口。
作业一级接口为了便于用户直接或间接地控制自己的作业而设置。