9512.net
甜梦文库
当前位置:首页 >> 数学 >>

μCOS广州讲稿



嵌入式实时操作系统 C/OS-II讲座 讲座
北 华 大 学 任哲 2006 广州

为什么要学习C/OS-II
一.凡从事嵌入式系统开发工作的人,必须 对嵌入式操作系统有足够的了解。 二.对于初学者,从C/OS-II开始是个明智的选择。 1. C/OS-II麻雀虽小,却五脏基本全(它是个微 内核)。 2.可以学习实时系统的一些编程技巧。 3.可以把在学校中学到的操作系统抽象概念具体 化。 4.具有很强的实用性。 5.学习数据结构应用的好例子。

讲座的主要内容
一.计算机操作系统的基本概念 二.操作系统中常用的数据结构 三.并发操作系统的概念 四.任务的要素 五. C/OS-II的任务管理(任务调度) 六. C/OS-II的中断和时钟 七. C/OS-II的任务的同步与通信 八. C/OS-II的存储管理 九.硬件抽象层和测试台

什么是计算机操作系统
(Operating System,OS)

操作系统是一种为应用 程序提供服务的系统软 件,是一个完整计算机 系统的有机组成部分。 从层次来看,操作系统 位于计算机硬件之上, 应用软件之下。所以也 把它叫做应用软件的运 行平台。

计算机操作系统的作用
它在计算机应用程序与计算 从用户的角度来看, 机硬件系统之间,屏蔽了计 算机硬件工作的一些细节, 它就是一大堆函数 并对系统中的资源进行有效 (API和系统函数), 的管理。
操 作 系 统

应用软件 高级语言的接口 用 件 件 语言 的

用户可以调用(普 通过提供函数(应用程序接 通调用或系统调用) 口(API)),从而使应用程 它们来对系统资源 序的设计人员得以在一个友 好的平台上进行应用程序的 进行操作。
设计和开发,大大地提高了 应用程序的开发效率。

计算机操作系统的功能
任务管理
任务表

存储管理

存储 分配表

处理器的管理

总之,需要一大堆表
操作系统
设备表

文件管理
文件 目录

网络和通信的 管理

I/O设备管理

操作系统中经常使用 的数据结构(数组)
数组 int a[10] 1。同一数据类型数据 a 的集合; 2。占用连续内存空间; a+1 3。其中的所有元素名 a+2 称都相同,但每个元 a+3 素都有一个编号; 4。元素名去掉编号 a+9 (下标),得到的是 数组名,数组名是个 应用:记录 指针。 a[0] a[1] a[2] a[3] … a[9] … 1。分类存放; 2。检索速度 快且恒定; 3。缺点:占 用连续空间大 使用上的特点:

同类事物的表、取口纸

操作系统中经常使用 的数据结构(位图)
a[10] (可以记录80个事物的状态) 位图是数组的一种 特殊应用
D7 D6 D5 D4 D3 D2 D1 D0

a a+1 a+2 a+3 a+9

a[0] 1/0 a[1] a[2] a[3] … … a[9]

应用:

登记表

操作系统中经常使用 的数据结构(结构)
使用上的特点: 1。不同数 据类型数 struct Student{ 据的集合; int age; 2。占用连 char*name; 续内存空 char sex; 间; }; 1。不分类存 放,但用来描 述同一事物; 2。检索速度 快且恒定;

应用:通讯录中的一条记录、 工具箱、厨房等等

操作系统中经常使用 的数据结构(链表)
两个元素的链表 struct Student{ Student*next next next int age; char*name; char sex; 1。同数据类型数据的集合; 使用上的特点: 2。不占用连续内存空间。 }; 1。分类存放,但空间上不连续(不 需要大量的连续存储空间); 2。检索速度慢,且耗费的时间不固 定;

应用:存放大量的较大 的表,类似档案柜

操作系统中经常使用 的数据结构(队列) 按照先进先出 的规则组织的数据结构 主要用于对象的排队 可以用数组也可以用链 表来实现

操作系统中经常使用 的数据结构(堆栈) 按照先进后出 主要用于 规则组织的数据结构 程序模块的嵌套运行 主要用数组来实现

什么是多任务系统
处理器如何进行程序的

并发: 切换?

运行多个程序。或者说是由多个 并发(注意,不是同时! ) 程序轮班地占用处理器这个资源。 地运行多个程序的计算机管理系统。 且在占用这个资源期间,并不一 定能够把程序运行完毕。

并发过程 由同一个处理器轮换地 示意图 简单地说,就是能用一个处理器

程序的切换(两句话) 从此可以知道,哪个程 处理器是个傻瓜,PC让 序占有了PC,哪个程序 它干啥,它就干啥。 深刻地理解PC是理解系统 就占有了处理器。 进行程序切换动作的关键。 PC是个指路器,它指向哪 哪个人占有了一个姑娘 儿,处理器就去哪儿。 的芳心,哪个人就……

= PC

数据传送指令 数据传送指令 如何操作PC 子程序返回指令(由堆 子程序返回指令( 子程序返回指令 指令: 所谓切换就是: 栈弹出) 栈弹出) 不同的计算机类型的指 PC 目标地址 令是不同的。 中断服务程序返回指令 中断服务程序返回指令 由堆栈弹出) (由堆栈弹出)

小结 系统是通过把待运行程 序的地址赋予程序计数 PC来实现程序的切换 器PC来实现程序的切换 的。

任务运行时与 处理器之间的关系
运行环境包括了两部分: 处理器中的运行环境和 内存中的运行环境 处理器 PC 寄存器组 SP 内存 任务代码

处理器通过两个指针寄存 器(PC和SP)来与任务 程序运行环境 代码和任务堆栈建立联系 并运行它

任务堆栈

多任务时的问题
处理器 PC 寄存器组 SP 当有多个任务时,处理 内存 内存 器中的运行环境应该怎 内存 么办? 任务代码 任务代码 任务代码



任务堆栈 任务堆栈 任务堆栈 程序运行环境

多任务时任务与处理器 之间关系的处理 在内存中为每个任
处理器 PC 寄存器组 SP 复制 复制 调度器 复制 PC PC PC PC 务创建一个虚拟的 处理器(处理器部 分的运行环境 程序 程序

寄存器组 SP SP SP 虚拟SP 虚拟 虚拟 处理器 虚拟 当需要中止当前任 处理器 处理器 当需要运行某个任务时 处理器 务时,则把任务对 再把另一个需要运行的任 就把该任务的虚拟处理 务的虚拟处理器复制到实 由操作系统的调度 应的虚拟处理器复 器复制到实际处理器中 制到内存 际处理器中 器按某种规则来进 行这两个复制工作

也就是说,任务的切换是 任务运行环境的切换

虚拟处理器应该存储的主要信息: 任务控制块 内存 1。程序的断点地址(PC) 任务控制块结构的主要成员 2。任务堆栈指针(SP) 这些内容通常保 任务代码 3。程序状态字寄存器(PSW) 存在任务堆栈中, typedef struct os_tcb { 4。通用寄存器内容 这些内容也常叫 *OSTCBStkPtr; //指向任务堆栈栈顶的指针 OS_STK *OSTCBStkPtr 5。函数调用信息(已存在于堆栈) 做任务的上下文。 5 …… INT8 OSTCBStat; INT8U OSTCBStat //任务的当前状态标志 INT8 OSTCBPrio; INT8U OSTCBPrio //任务的优先级别 另外再用一个数据结构保存任务堆栈 任务控制块是由操 任务堆栈 …… 指针(SP),这个数据结构叫做任务 作系统另行构造的 } OS_TCB; 控制块,它除了保存任务堆栈指针之 一个数据结构,每 外还要负责保存任务其他信息。 个任务都有一个。

虚拟处理器 要建立一个概念:具有

控制块的程序才是一个 可以被系统所运行的任务。 实质上系统是通过SP的切换 其实,程序切换的关键是 程序代码、私有堆栈、任 来实现程序的切换的。 把程序的私有堆栈指针赋 务控制块是任务的三要件。 任务控制块提供了运行环 予处理器的堆栈指针 境的存储位置。

SP

把一个大型任务分解成多个小任 务,然后在计算机中通过运行这 内存 些小任务,最终达到完成大任务 从应用程序设计的角度来看, 的目的。 在内存中应该 C/OS-II的任务就是一个用 任务代码 存有任务的代 任务的基本概念 在μC/OS-II中,与上述那些小任 户编写的C函数和与之相关 码和与该任务 配套的堆栈 务对应的程序实体就叫做“任务” 联的一些数据结构而构成的 (实质上是一个线程),μC/OS一个实体。 任务堆栈 II就是一个能对这些小任务的运 行进行管理和调度的多任务操作 系统。

小结
一个完整的任务应该有如下三部分: 任务代码(程序) 任务的私有堆栈(用以保护运行环境) 任务控制块(提供私有堆栈也是虚拟处 理器的位置) 这些都是任务方应该提供的基本信息。

任务切换过程
处理器的SP=任 务块中保存的SP 恢复待运行任务 的运行环境 处理器的PC=任 务堆栈中的断点 地址

获得待运行任务 的任务控制块

如何获得待运行 任务的任务控制 块?

C/OS-II中 的任务管理

任务在没有被配备 任务控制块或被剥 夺了任务控制块时 的状态叫做任务的 睡眠状态

正在运行的任务,需要 等待一段时间或需要等 待一个事件发生再运行 时,该任务就会把CPU 的使用权让给别的任务 而使任务进入等待状态

任务的状态及其转换
系统为任务配备 了任务控制块且 在任务就绪表中 进行了就绪登记, 这时任务的状态 叫做就绪状态。 处于就绪状态的 任务如果经调度 器判断获得了 CPU的使用权, 则任务就进入运 行状态

一个正在运行的 。 任务一旦响应中 断申请就会中止 运行而去执行中 断服务程序,这 时任务的状态叫 做中断服务状态

任务控制块结构的主要成员

任务控制块——任务在系统中的身份证
由于系统存在着多个任务,于是

typedef struct os_tcb { 系统如何来识别并管理一个任务就是 //指向任务堆栈栈顶的指针 *OSTCBStkPtr; OS_STK *OSTCBStkPtr …… 一个需要解决的问题。识别一个任务 INT8 OSTCBStat; INT8U OSTCBStat //任务的当前状态标志 基于上述原因,系统必须为每个任务创建 的最直接的办法是为每一个任务起一 INT8 OSTCBPrio; INT8U OSTCBPrio //任务的优先级别 一个保存与该任务有关的相关信息的数据 个名称。 …… 结构,这个数据结构就叫做该任务的任务 } OS_TCB; 由于C/OS-II中的任务都有一个 控制块(TCB)。

任务控制块是不是像 前面谈到,一个任务的任务控制块 的主要作用就是保存该任务的虚拟 我们人在一个国家中 处理器的堆栈指针寄存器SP。 的身份证?(其实, 另外,前面也谈到,一个任务在 其实,随着任务管理工作的复杂性 不同的时刻还处于不同的状态, 系统中的所有资源 惟一的优先级别,因此C/OS-II是用 的提高,它还应该保存一些其他信 显然,记录了任务状态的数据也 任务的优先级来作为任务的标识的。 息。 都应该有身份证。) 应该保存到任务控制块中。 所以,任务控制块还要来保存该
任务的优先级别。

任务在内存中的结构

void MyTask(void *pdata) { 临界段 for (;;) { 可以被中断的用户代码; 可以被中断的用户代码; 进入临界段( OS_ENTER_CRITICAL( );//进入临界段(关中断) ; 进入临界段 关中断) 不可以被中断的用户代码; 不可以被中断的用户代码; OS_EXIT_CRITICAL( ); //退出临界段(开中断) 退出临界段( ; 退出临界段 开中断) 可以被中断的用户代码; 可以被中断的用户代码; } 无限循 }

于是可以这样说,μC/OS-II 用户任务代码的 任务的代码结构是一个可以 一般结构 带有临界段的无限循环。


void OSTaskIdle(void* pdata) { # 在多任务系统运行时,系统经 if OS_CRITICAL_METHOD = = 3 OS_CPU_SR cpu_sr; 常会在某个时间内无用户任务 空闲任务只是做了一 #endif注意!空闲任务中没 个计数工作

C/OS-II规定,一个 用户应用程序必须 可运行而处于所谓的空闲状态, 有调用任务延时函数 pdata = pdata; //防止某些编译器报错 系统提供的空闲任务 使用这个空闲任务, 为了使CPU在没有用户任务可执 for(;;) 行的时候有事可做,μC/OS-II { 而且这个任务是不 OS_ENTER_CRITICAL( );//关闭中断 提供了一个叫做空闲任务 //计数 能用软件来删除的 OSdleCtr++;
OS_EXIT_CRITICAL( ); //开放中断 OSTaskIdle( )的系统任务 }

}

μC/OS-II提供的另一个系统任务是统
计任务OSTaskStat( )。这个统计任务 每秒计算一次CPU在单位时间内被使用 系统提供的另一个任务 的时间,并把计算结果以百分比的形 ——统计任务 式存放在变量OSCPUsage中,以便应用 程序通过访问它来了解CPU的利用率, 所以这个系统任务OSTaskStat( )叫做 统计任务

用户可以根据应用程序的需要,在文件 C/OS_II 把任务的优先 固定地,系统总是把最低优先级别 OS_CFG.H中通过给表示最低优先级别的 OS_LOWEST_PRIO自动赋给空闲任务。如果 权分为64个优先级别,每 常数OS_LOWEST_PRIO赋值的方法,来说 应用程序中还使用了统计任务,系统则会 一个级别都用一个数字来 明应用程序中任务优先级别的数目。该 把优先级别OS_LOWEST_PRIO-1自动赋给统 表示。数字0表示任务的 常数一旦被定义,则意味着系统中可供 计任务,因此用户任务可以使用的优先级 使用的优先级别为:0,1,2,……, 优先级别最高,数字越大 别是:0,1,2…OS_LOWEST_PRIO-2,共 OS_LOWEST_PRIO,共OS_LOWEST_PRIO+1 则表示任务的优先级别越 OS_LOWEST_PRIO-1个 个 低

任务的优先权 及优先级别

void main(void) typedef unsigned int OS_STK; { 在应用程序中定义任务堆栈的栈区非常简单, //这是系统定义的一个数据类型 在创建用户任务时,要 …… 即定义一个OS_STK类型的一个数组并在创建 传递任务的堆栈指针和 OSTaskCreate( 任务优先级别 一个任务时把这个数组的地址赋给该任务就 MyTask, //任务的指针 可以了。 &MyTaskAgu, //传递给任务的参数 例如: & MyTaskStk[MyTaskStkN-1],//任务堆栈栈顶地址 20 //任务的优先级别 //定义堆栈的长度 ); #define TASK_STK_SIZE 512 …… }

保存CPU寄存器中的内容及存 使用函数 储任务私有数据的需要,每个 OSTaskCreate( )创建任 任务都应该配有自己的堆栈, 务时,一定要注意所使 任 务 堆 栈 任务堆栈是任务的重要的组成 用的处理器对堆栈增长 部分

方向的支持是向上的还 OS_STK TaskStk[TASK_STK_SIZE]; 是向下的
//定义一个数组来作为任务堆栈

应用程序在创建一个新任务的 OS_STK *OSTaskStkInit( 时候,必须把在系统启动这个 由于各种处理器的寄存器及对堆栈的操作 其实,任务堆栈的初始化就是对该任务的 μC/OS-II在创建任务函数 void (*task)(void *pd), 任务时CPU各寄存器所需要的 方式不尽相同,因此该函数需要用户在进 虚拟处理器的初始化(复位)。 OSTaskCreate( )中通过调用任务 void *pdato, 任务堆栈的初始化 行μC/OS-II的移植时,按所使用的处理器 初始数据(任务指针、任务堆 OS_STK *ptos, 堆栈初始化函数OSTaskStkInit( ) 由用户来编写。实现这个函数的具体细节, 栈指针、程序状态字等等), INT16U opt 来完成任务堆栈初始化工作的 将在本书有关μC/OS-II移植的章节中做进 事先存放在任务的堆栈中 ); 一步的介绍

它的原型如下:

当应用程序调用函数OSTaskCreate( 任务控制块结构的主要成员

) 创建一个任务时,这个函数会调用系 当进行系统初始化时,初始化函 typedef struct os_tcb { 统函数OSTCBInit ( )来为任务控制块 OS_STK *OSTCBStkPtr; //指向任务堆栈栈顶的指针 数会按用户提供的任务数为系统创建 …… μC/OS-II用来记录任务的堆 进行初始化。这个函数首先为被创建 具有相应数量的任务控制块并把它们 任务控制块就相当于是一个 struct os_tcb *OSTCBNext;//指向后一个任务控制块的指针 任务从空任务控制块链表获取一个任 struct栈指针、任务的当前状态、任 os_tcb *OSTCBPrev; 链接为一个链表。 //指向前一个任务控制块的指针 任务的身份证,没有任务控 务控制块,然后用任务的属性对任务 由于这些任务控制块还没有对应 …… 务的优先级别等一些与任务管 空任务控制块链表 制块的任务是不能被系统承 控制块各个成员进行赋值,最后再把 任务控制块链表 INT16U OSTCBDly; //任务等待的时限(节拍数) 的任务,故这个链表叫做空任务块链 理有关的属性的表就叫做任务 INT8U OSTCBStat; //任务的当前状态标志 这个任务控制块链入到任务控制块链 认和管理的 表。即相当于是一些空白的身份证。 INT8U OSTCBPrio; //任务的优先级别 控制块 表的头部

任务控制块 (OS_TCB) 及任务控制块链表

…… } OS_TCB;

多任务操作系统的核心工作就是任 为了能够使系统清楚地知道,系 务调度。 统中哪些任务已经就绪,哪些还 所谓调度,就是通过一个算法在多 没有就绪,C/OS_II在RAM中设 个任务中确定该运行的任务,做这项工 任务就绪表 C/OS_II进行任务调度 作的函数就叫做调度器。 立了一个记录表,系统中的每个 及 C/OS_II进行任务调度的思想是 任务都在这个表中占据一个位置, 的依据就是任务就绪表 “近似地每时每刻总是让优先级最高的 任务调度 并用这个位置的状态(1或者0) 就绪任务处于运行状态” 。为了保证 来表示任务是否处于就绪状态, 这一点,它在系统或用户任务调用系统 函数及执行中断服务程序结束时总是调 这个表就叫做任务就绪状态表, 用调度器,来确定应该运行的任务并运 简称叫任务就绪表 行它


任务就绪表就是一个二维数组OSRdyTbl[ ]

为加快访问任务就绪表的 速度,系统定义了一个变 量OSRdyGrp来表明就绪表 每行中是否存在就绪任务。

OSRdyGrp D7 D6 D5 D4 D3 D2 D1 D0 x 7 6 OSRdyTbl[ ] 5 4 3 2

1/0 1/0 1/0 1/0 1/0 1/0 1/0 1/0 0 1 2 3 4 5 6 7 y

1

0

1/0 1/0 1/0 1/0 1/0 1/0 1/0 1/0 1/0 1/0 1/0 1/0 1/0 1/0 1/0 1/0 1/0 1/0 1/0 1/0 1/0 1/0 1/0 1/0 1/0 1/0 1/0 1/0 1/0 1/0 1/0 1/0 1/0 1/0 1/0 1/0 1/0 1/0 1/0 1/0 1/0 1/0 1/0 1/0 1/0 1/0 1/0 1/0 1/0 1/0 1/0 1/0 1/0 1/0 1/0 1/0 1/0 1/0 1/0 1/0 1/0 1/0 1/0 1/0

任务就绪表的示意图

在程序中,可以用类似下面的代码把优先 D D D D D D D D prio=29 0 0 0 1 1 1 0 1 级别为prio的任务置为就绪状态:
7 6 5 4 3 2 1 0

Y OSRdyGrp | =OSMapTbl[prio>>3]; =OSMapTbl[prio>>3 OSRdyGrp | =OSMapTbl[prio>>3]; OSRdyGrp D7 D6 D5 D4 D3 D2

OSRdyTbl[prio>>3]

如果要使一个优先级别为prio的任务脱离就绪 1 1 状态则可使用如下类似代码:
把prio为29的任务置为就绪状态

X OSRdyTbl[prio>>3] | = OSMapTbl[prio&0x07]; | = OSMapTbl[prio&0x07]; OSRdyTbl[3 ] D1 D0 D7 D6 D5 D4 D3 D2 D1 D0

if((OSRdyTbl[prio>>3]&=if((OSRdyTbl[prio>>3]&=OSMapTbl[prio&0 07])== ])==0 OSMapTbl[prio&0x07])==0) OSRdyGrp&=OSRdyGrp&=-OSMapTbl[prio>>3];

从任务就绪表中获取优先级别最高的就绪任务可用如下 OSRdyGrp OSRdyTbl[y ] 类似的代码: D7 D6 D5 D4 D3 D2 D1 D0 D7 D6 D5 D4 D3 D2 D1 D0
0 0 1 0 1 0 0 0 0 1 1 0 0 0 0 0

y = OSUnMapTal[OSRdyGrp]; //D5、D4、D3位 x= x == OSUnMapTal[OSRdyTbl[y]]; //D2、D1、D0位 y OSUnMapTal[OSRdyGrp]; OSUnMapTal[OSRdyTbl[y]]; prio = (y<<3)+x; //优先级别
D7 D6 D5 D4 D3 D2 D1 D0 0 1



prio=29

0

0

0

1

1

1

图5-6 在就绪表中查找最高优先级别任务的过程 y = OSUnMapTbl[OSRdyGrp]; prio = (INT8U)((y << 3) + OSUnMapTbl[OSRdyTbl[y]]);

小结
系统通过查找任务就绪表来 获取待运行任务的优先级
优先级

其实,调度 器在进行调 度时,在这 个位置还要 进行一下判 断:究竟是 待运行任务 是否为当前 任务,如果 是,则不切 换;如果不 是才切换, 而且还要保 存被中止任 务的运行环 境。

任务切换过程
处理器的SP=任 务块中保存的SP 恢复待运行任务 的运行环境 处理器的PC=任 务堆栈中的断点 地址

根据就绪表获得 获得待运行任务 待运行任务的任 的任务控制块 务控制块指针

如何获得待运行 任务的任务控制 块?

任务切换就是中止正在运行的任 后恢复待运 不要企图用PUSH和POP指令来使程序计数 务(当前任务),转而去运行另 1. 行任务的断 先保护被中 器PC压栈和出栈,因为没有这样的指令。 外一个任务的操作,当然这个任 点数据 止任务的断 点数据 务应该是就绪任务中优先级别最 需要由宏OS_TASK_SW( ) 来引发 高的那个任务 只好变通一下了。

任务切换宏 OS_TASK_SW( 一次中断或者一次调用来使 )

中断动作和过程调用指令可以使PC压栈; OSCtxSw( ) 中断返回指令可以使PC出栈。 执行任务切换工作 因此任务切换OSCtxSw( )必定是一个中断 OSCtxSw( 服务程序。

调度时机 很容易想到的调度时机就 对于实时系统来说,应该尽 是定时调度。 可能地实现即时调度。

应用程序通过调用OSTaskCreate( ) 函数来创 建一个任务,OSTaskCreate( )函数的原型如下: INT8U OSTaskCreate ( void (*task)(void *pd),//指向任务的指针 void *pdata, //传递给任务的参数 OS_STK *ptos, //指向任务堆栈栈顶的指针 INT8U prio //任务的优先级 )

用函数OSTaskCreate( ) 创建任务

void main(void) { …… //对C/OS-II进行初始化 OSInit( ); 一般来说,任务可以在调用函数OSStart( )启 …… 动任务调度之前来创建,也可以在任务中来创 OSTaskCreate (TaskStart,……);//创建任务TaskStart 建。但是,μC/OS-II有一个规定:在调用启动 OSStart( ); //开始多任务调度 } 任务函数OSStart( )之前,必须已经创建了至 少一个任务。因此,人们习惯上在调用函数 void TaskStart(void*pdata) { OSStart( )之前先创建一个任务,并赋予它最 ……//在这个位置安装并启动C/OS-II的时钟 高的优先级别,从而使它成为起始任务。然后 OSStatInit( ); //初始化统计任务 在这个起始任务中,再创建其他各任务。 ……//在这个位置创建其他任务 如果要使用系统提供的统计任务,则统计任务 for(;;) 的初始化函数也必须在这个起始任务中来调用 { 起始任务TaskStart的代码 } }

创建任务 的一般方法

初始化函数OSInit( )对数据结构进行初 函数OSInit( )将对μC/OS-II的所有的 在使用C/OS-II的所有服务之 始化时,主要要创建包括空任务控制块 全局变量和数据结构进行初始化,同时 前,必须要调用C/OS-II的初 链表在内的5个空数据缓冲区。同时,为 创建空闲任务OSTaskIdle,并赋之以最 始化函数OSInit( )对C/OS-II 了可以快速地查询任务控制块链表中的 低的优先级别和永远的就绪状态。如果 自身的运行环境进行初始化。)还要创 各个元素,初始化函数OSInit( 用户应用程序还要使用统计任务的话 建一个数组 (常数OS_TASK_STAT_EN=1),则 OSTCBPrioTbl[OS_LOWEST_PRIO + 1], OSInit( )还要以优先级别为 OS_LOWEST_PRIO-1来创建统计任务 在这个数组中,按任务的优先级别的顺 序把任务控制块的指针存放在了对应的 元素中

C/OS-II的 初始化

μC/OS-II进行任务的管理是从调用
启动函数OSStart( )开始的,当然 其前提条件是在调用该函数之前至 少创建了一个用户任务

μC/OS-II的启动

第3章

μC/OS-Ⅱ的中断和时钟 C/OS-

本章主要内容:

C/OS-II系统响应中断的过程为:系 统接收到中断请求后,这时如果 注意!中断服务子程序运行结束 CPU处于中断允许状态(即中断是 之后,系统将会根据情况进行一 开放的),系统就会中止正在运行 μC/OS-II系统 的当前任务,而按照中断向量的指 次任务调度去运行优先级别最高 向转而去运行中断服务子程序;当 响应中断的过程 的就绪任务,而并不是一定要接 中断服务子程序的运行结束后,系 续运行被中断的任务的。 统将会根据情况返回到被中止的任 务继续运行或者转向运行另一个具 有更高优先级别的就绪任务。

中断请求 关闭中断 转到中断向量 响 应 任 务 响 应 时 间 中 断 恢 复 通知内核 ISR 通知内核退出 ISR 恢复CPU寄存器 中断 中断 复 务 ISR给任务发信号 应 时 间 中 恢复CPU寄存器 恢 断 通知内核退出 ISR 响 保存CPU寄存器 任 断 中

任务 任务

任务 任务

中断的响应过程

void OSIntEnter (void) { if (OSRunning == TRUE) { if (OSIntNesting < 255) { OSIntNesting++; //中断嵌套层数计数器加一 } } }

void OSIntExit (void) { #if OS_CRITICAL_METHOD == 3 OS_CPU_SR cpu_sr; #endif if (OSRunning == TRUE) { OS_ENTER_CRITICAL( ); if (OSIntNesting > 0) { OSIntNesting--; //中断嵌套层数计数器减一 中断嵌套层数计数器减一 } if ((OSIntNesting == 0) && (OSLockNesting == 0)) { OSIntExitY = OSUnMapTbl[OSRdyGrp]; OSPrioHighRdy = (INT8U)((OSIntExitY << 3) + OSUnMapTbl[OSRdyTbl[OSIntExitY]]); if (OSPrioHighRdy != OSPrioCur) { OSTCBHighRdy = OSTCBPrioTbl[OSPrioHighRdy]; OSCtxSwCtr++; OSIntCtxSw( ); } } OS_EXIT_CRITICAL( ); } }

在中断服务程序中调用的 负责任务切换工作的函数 OSIntCtxSw( )叫做中断级 OSIntCtxSw( ) 任务切换函数
{ OSTCBCur = OSTCBHighRdy; //任务控制块的切换 OSPrioCur=OSPrioHighRdy; SP = OSTCBHighRdy->OSTCBStkPtr; //SP指向待运行任务堆栈 用出栈指令把R1,R2,……弹入 弹入CPU的通用寄存器; 的通用寄存器; 用出栈指令把 弹入 的通用寄存器 //中断返回,使PC指向待运行任务 RETI; }

第二种方法稍微复杂一些,但可以使CPU中断允许标志 的状态,在临界段前和临界段后不发生改变。在宏 由于各厂商生产的CPU和C编译器的关中断 OS_ENTER_CRITICAL( )中,把CPU的允许中断标志保持 和开中断的方法和指令不尽相同,为增强 第一种方法最简单,即直接使用处理器的 在应用程序中经常有一些代码段 到堆栈中,然后再关闭中断,这样在临界段结束时,即 μC/OS-II的可移植性(即在 μC/OS-II的 在调用宏OS_EXIT_CRITICAL( )时只要把堆栈中保存的 开中断和关中断指令来实现宏,这时需要 必须不受任何干扰地连续运行, CPU允许中断状态恢复就可以了。这两个宏的示意性代 各个C函数中尽可能地不出现汇编语言代 令常数OS_CRITICAL_METHOD=1。其示 这样的代码段叫做临界段。因此, 码如下:

码),μC/OS-II用两个宏来实现中断的开 意性代码为: 为了使临界段在运行时不受中断 放和关闭,而把与系统的硬件相关的关中 #define OS_ENTER_CRITICAL( ) \ 所打断,在临界段代码前必须用 asm(“PUSH OS_ENTER_CRITICAL( )\ PSW”) \ /*通过保存程序状态字来保存中 断和开中断的指令分别封装在这两个宏中: #define 关中断指令使CPU屏蔽中断请求, 断允许标志*/ asm(“DI”) \\关中断 asm(“DI”) 而在临界段代码后必须用开中断 //关中断 OS_ENTER_CRITICAL( ) 指令解除屏蔽使得CPU可以响应 #define OS_EXIT_CRITICAL( ) OS_EXIT_CRITICAL( ) #define OS_EXIT_CRITICAL( )\ asm(“POP 中断请求 //恢复中断允许标志 PSW”) asm(“EI”) \\开中断

应用程序中的临界段

void OSTimeTick (void) 时钟节拍服务函数 { …… OSTimeTickHook( ); …… OSTime++; //记录节拍数 …… if (OSRunning = = TRUE) { ptcb = OSTCBList; while (ptcb->OSTCBPrio != OS_IDLE_PRIO) { OS_ENTER_CRITICAL( ); if (ptcb->OSTCBDly != 0) { if (--ptcb->OSTCBDly = = 0) //任务的延时时间减一 { if ((ptcb->OSTCBStat & OS_STAT_SUSPEND) = = OS_STAT_RDY) { OSRdyGrp |= ptcb->OSTCBBitY; OSRdyTbl[ptcb->OSTCBY] |= ptcb->OSTCBBitX; } else { ptcb->OSTCBDly = 1; } } } ptcb = ptcb->OSTCBNext; OS_EXIT_CRITICAL( ); } }

void OSTickISR(void) 这是系统时钟中断服务程序 { C/OS-II与大多数计算机系统一样,用硬 保存CPU寄存器; 件定时器产生一个周期为ms级的周期性中 调用OSIntEnter( ); //记录中断嵌套层数 断来实现系统时钟,最小的时钟单位就是 if (OSIntNesting = = 1; { 两次中断之间相间隔的时间,这个最小时 OSTCBCur->OSTCBStkPtr Tick)。 钟单位叫做时钟节拍(Time= SP; //保存堆栈指针 } 硬件定时器以时钟节拍为周期定时地产生 调用OSTimeTick( ); //节拍处理 调用 中 清除中断; 中 断 的 中 断 服 务 程 序 叫 做 断,该 开中断; OSTickISR( )。中断服务程序通过调用函数 //中断嵌套层数减一 调用OSIntExit( ); OSTimeTick( )来完成系统在每个时钟节拍 恢复CPU寄存器; 时需要做的工作。 中断返回; }

函数OSTimeTick( )的 任务,就是在每个时 钟节拍了解每个任务 的延时状态,使其中 已经到了延时时限的 C/OS-II的系统时钟 非挂起任务进入就绪 状态。

由于嵌入式系统的任务是一个无限循环, void OSTimeDly (INT16U ticks) 这是系统提供的延时函数 { 并且C/OS-II还是一个抢占式内核,所 #if OS_CRITICAL_METHOD = = 3 以为了使高优先级别的任务不至于独占 OS_CPU_SR cpu_sr; #endif CPU,可以给其他任务优先级别较低的 if (ticks > 0) { 任务获得CPU使用权的机会,C/OS-II OS_ENTER_CRITICAL( ); if ((OSRdyTbl[OSTCBCur->OSTCBY] 规定:除了空闲任务之外的所有任务必 &= ~OSTCBCur->OSTCBBitX) = = 0) { 须在任务中合适的位置调用系统提供的 OSRdyGrp 函数OSTimeDly( ),使当前任务的运行 &= ~OSTCBCur->OSTCBBitY; //取消当前任务的就绪状态 } 延时(暂停)一段时间并进行一次任务 OSTCBCur->OSTCBDly = ticks;//延时节拍数存入任务控制块 OS_EXIT_CRITICAL( ); 调度,以让出CPU的使用权。 OS_Sched( ); //调用调度函数

任务的延时

} }

取消任务延 时函数

INT8U OSTimeDlyResume( INT8U prio);

其他用来管理时间的函数
INT32U OSTimeGet( void ); void OSTimeSet( INT32U ticks );
设置系统时 间函数

获得系统时 间函数

系统中的多个任务在运行时,经常需要互相 例如,两个任务:任务A和任务B,它们 无冲突地访问同一个共享资源,或者需要互 需要通过访问同一个数据缓冲区合作完 例如,任务A和任务B共享一台打印机, 总之,多个任务共享同一资源或有工作 相支持和依赖,甚至有时还要互相加以必要 成一项工作,任务A负责向缓冲区写入 如果系统已经把打印机分配给了任务A, 的限制和制约,才保证任务的顺利运行。因 顺序要求时,在正式工作之前要互相打 数据,任务B负责从缓冲区读取该数据。 则任务B因不能获得打印机的使用权而应 此,操作系统必须具有对任务的运行进行协 招呼 。 显然,当任务A还未向缓冲区写入数据 调的能力,从而使任务之间可以无冲突、流 该处于等待状态,只有当任务A把打印机 时(缓冲区为空时),任务B因不能从 黄宏:别走啊! 畅地同步运行,而不致导致灾难性的后果。 释放后,系统才能唤醒任务B使其获得打 宋丹丹:我自己的腿,我爱走就走,你管不着! 缓冲区得到有效数据而应该处于等待状 与人们依靠通信来互相沟通,从而使人际关 黄宏:腿是你自己的,但手是咱俩的呀! 印机的使用权。如果这两个任务不这样 态,只有等任务A向缓冲区写入了数据 系和谐、工作顺利的做法一样,计算机系统 做,那么也会造成极大的混乱 。 是依靠任务之间的良好通信来保证任务与任 之后,才应该通知任务B去取数据。 务的同步的。

第4章 任务的同步与通信

任务间的同步依赖于任务间的通信。 在C/OS-II中,是使用信号量、邮 箱(消息邮箱)和消息队列这些被 称作事件的中间环节来实现任务之 间的通信的。

事件

黄宏

宋丹丹

一个简单的信号量

发信方

1/0

收信方

共享资源

如果一个正在等待的任务具备了可以运行的条件,那么就要使 把 一 个 任 务 置 于 等 待 状 )函数。该函数 如果一个正在等待事件的任务已经超过了等待的时间, 它进入就绪状态。这时要调用OS_EventTaskRdy(态 要 调 用 为了把描述事件的数据结构统一起来, OS_EventTaskWait( )函数。该函数的原型为: 却仍因为没有获取事件等原因而未具备可以运行的条件, 的作用就是把调用这个函数的任务在任务等待表中的位置清0 μC/OS-II使用叫做事件控制块ECB的数 却又要使它进入就绪状态,这时要调用OS_EventTO( )函 (解除等待状态)后,再把任务在任务就绪表中对应的位置1, 数。 然后引发一次任务调度。 据结构来描述诸如信号量、邮箱(消息 void OS_EventTaskWait ( OS_EventTO( )函数的原型为: typedef struct 邮箱)和消息队列这些事件。事件控制 OS_EVENT *pevent //事件控制块的指针 OS_EventTaskRdy( )函数的原型为: { void块中包含包括等待任务表在内的所有有 ) );OS_EventTO ( INT8U OSEventType; //事件的类型 事件的类型 OS_EVENT *pevent //事件控制块的指针 INT8U OS_EventTaskRdy ( 关事件的数据 INT16U OSEventCnt; //信号量计数器 信号量计数器 函数OS_EventTaskWait ( ),将在任务调用函数 ); OS_EVENT *pevent, //事件控制块的指针 void *OSEventPtr; //消息或消息队列的指针 消息或消息队列的指针 OS×××Pend( 请 求 等待事件的任务组 void *msg,OSEventGrp; ) //未使用 一 个 事 件 时 , 被 INT8U //等待事件的任务组 函数OS_EventTO ( )将在任务调用OS×××Pend( ) 请求 INT8U msk //清除TCB状态标志掩码 OS×××Pend( )所调用。 INT8U OSEventTbl[OS_EVENT_TBL_SIZE];//任务等待表 一个事件时,被函数OS×××Pend( )所调用。 任务等待表 ); } OS_EVENT;

事件控制块

函数OS_EventTaskRdy ( )将在任务调用函数OS×××Post ( ) 发 送一个事件时,被函数OS×××Post ( )所调用。

在C/OS-II初始化时,系统会在初始化函数 OSInit( )中按应用程序使用事件的总数 OS_MAX_EVENTS(在文件OS_CFG.H中定义), 创建OS_MAX_EVENTS个空事件控制块并借用 成员OSEventPtr作为链接指针,把这些空事件控 制块链接成一个单向链表。由于链表中的所有控 制块尚未与具体事件相关联,故该链表叫做空事 件控制块链表。以后,每当应用程序创建一个事 件时,系统就会从链表中取出一个空事件控制块, 并对它进行初始化以描述该事件。而当应用程序 删除一个事件时,就会将该事件的控制块归还给 空事件控制块链表

空事件控制块链表

在使用信号量之前,应用程序必须调用函数 任 务 通 过 调 用 函 数 OSSemPend( ) 请 求 信 号 量 , 函 数 任务获得信号量,并在访问共享资源结束以后,必须要释放信 应OSSemPend( 不 需 要 某 个 信 号 量建 , 那 个可 以 调 用量 数 用 程 序 如 果 )的原型如下: 创 了 一 么 信 号 函 , OSSemCreate( ) 来 号量,释放信号量也叫做发送信号量,发送信号量需调用函数 OSSemDel( )来删除该信号量,这个函数的原型为: OSSemPost ( )。OSSemPost ( )函数在对信号量的计数器操作之前, OSSemCreate( )的原型为: void OSSemPend ( OS_EVENT *pevent, //信号量的指针 首先要检查是否还有等待该信号量的任务。如果没有,就把信 INT16U *OSSemDel ( //等待时限 OS_EVENT timeout, 号量计数器OSEventCnt加一;如果有,则调用调度器OS_Sched( ) INT8U *err); *OSSemCreate //错误信息 OS_EVENT *pevent, //信号量的指针 ( OS_EVENT 去运行等待任务中优先级别最高的任务。 INT8U opt, //删除条件选项 OSSemPost ) 函数OSSemPost ( )的原型为: //信号量计数器初值 INT16U 参数pevent是被请求信号量的指针。 INT8U *err cnt //错误信息 ); ); 为防止任务因得不到信号量而处于长期的等待状态,函数 INT8U OSSemPost ( OSSemPend允许用参数timeout设置一个等待时间的限制, OS_EVENT *pevent //信号量的指针 当任务等待的时间超过timeout时可以结束等待状态而进入 ); 函数的返回值为已创建的信号量的指针。 就绪状态。如果参数timeout被设置为0,则表明任务的等 待时间为无限长。 调用函数成功后,函数返回值为OS_ON_ERR,否则会根据具体 错误返回OS_ERR_EVENT_TYPE、OS_SEM_OVF。

信号量及其操作

解决问题的办法之一,是使获得信号量任 通过例子可以发现,使用信号量的任务是否 图4-15描述了A、B、C三个任务的运行 务的优先级别在使用共享资源期间暂时提 在可剥夺型内核中,当任务以独占方 能够运行是受任务的优先级别和是否占用信 情况。其中,任务A的优先级别高于任 升到所有任务最高优先级的高一个级别上, 式使用共享资源时,会出现低优先级 号量两个条件约束的,而信号量的约束高于 务B,任务B的优先级别高于任务C。任 以使该任务不被其他的任务所打断,从而 任务先于高优先级任务而被运行的现 优先级别的约束。于是当出现低优先级别的 务A和任务C都要使用同一个共享资源S, 能尽快地使用完共享资源并释放信号量, 象,这种现象叫做任务优先级反转。 任务与高优先级别的任务使用同一个信号量, 而用于保护该资源的信号量在同一时间 然后在释放了信号量之后再恢复该任务原 在一般情况下是不允许出现这种任务 而系统中还存有别的中等优先级别的任务时, 只能允许一个任务以独占的方式对该资 来的优先级别。 优先级反转现象的,下面就对优先级 如果低优先级别的任务先获得了信号量,就 源进行访问,即这个信号量是一个互斥 的反转现象做一个详细的分析,以期 会使高级别的任务处于等待状态,而那些不 型信号量。 找出原因及解决方法。 使用该信号量的中等级别的任务却可以剥夺 低优先级别的任务的CPU使用权而先于高优 先级别的任务而运行了。

互斥型信号量和 任务优先级反转

在描述互斥型信号量的事件控制块中,除了 当任务需要访问一个独占式共享资源时,就要调用函 成员OSEventType要赋以常数 OS_EVENT *OSMutexCreate ( 数OSMutexPend( )来请求管理这个资源的互斥型信号 INT8UOS_EVENT_TYPE_MUTEX以表明这是一个 prio, //优先级别 量,如果信号量有信号(OSEventCnt的低8位为0xFF), 任务可以通过调用函数OSMutexPost( )发送一个互斥型信 INT8U互斥型信号量和仍然没有使用成员 *err //错误信息 则意味着目前尚无任务占用资源,于是任务可以继续 号量,这个函数的原型为: ); 运行并对该资源进行访问,否则就进入等待状态,直 OSEventPtr之外,成员OSEventCnt被分成了 至占用这个资源的其他任务释放了该信号量。 低8位和高8位两部分:低8位用来存放信 INT8U OSMutexPost ( 函数OSMutexCreate( )从空事件控制块链表获取一个事件控制 函数OSMutexPend( //互斥型信号量指针 号值(该值为0xFF时,信号为有效,否则信 OS_EVENT *pevent )的原型为: 块,把成员OSEventType赋以常数OS_EVENT_TYPE_MUTEX ); 号为无效),高8位用来存放为了避免出现 以表明这是一个互斥型信号量,然后再把成员OSEventCnt的高 void OSMutexPend ( 优先级反转现象而要提升的优先级别prio。 8 位 赋 以 prio ( 欲 提 升 的 优 先 级 别 ) , 低 8 位 赋 以 常 数

创 建 互 斥 型 信 号 量 需 要 调 用 函 数 OSMutexCreate( ) 。 函 数 OSMutexCreate( )的原型如下:

互斥型信号量

OS_EVENT *pevent, //互斥型信号量指针 OS_MUTEX_AVAILABLE ( 该//等待时限 0xFFFF ) 的 低 8 位 常数值为 INT16U timeout, (0xFF)以表明信号量尚未被任何任务所占用,处于有效状态。 INT8U *err //错误信息 );

创建邮箱需要调用函数OSMboxCreate ( ),这个函 任务可以通过调用函数OSMboxPost ( )向消息邮箱发送消 数的原型为: 息,这个函数的原型为: 如果把数据缓冲区的指针赋给一个事件 控制块的成员OSEventPrt,同时使事件 当一个任务请求邮箱时需要调用函数OSMboxPend( ),这 OS_EVENT *OSMboxCreate ( INT8U OSMboxPost ( 控制块的成员OSEventType为常数 个函数的主要作用就是查看邮箱指针OSEventPtr是否为 OS_EVENT *pevent, //消息邮箱指针 void *msg //消息指针 void OS_EVENT_TYPE_MBOX,则该事件 *msg //消息指针 ); NULL,如果不是NULL就把邮箱中的消息指针返回给调 用函数的任务,同时用OS_NO_ERR通过函数的参数err通 ); 控制块就叫做消息邮箱,消息邮箱是在 函数中的参数msg为消息的指针,函数的返回值为 知任务获取消息成功;如果邮箱指针OSEventPtr是NULL, 两个需要通信的任务之间通过传递数据 消息邮箱的指针。 则使任务进入等待状态,并引发一次任务调度。 缓冲区指针的方法来通信的。 调用函数OSMboxCreate ( )需先定义msg的初始值。 函数OSMboxPend( )的原型为: 在一般的情况下,这个初始值为NULL;但也可以 void *OSMboxPend ( 事先定义一个邮箱,然后把这个邮箱的指针作为参 OS_EVENT *pevent, //请求消息邮箱指针 数传递到函数OSMboxCreate ( )中,使之一开始就 INT16U timeout, //等待时限 指向一个邮箱。 INT8U *err //错误信息

消息邮箱及其操作

);

任务需要通过调用函数OSQPost( )或OSQPostFront( )来向消息 队列发送消息。函数OSQPost( )以FIFO(先进先出)的方式 使用消息队列可以在任务之间传递多条消息。消息 请求消息队列的目的是为了从消息队列中获取消息。任务 组织消息队列,函数OSQPostFront( )以LIFO(后进先出)的 队列由三个部分组成:事件控制块、消息队列和消 为了对图 所示的 消息指针数 组进行 有 效的管 理 , 请求消息队列需要调用函数OSQPend( ),该函数的原型为: 方式组织消息队列。这两个函数的原型分别为: 息。 C/OS-II把消息指针数组的基本参数都记录在一个叫 其中,可以移动的指针为OSQIn和OSQOut,而指针 INT8U OSQPost( 控 制 块 成 员 OSEventType 的 值 置 为 当把事 做队列控制块的结构中,队列控制块的结构如下: OSQStart和OSQEnd只是一个标志(常指针)。当可移动的 void*OSQPend( 件 创建一个消息队列首先需要定义一指针数组,然后把各个 OS_EVENT*pevent, //消息队列的指针 在C/OS-II初始化时,系统将按文件 OS_EVENT_TYPE_Q时,该事件控制块描述的就 指针OSQIn或OSQOut移动到数组末尾,也就是与OSQEnd OS_EVENT*pevent, //所请求的消息队列的指针 消息数据缓冲区的首地址存入这个数组中,然后再调用函 void*msg struct os_q OS_CFG.H中的配置常数OS_MAX_QS定 //消息指针 是一个消息队列。 //等待时限 typedef 相等时,可移动的指针将会被调整到数组的起始位置 函 数 INT16U timeout, 数 OSQCreate( ) 来 创 建 消 息 队 列 。 创 建 消 息 队 列 );OSQStart。也就是说,从效果上来看,指针OSQEnd与 义OS_MAX_QS个队列控制块,并用队列 { 消息队列的数据结构如图4-21所示。从图中可以看 INT8U*err //错误信息 OSQCreate( )的原型为: 控制块中的指针OSQPtr将所有队列控制块 struct os_q *OSQPtr; 到,消息队列相当于一个共用一个任务等待列表的 ); OSQStart等值。于是,这个由消息指针构成的数组就头尾衔 和 void **OSQStart; 链接为链表。由于这时还没有使用它们, 消息邮箱数组,事件控制块成员OSEventPtr指向了 接起来形成了一个如图所示的循环的队列。 OS_EVENT OSQCreate( 故这个链表叫做空队列控制块链表 一个叫做队列控制块(OS_Q)的结构,该结构管 void **OSQEnd; void**start, //指针数组的地址 INT8U OSQPost( 理了一个数组MsgTbl[ ],该数组中的元素都是一些 void **OSQIn; INT16U**OSQOut; OS_EVENT*pevent, //消息队列的指针 指向消息的指针。 void size //数组长度 ); INT16U OSQSize; void*msg //消息指针 ); INT16U OSQEntries; } OS_Q; 函数中的参数msg为待发消息的指针。

消息队列及其操作

在实际应用中,任务常常需要与多个事件同步, 即要根据多个信号量组合作用的结果来决定任务 的运行方式。C/OS-II为了实现多个信号量组合 的功能定义了一种特殊的数据结构——信号量集。 信号量集所能管理的信号量都是一些二值信号, 所有信号量集实质上是一种可以对多个输入的逻 辑信号进行基本逻辑运算的组合逻辑,其示意图 如图5-1所示

信号量集

不同于信号量、消息邮箱、消息队列等事件,C/OS-II不 使用事件控制块来描述信号量集,而使用了一个叫做标志 组的结构OS_FLAG_GRP。 OS_FLAG_GRP结构如下: typedef struct{ INT8U OSFlagType; //识别是否为信号量集的标志 void *OSFlagWaitList;//指向等待任务链表的指针 *OSFlagWaitList;// OS_FLAGS OSFlagFlags; //所有信号列表 }OS_FLAG_GRP;

信号量集的标志组

成员OSFlagWaitList是一个指针,当一个信号量集被创建后, 这个指针指向了这个信号量集的等待任务链表。

从 等 待 任 务 链 表 中 删 除 一 个 节 点 的 函 数 为 与其他前面介绍过的事件不同,信号量集用一个双向链表来 OS_FlagUnlink( ),这个函数的原型为: 组织等待任务,每一个等待任务都是该链表中的一个节点 给等待任务链表添加节点的函数为OS_FlagBlock( ),这个函数 (Node)。标志组OS_FLAG_GRP的成员OSFlagWaitList就 的原型为: void OS_FlagUnlink (OS_FLAG_NODE 指向了信号量集的这个等待任务链表。 *pnode); 等待任务链表节点OS_FLAG_NODE的结构如下: static void OS_FlagBlock ( 这个函数将在发送信号量集函数OSFlagPost( )中被调用。 OS_FLAG_GRP *pgrp, //信号量集指针 typedef struct { OS_FLAG_NODE *pnode, //待添加的等待任务节点指针 void *OSFlagNodeNext; //指向下一个节点的指针 OS_FLAGS flags, //指定等待信号的数据 void *OSFlagNodePrev; //指向前一个节点的指针 INT8U wait_type, //信号与等待任务之间的逻辑 void *OSFlagNodeTCB; //指向对应任务控制块的指针 INT16U timeout //等待时限 void *OSFlagNodeFlagGrp; //反向指向信号量集的指针 ); OS_FLAGS OSFlagNodeFlags; //信号过滤器 INT8U OSFlagNodeWaitType;//定义逻辑运算关系的数据 这个函数将在请求信号量集函数OSFlagPend ( )中被调用。 } OS_FLAG_NODE;

等待任务链表

任务可以通过调用函数OSFlagCreate ( )来创建一个 任务可以通过调用函数OSFlagPost ( )向信号量集发信号, 信号量集。OSFlagCreate ( OSFlagPost ( )函数的原型为: )的函数原型为: OS_FLAG_GRP *OSFlagCreate ( OS_FLAGS OSFlagPost ( OS_FLAGS *pgrp, //信号的初始值 OS_FLAG_GRP flags, //信号量集指针 INT8U flags, //错误信息 OS_FLAGS*err //选择所要发送的信号 ); INT8U opt, //信号有效的选项 任务可以通过调用函数OSFlagPend( )请求一个信号量 INT8U *err //错误信息 ); 集,OSFlagPend( )函数的原型为:

信号量集的操作

OS_FLAGS OSFlagPend ( 所谓任务向信号量集发信号,就是对信号量集标志组中的信号 OS_FLAG_GRP *pgrp, //所请求的信号量集指针 进行置“1”(置位)或置“0”(复位)的操作。至于对信号量 OS_FLAGS flags, //滤波器 集中的哪些信号进行操作,用函数中的参数flags来指定;对指 INT8U wait_type, //逻辑运算类型 定的信号是置“1”还是置“0”,用函数中的参数opt来指定 //等待时限 (optINT16U timeout, = OS_FLAG_SET为置“1”操作;opt = OS_FLAG_CLR为 INT8U *err //错误信息 置“0”操作)。 );

应用程序在运行中为了某种特殊需要,经常 需要临时获得一些内存空间,因此作为一个 比较完善的操作系统必须具有动态分配内存 的能力。 能否合理、有效地对内存储器进行分配和管 理,是衡量一个操作系统品质的指标之一。 特别地对于实时操作系统来说,还应该保证 系统在动态分配内存时,它的执行时间必须 是可确定的。C/OS-II改进了ANSI C用来动态 分配和释放内存的malloc( )和free( )函数,使 它们可以对大小固定的内存块进行操作,从 而使 malloc( )和free( )函数的执行时间成为可 确定的,满足了实时操作系统的要求。

第6章 内存的 动态分配

C/OS-II对内存进行两级管理,即把一个大 片连续的内存空间分成了若干个分区,每个 分区又分成了若干个大小相等的内存块来进 行管理。操作系统以分区为单位来管理动态 内存,而任务以内存块为单位来获得和释放 动态内存。内存分区及内存块的使用情况则 由表——内存控制块来记录。 本节首先介绍内存分区和分区中的内存块, 然后再介绍内存控制块。

内存控制块

为了使系统能够感知和有效地管理内存分区,C/OS-II给每个内 应用程序如果要使用动态内存的话,则要首先在内存中划分 存分区定义了一个叫做内存控制块(OS_MEM)的数据结构。系 出可以进行动态分配的区域,这个划分出来区域叫做内存分 统就用这个内存控制块来记录和跟踪每一个内存分区的状态。内 区,每个分区要包含若干个内存块。C/OS-II要求同一个分区 存控制块的结构如下: 中的内存块的字节数必须相等,而且每个分区与该分区的内 存块的数据类型必须相同。 typedef struct { 在内存中划分一个内存分区与内存块的方法非常简单,只要 void *OSMemAddr; //内存分区的指针 定义一个二维数组就可以了,其中的每个一维数组就是一个 void *OSMemFreeList; //内存控制块链表的指针 内存块。例如,定义一个用来存储INT16U类型数据,有10个 INT32U OSMemBlkSize; //内存块的长度 内存块,每个内存块长度为10的内存分区的代码如下: INT32U OSMemNBlks; //分区内内存块的数目 INT32U OSMemNFree; //分区内当前可分配的内存块的数目 INT16U IntMemBuf[10][10]; } OS_MEM;

可动态分配内存的划分

需要注意的是,上面这个定义只是在内存中划分出了分区及 当应用程序调用函数OSMemCreate( )建立了一个内存分区之后, 内存块的区域,还不是一个真正的可以动态分配的内存区, 内存控制块与内存分区和内存块之间的关系如图 如图6-1(a)所示。只有当把内存控制块与分区关联起来之后, 系统才能对其进行相应的管理和控制,它才能是一个真正的 动态内存区

其中参数pdata是一个OS_MEM_DATA类型的结构,该结构的定 划分了欲使用的分区和内存块之后,应用程序可以通过 当应用程序不再使用一个内存块时,必须要及时地将它 义如下: 函 数 OSMemCreate( ) 来 建 立 一 个 内 存 分 区 , 调用 释放。应用程序通过调用函数OSMemPut( )来释放一个内 OSMemCreate( ) 函数的原型为: 存块,OSMemPut( )函数的原型为: typedef struct { OS_MEM *OSMemCreate( void OSMemPut //内存分区的指针 INT8U*OSAddr; ( 应用程序可以通过调用函数OSMemQuery( )来查询一个分区目 void *OSFreeList; //分区内内存块链表的头指针 void*addr, //内存分区的起始地址 OS_MEM *pmem, //内存块所属内存分区的指针 前的状态信息,函数函数OSMemQuery( )的原型为: INT32U nblks, INT32U //内存块的长度 void *pblkOSBlkSize; //分区中内存块的数目 //待释放内存块的指针 //分区内内存块的数目 //每个内存块的字节数 )INT32U OSNBlks; );INT32U blksize, INT8U OSMemQuery ( //错误信息 在应用程序需要一个内存块时,应用程序可以通过调用函 INT32U OSNFree; INT8U *err //分区内空闲内存块的数目 OS_MEM OSNUsed; 向 某 内//待查询的内存控制块的指针 块 , 数 OSMemGet( 存 分 //已被分配的内存块数目 区请求获得一个内存 ); INT32U *pmem, ) OS_MEM_DATA OSMemGet( )函数的原型为: } OS_MEM_DATA; *pdata//存放分区状态信息的结构的指针 ) void *OSMemGet ( OS_MEM *pmem, //内存分区的指针 INT8U *err //错误信息 );

动态内存的管理

关于C/OS-II的硬件抽象层
C/OS-II除了提供 了需要在移植时 修改的CPU和定时 器的抽象层之外, 未提供其他硬件 抽象层。 这些硬件抽象层 需要用户根据硬 件平台自行编写。
应用程序

高级语言 封装的函数

硬件抽象层

其他硬件

关于C/OS-II在PC机上 的硬件抽象层(续)
C/OS-II在PC 机上利用了PC 机原有的DOS 硬件抽象层。 C/OS-II把用 到的这些DOS 函数封装成了 以PC开头的函 数。
应用程序 PC开头 的函数

DOS的 硬件抽象层

PC的硬件

void main (void) { char* s_M="M"; //定义要显示的字符 OSInit( ); //初始化C/OS-II PC_DOSSaveReturn( ); //保存Dos环境 PC_VectSet(uCOS, OSCtxSw);/*安装C/OS-II任务 切换中断向量*/ OSTaskCreate( MyTask, //创建任务MyTask s_M, //给任务传递参数 &MyTaskStk[TASK_STK_SIZE - 1], //设置任务堆栈栈顶指针 0 // MyTask优先级别为0 ); OSStart( ); //启动多任务管理 }

void MyTask (void *pdata) { …… for (;;) {…… if (PC_GetKey(&key) == TRUE) { if (key == 0x1B) { PC_DOSReturn( ); //返回 返回Dos 返回 } } OSTimeDlyHMSM(0, 0, 1, 0); } }

什么是嵌入式系统
嵌入式系统是嵌入到宿主对象中的一 种专用计算机系统,它的作用是对宿 主对象进行自动控制从而提高其智能 水平。

嵌入性 专用性 是嵌入式系统的三个基本特征。 计算机系统

嵌入式系统的应用

嵌入式系统

嵌入式系统的特点

实时性。目前,嵌入式系统广泛应用于生产过 专用性强。嵌入式系统通常是面向某个 微型化。嵌入式系统芯片 程控制、数据采集、传输通信等场合,这些应 特定应用的,所以嵌入式系统的硬件为 内部存储器的容量通常不 用的共同特点就是要求系统能快速响应事件, 特定用户群来设计的,它通常都具有某 会很大(1MB以内)、一 因此要求嵌入式操作系统要有较强的实时性。 种专用性的特点。 易移植性。为了适应 运行在嵌入式硬件平台上,对整个 般也不配置外存,加之电 多种多样的硬件平台, 源的容量较小(常常是用 系统及其所操作的部件、装置等资 嵌入式操作系统应该 电池甚至是微型电池供 功耗小。由于嵌入式系统中的软件一般 源进行统一协调、指挥和控制的系 可以在不做大量修改 电)、外部设备的多样化, 不是存贮于磁盘等载体中,而都固化在 的情况下能稳定地运 统软件就叫做嵌入式操作系统。 因而不允许嵌入式操作系 存储器芯片或单片系统的存储器之中, 行在不同的平台上。 统占用较大的资源,所以 所以它具有功耗小的特点,从而便于把 可裁剪性。嵌入式操作系统运行的硬件平台多种多样, 在保证应用功能的前提下, 高可靠性。嵌入式系统广泛地应用于军事武器、 它应用在飞机、舰船、数码相机等移动 其宿主对象更是五花八门。所以要求嵌入式操作系统中 嵌入式操作系统的规模越 设备中。 航空航天、交通运输、重要的生产设备领域, 提供的各个功能模块可以让用户根据需要选择使用,即 小越好。 所以要求嵌入式操作系统必须有极高的可靠性, 要求它具有良好的可剪裁性。 对关键要害的应用还要提供必要的容错和防错 措施,以进一步提高系统的可靠性。

特点是:

如果操作系统能使计算机系统 对实时系统有两个基本要求: 及时响应外部事件的请求,并 能及时控制所有实时设备与实 如果要求系统必须 嵌入式实时操作系统 第一,实时系统的计算必须产生 时任务协调运行,且能在一个 在极严格的时间内 正确的结果,称为逻辑或功能正 规定的时间内完成对事件的处 完成实时任务,这 确(logical or functional 理,那么这种操作系统就是一 样的系统就叫做硬 correctness); 个实时操作系统(Real Time 实时操作系统;否 Operation则就叫做软实时操 System, RTOS)。 第二,实时系统的计算必须在预 作系统。 定的周期内完成,称为时间正确 (timing correctness)。

多任务 计算机在执行应用程序时,经常要用I/O设备 进行数据的输入和输出,而I/O设备在工作时 可剥夺型内核中,CPU总是运行多个任务中优 要求作为进行任务切换的调度器的运行时间 总的需要一段时间的,于是在I/O设备工作期 先级别最高的那个任务,即使CPU正在运行某 应该是固定的,即调度器进行任务切换所用 间,如果CPU没有别的任务的话,那么就只能 个低优先级别的任务,当有高优先级别的任 的时间不能受应用程序中其他因素(例如任 等待,因此就会使计算机运行一个应用程序 务准备就绪时,该高级别的任务就会剥夺正 务数目)的影响。 所花的时间比较长,也就是说,这种系统的 在运行任务的CPU使用权,而使自己获得CPU 实时性较差。 的使用权。 由于外部事件的发生常常是以一个中断申请 如果把一个大的任务分解成多个可并行运行 由于可剥夺型内核实时性较好,所以目前大 信号的形式来通知CPU,然后才运行中断服务 的小的任务,那么在一个任务需要等待I/O时, 多数嵌入式实时操作系统是可剥夺型内核。 程序中来处理该事件。自CPU响应中断到CPU 就可以交出对CPU的使用权,而让CPU去运行 转向中断服务程序之间所用的时间叫做中断 其他的任务,于是就可以大大提高CPU的利用 延时。显然,中断延时要影响系统的实时性。 率,当然系统完成任务所花的时间就会大为 因此,缩短中断延时也是实时操作系统不断 减少,从而给提高系统的实时性能创造了条 需要解决的一项课题。 件。

实时操作系统的条件

C/OS –II 的体系结构

C/OS-II中的数据类型

谢谢,恳请提出 意见和建议!
北华大学 计算机科学技术学院 任 哲 电话:0432-6181933 电子邮箱:renzhe71@sina.com

处理器的分配
用一个处理器来运行多个任务的首要问题就是 如何为任务分配处理器,即决定一个任务应该 在操作系统中,处理器的分配工作是由一个 何时占用处理器,何时出让处理器。

叫做调度器的模块来执行的,调度器分配处 理器的方法叫做调度策略,不同的调度策略 可以用为每个任务分配一个时间段的方法让任 务轮流占用处理器。这个时间段也叫做时间片。 具有不同的特点,实时系统通常是按任务的 优先级来为任务分配处理器的,这种调度方 也可以为系统中的每个任务一个表明其占用处 法也叫做可剥夺调度,这种操作系统内核也 理器权限的级别(优先级),在运行时,哪个 叫做可剥夺内核。 任务的级别高则由哪个任务占用处理器,即高
级别的任务可以由级别低的任务手中抢占处理 器。



更多相关文章:
C语言讲稿第十六讲
第十六讲一、内容概要及重点 1.教学内容:文件(共 2 学时,本讲 2 学时) (1) C 文件概述 (2) 文件类型指针 (3) 文件的打开与关闭 (4) 文件的读写 2...
C讲稿(ch3)
C课堂讲稿 34页 2下载券C​讲​稿​(​c​h​3​) 暂无评价|0人阅读|0次下载|举报文档 很​好 第三章 表达式 表达式 作用 在C源程序中表示...
C语言讲稿
C语言讲稿 隐藏>> 第1 章 C 程序概述人与计算机交换信息是要用语言来交流的, 这种语言称为计算机 语言。用计算机语言编写的代码称为程序。计算机的工作是受程 ...
《氧化还原反应》讲课稿
点燃 [投影] C +O2 == CO2 CuO+H2 == Cu+H2O 点燃 S + O2 == SO2 高温 Fe2O3 + 3H2 == 2Fe+3H2O 学生活动思考得出: 氧化反应 点燃 C +O2 ...
2006年秋c讲稿[1]1
C语言讲稿(第一章) 43页 1财富值如要投诉违规内容,请到百度文库投诉中心;如要提出功能问题或意见建议,请点击此处进行反馈。 2006年秋c讲稿[1]1 简要介绍资料的...
演讲与口才》作业一至五答案
(2 分) A、 《论演讲家》 B、 《论语》 C、 《演讲学》 D、 《修辞学》 3、鲁迅 1927 年在广州作了题为()的演讲,是学术演讲的典范。 (2 分) A、...
演讲与口才--任务一
(2 分) A、 手势动作 B、 体态语言 C、 头部动作 D、 眼神交流 标记未做 2、鲁迅 1927 年在广州作了题为()的演讲,是学术演讲的典范。(2 分) A、 《...
C语言讲稿
50 李星毅 第 3 页共 54 页 8/11/2013 计算机基础教学讲稿——C 语言 1.....Os shell(暂时退出) 暂时退出 Turbo C 2.0 到 DOS 提示符下,此时可以运行 ...
C语言讲稿第十四讲
C语言讲稿C语言讲稿隐藏>> 第十四讲一、内容概要及重点 1.教学内容:指针(共 4 学时,本讲 2 学时) (1) 字符串的指针和指向字符串的指针变量 (2) 函数的...
C演讲稿
C演讲稿_演讲/主持_工作范文_应用文书。家庭美德 尊敬的各位领导、诸位同事你们好: 今天有幸第一个站在这个演讲台上,我演讲的题目是: 讲家庭美 德做有孝心的人...
更多相关标签:

All rights reserved Powered by 甜梦文库 9512.net

copyright ©right 2010-2021。
甜梦文库内容来自网络,如有侵犯请联系客服。zhit325@126.com|网站地图