nullnull嵌入式实时操作系统
μC/OS-II讲座北 华 大 学
任 哲
2006 广州null为什么要学习μC/OS-II一.凡从事嵌入式系统开发工作的人,必须
对嵌入式操作系统有足够的了解。
二.对于初学者,从μC/OS-II开始是个明智的选择。
1. μC/OS-II麻雀虽小,却五脏基本全(它是个微内核)。
2.可以学习实时系统的一些编程技巧。
3.可以把在学校中学到的操作系统抽象概念具体化。
4.具有很强的实用性。
5.学习数据结构应用的好例子。null讲座的主要
内容
财务内部控制制度的内容财务内部控制制度的内容人员招聘与配置的内容项目成本控制的内容消防安全演练内容
一.计算机操作系统的基本概念
二.操作系统中常用的数据结构
三.并发操作系统的概念
四.任务的要素
五. μC/OS-II的任务管理(任务调度)
六. μC/OS-II的中断和时钟
七. μC/OS-II的任务的同步与通信
八. μC/OS-II的存储管理
九.硬件抽象层和测试台null操作系统是一种为应用程序提供服务的系统软件,是一个完整计算机系统的有机组成部分。
从层次来看,操作系统位于计算机硬件之上,应用软件之下。所以也把它叫做应用软件的运行平台。什么是计算机操作系统
(Operating System,OS)null
它在计算机应用程序与计算机硬件系统之间,屏蔽了计算机硬件工作的一些细节,并对系统中的资源进行有效的管理。
通过提供函数(应用程序接口(API)),从而使应用程序的
设计
领导形象设计圆作业设计ao工艺污水处理厂设计附属工程施工组织设计清扫机器人结构设计
人员得以在一个友好的平台上进行应用程序的设计和开发,大大地提高了应用程序的开发效率。 计算机操作系统的作用从用户的角度来看,它就是一大堆函数(API和系统函数),用户可以调用(普通调用或系统调用)它们来对系统资源进行操作。null操作系统计算机操作系统的功能
总之,需要一大堆表
null操作系统中经常使用
的数据结构(数组)数组
1。同一数据类型数据的集合;
2。占用连续内存空间;
3。其中的所有元素名称都相同,但每个元素都有一个编号;
4。元素名去掉编号(下标),得到的是数组名,数组名是个指针。int a[10]a[0]a[1]a[2]a[3]a[9]aa+1使用上的特点:
1。分类存放;
2。检索速度快且恒定;
3。缺点:占用连续空间大……a+2a+3a+9应用:记录同类事物的表null操作系统中经常使用
的数据结构(位图)
位图是数组的一种特殊应用
a[10]
(可以记录80个事物的状态)a[0]a[1]a[2]a[3]a[9]aa+1……a+2a+3a+9应用:登记表1/0D7 D6 D5 D4 D3 D2 D1 D0null操作系统中经常使用
的数据结构(结构)
1。不同数据类型数据的集合;
2。占用连续内存空间;
struct Student{
int age;
char*name;
char sex;
};使用上的特点:
1。不分类存放,但用来描述同一事物;
2。检索速度快且恒定;
应用:通讯录中的一条记录、
工具箱、厨房等等null操作系统中经常使用
的数据结构(链表)struct Student{
Student*next
int age;
char*name;
char sex;
};
1。同数据类型数据的集合;
2。不占用连续内存空间。
使用上的特点:
1。分类存放,但空间上不连续(不需要大量的连续存储空间);
2。检索速度慢,且耗费的时间不固定;
应用:存放大量的较大
的表,类似档案柜null操作系统中经常使用
的数据结构(队列)按照先进先出
的
规则
编码规则下载淘宝规则下载天猫规则下载麻将竞赛规则pdf麻将竞赛规则pdf
组织的数据结构
可以用数组也可以用链
表来实现
主要用于对象的排队
null操作系统中经常使用
的数据结构(堆栈)
按照先进后出
规则组织的数据结构
主要用数组来实现
主要用于
程序模块的嵌套运行
null什么是多任务系统
简单地说,就是能用一个处理器
并发(注意,不是同时! )
地运行多个程序的计算机管理系统。
并发:由同一个处理器轮换地
运行多个程序。或者说是由多个
程序轮班地占用处理器这个资源。
且在占用这个资源期间,并不一
定能够把程序运行完毕。
处理器如何进行程序的
切换?null程序的切换(两句话)处理器是个傻瓜,PC让它干啥,它就干啥。PC是个指路器,它指向哪
儿,处理器就去哪儿。从此可以知道,哪个程序占有了PC,哪个程序就占有了处理器。
深刻地理解PC是理解系统
进行程序切换动作的关键。
null如何操作PC指令:
不同的计算机类型的指令是不同的。
数据传送指令
子程序返回指令(由堆
栈弹出)
中断服务程序返回指令
(由堆栈弹出)null小结系统是通过把待运行程序的地址赋予程序计数器PC来实现程序的切换的。null任务代码任务堆栈内存处理器任务运行时与
处理器之间的关系处理器通过两个指针寄存器(PC和SP)来与任务代码和任务堆栈建立联系并运行它寄存器组运行环境包括了两部分:处理器中的运行环境和内存中的运行环境null处理器多任务时的问题?当有多个任务时,处理器中的运行环境应该怎么办?寄存器组程序运行环境null多任务时任务与处理器 之间关系的处理程序在内存中为每个任务创建一个虚拟的处理器(处理器部分的运行环境由操作系统的调度器按某种规则来进行这两个复制工作寄存器组寄存器组
也就是说,任务的切换是
任务运行环境的切换
null虚拟处理器虚拟处理器应该存储的主要信息:
1。程序的断点地址(PC)
2。任务堆栈指针(SP)
3。程序状态字寄存器(PSW)
4。通用寄存器内容
5。函数调用信息(已存在于堆栈)
另外再用一个数据结构保存任务堆栈
指针(SP),这个数据结构叫做任务
控制块,它除了保存任务堆栈指针之
外还要负责保存任务其他信息。
这些内容通常保存在任务堆栈中,这些内容也常叫做任务的上下文。任务控制块是由操作系统另行构造的一个数据结构,每个任务都有一个。任务控制块结构的主要成员
typedef struct os_tcb {
OS_STK *OSTCBStkPtr; //指向任务堆栈栈顶的指针
……
INT8U OSTCBStat; //任务的当前状态标志
INT8U OSTCBPrio; //任务的优先级别
……
} OS_TCB;
其实,程序切换的关键是
把程序的私有堆栈指针赋
予处理器的堆栈指针SP
实质上系统是通过SP的切换
来实现程序的切换的。
要建立一个概念:具有
控制块的程序才是一个
可以被系统所运行的任务。
程序代码、私有堆栈、任
务控制块是任务的三要件。
任务控制块提供了运行环
境的存储位置。
null
任务的基本概念
把一个大型任务分解成多个小任务,然后在计算机中通过运行这些小任务,最终达到完成大任务的目的。
在μC/OS-II中,与上述那些小任务对应的程序实体就叫做“任务”(实质上是一个线程),μC/OS-II就是一个能对这些小任务的运行进行管理和调度的多任务操作系统。 从应用程序设计的角度来看,μC/OS-II的任务就是一个用户编写的C函数和与之相关联的一些数据结构而构成的一个实体。 null任务切换过程获得待运行任务的任务控制块如何获得待运行任务的任务控制块?null小结一个完整的任务应该有如下三部分:
任务代码(程序)
任务的私有堆栈(用以保护运行环境)
任务控制块(提供私有堆栈也是虚拟处理器的位置)
这些都是任务方应该提供的基本信息。
null
μC/OS-II中
的任务管理
null
任务的状态及其转换
正在运行的任务,需要等待一段时间或需要等待一个事件发生再运行时,该任务就会把CPU的使用权让给别的任务而使任务进入等待状态。 任务在没有被配备任务控制块或被剥夺了任务控制块时的状态叫做任务的睡眠状态 系统为任务配备了任务控制块且在任务就绪表中进行了就绪登记,这时任务的状态叫做就绪状态。 处于就绪状态的任务如果经调度器判断获得了CPU的使用权,则任务就进入运行状态 一个正在运行的任务一旦响应中断申请就会中止运行而去执行中断服务程序,这时任务的状态叫做中断服务状态 null前面谈到,一个任务的任务控制块
的主要作用就是保存该任务的虚拟
处理器的堆栈指针寄存器SP。
其实,随着任务管理工作的复杂性
的提高,它还应该保存一些其他信
息。任务控制块——任务在系统中的身份证 由于系统存在着多个任务,于是
系统如何来识别并管理一个任务就是
一个需要解决的问题。识别一个任务
的最直接的办法是为每一个任务起一
个名称。
由于μC/OS-II中的任务都有一个
惟一的优先级别,因此μC/OS-II是用
任务的优先级来作为任务的标识的。
所以,任务控制块还要来保存该
任务的优先级别。另外,前面也谈到,一个任务在
不同的时刻还处于不同的状态,
显然,记录了任务状态的数据也
应该保存到任务控制块中。基于上述原因,系统必须为每个任务创建
一个保存与该任务有关的相关信息的数据
结构,这个数据结构就叫做该任务的任务
控制块(TCB)。任务控制块结构的主要成员
typedef struct os_tcb {
OS_STK *OSTCBStkPtr; //指向任务堆栈栈顶的指针
……
INT8U OSTCBStat; //任务的当前状态标志
INT8U OSTCBPrio; //任务的优先级别
……
} OS_TCB;
任务控制块是不是像
我们人在一个国家中
的身份证?(其实,
系统中的所有资源
都应该有身份证。)
null
任务在内存中的结构
null
用户任务代码的
一般结构
void MyTask(void *pdata)
{
for (;;)
{
可以被中断的用户代码;
OS_ENTER_CRITICAL( );//进入临界段(关中断)
不可以被中断的用户代码;
OS_EXIT_CRITICAL( ); //退出临界段(开中断)
可以被中断的用户代码;
}
}
临界段无限循环于是可以这样说,μC/OS-II任务的代码结构是一个可以带有临界段的无限循环。 null
系统提供的空闲任务
在多任务系统运行时,系统经常会在某个时间内无用户任务可运行而处于所谓的空闲状态,为了使CPU在没有用户任务可执行的时候有事可做,μC/OS-II提供了一个叫做空闲任务OSTaskIdle( )的系统任务 void OSTaskIdle(void* pdata)
{
# if OS_CRITICAL_METHOD = = 3
OS_CPU_SR cpu_sr;
#endif
pdata = pdata; //防止某些编译器报错
for(;;)
{
OS_ENTER_CRITICAL( );//关闭中断
OSdleCtr++; //计数
OS_EXIT_CRITICAL( ); //开放中断
}
} 空闲任务只是做了一个计数工作注意!空闲任务中没有调用任务延时函数μC/OS-II规定,一个用户应用程序必须使用这个空闲任务,而且这个任务是不能用软件来删除的 null
系统提供的另一个任务
——统计任务
μC/OS-II提供的另一个系统任务是统计任务OSTaskStat( )。这个统计任务每秒计算一次CPU在单位时间内被使用的时间,并把计算结果以百分比的形式存放在变量OSCPUsage中,以便应用程序通过访问它来了解CPU的利用率,所以这个系统任务OSTaskStat( )叫做统计任务 null
任务的优先权
及优先级别
μC/OS_II 把任务的优先权分为64个优先级别,每一个级别都用一个数字来表示。数字0表示任务的优先级别最高,数字越大则表示任务的优先级别越低 用户可以根据应用程序的需要,在文件OS_CFG.H中通过给表示最低优先级别的常数OS_LOWEST_PRIO赋值的
方法
快递客服问题件处理详细方法山木方法pdf计算方法pdf华与华方法下载八字理论方法下载
,来说明应用程序中任务优先级别的数目。该常数一旦被定义,则意味着系统中可供使用的优先级别为:0,1,2,……,OS_LOWEST_PRIO,共OS_LOWEST_PRIO+1个
固定地,系统总是把最低优先级别OS_LOWEST_PRIO自动赋给空闲任务。如果应用程序中还使用了统计任务,系统则会把优先级别OS_LOWEST_PRIO-1自动赋给统计任务,因此用户任务可以使用的优先级别是:0,1,2…OS_LOWEST_PRIO-2,共OS_LOWEST_PRIO-1个
null
任 务 堆 栈
保存CPU寄存器中的内容及存储任务私有数据的需要,每个任务都应该配有自己的堆栈,任务堆栈是任务的重要的组成部分 在应用程序中定义任务堆栈的栈区非常简单,即定义一个OS_STK类型的一个数组并在创建一个任务时把这个数组的地址赋给该任务就可以了。
例如:
//定义堆栈的长度
#define TASK_STK_SIZE 512
//定义一个数组来作为任务堆栈
OS_STK TaskStk[TASK_STK_SIZE]; typedef unsigned int OS_STK;
//这是系统定义的一个数据类型 void main(void)
{
……
OSTaskCreate(
MyTask, //任务的指针
&MyTaskAgu, //传递给任务的参数
& MyTaskStk[MyTaskStkN-1],//任务堆栈栈顶地址
20 //任务的优先级别
);
……
}
在创建用户任务时,要传递任务的堆栈指针和任务优先级别使用函数OSTaskCreate( )创建任务时,一定要注意所使用的处理器对堆栈增长方向的支持是向上的还是向下的 null
任务堆栈的初始化
应用程序在创建一个新任务的时候,必须把在系统启动这个任务时CPU各寄存器所需要的初始数据(任务指针、任务堆栈指针、程序状态字等等),事先存放在任务的堆栈中 μC/OS-II在创建任务函数OSTaskCreate( )中通过调用任务堆栈初始化函数OSTaskStkInit( )来完成任务堆栈初始化工作的 它的原型如下:
OS_STK *OSTaskStkInit(
void (*task)(void *pd),
void *pdato, OS_STK *ptos,
INT16U opt
);
由于各种处理器的寄存器及对堆栈的操作方式不尽相同,因此该函数需要用户在进行μC/OS-II的移植时,按所使用的处理器由用户来编写。实现这个函数的具体细节,将在本书有关μC/OS-II移植的章节中做进
一步的介绍
其实,任务堆栈的初始化就是对该任务的虚拟处理器的初始化(复位)。
null任务控制块 (OS_TCB)
及任务控制块链表 μC/OS-II用来记录任务的堆栈指针、任务的当前状态、任务的优先级别等一些与任务管理有关的属性的表就叫做任务控制块 任务控制块就相当于是一个任务的身份证,没有任务控制块的任务是不能被系统承认和管理的 任务控制块结构的主要成员
typedef struct os_tcb {
OS_STK *OSTCBStkPtr; //指向任务堆栈栈顶的指针
……
struct os_tcb *OSTCBNext;//指向后一个任务控制块的指针
struct os_tcb *OSTCBPrev; //指向前一个任务控制块的指针
……
INT16U OSTCBDly; //任务等待的时限(节拍数)
INT8U OSTCBStat; //任务的当前状态标志
INT8U OSTCBPrio; //任务的优先级别
……
} OS_TCB; 任务控制块链表空任务控制块链表当应用程序调用函数OSTaskCreate( )创建一个任务时,这个函数会调用系统函数OSTCBInit ( )来为任务控制块进行初始化。这个函数首先为被创建任务从空任务控制块链表获取一个任务控制块,然后用任务的属性对任务控制块各个成员进行赋值,最后再把这个任务控制块链入到任务控制块链表的头部
当进行系统初始化时,初始化函数会按用户提供的任务数为系统创建具有相应数量的任务控制块并把它们链接为一个链表。
由于这些任务控制块还没有对应的任务,故这个链表叫做空任务块链表。即相当于是一些空白的身份证。
null 任务就绪表
及
任务调度 多任务操作系统的核心工作就是任务调度。
所谓调度,就是通过一个算法在多个任务中确定该运行的任务,做这项工作的函数就叫做调度器。
μC/OS_II进行任务调度的思想是 “近似地每时每刻总是让优先级最高的就绪任务处于运行状态” 。为了保证这一点,它在系统或用户任务调用系统函数及执行中断服务程序结束时总是调用调度器,来确定应该运行的任务并运行它 。
μC/OS_II进行任务调度的依据就是任务就绪表
为了能够使系统清楚地知道,系统中哪些任务已经就绪,哪些还没有就绪,μC/OS_II在RAM中设立了一个记录表,系统中的每个任务都在这个表中占据一个位置,并用这个位置的状态(1或者0)来表示任务是否处于就绪状态,这个表就叫做任务就绪状态表,简称叫任务就绪表 nullnullOSRdyTbl[ ]1/0 1/0 1/0 1/0 1/0 1/0 1/0 1/01/0 1/0 1/0 1/0 1/0 1/0 1/0 1/01/0 1/0 1/0 1/0 1/0 1/0 1/0 1/01/0 1/0 1/0 1/0 1/0 1/0 1/0 1/01/0 1/0 1/0 1/0 1/0 1/0 1/0 1/01/0 1/0 1/0 1/0 1/0 1/0 1/0 1/01/0 1/0 1/0 1/0 1/0 1/0 1/0 1/01/0 1/0 1/0 1/0 1/0 1/0 1/0 1/0OSRdyGrp D7 D6 D5 D4 D3 D2 D1 D0 1/0 1/0 1/0 1/0 1/0 1/0 1/0 1/0任务就绪表的示意图01234567xy01234567nullOSRdyGrp D7 D6 D5 D4 D3 D2 D1 D0 11110000prio=29D7 D6 D5 D4 D3 D2 D1 D0 1D7 D6 D5 D4 D3 D2 D1 D0 1OSRdyTbl[3 ]把prio为29的任务置为就绪状态YXOSRdyGrp | =OSMapTbl[prio>>3]; OSRdyTbl[prio>>3]
| = OSMapTbl[prio&0x07]; 在程序中,可以用类似下面的代码把优先级别为prio的任务置为就绪状态:
OSRdyGrp | =OSMapTbl[prio>>3];
OSRdyTbl[prio>>3] | = OSMapTbl[prio&0x07];
如果要使一个优先级别为prio的任务脱离就绪状态则可使用如下类似代码:
if((OSRdyTbl[prio>>3]&=~OSMapTbl[prio&0x07])==0)
OSRdyGrp&=~OSMapTbl[prio>>3];
nullOSRdyGrp D7 D6 D5 D4 D3 D2 D1 D0 11110000prio=29D7 D6 D5 D4 D3 D2 D1 D0 1D7 D6 D5 D4 D3 D2 D1 D0 1OSRdyTbl[y ]x =
OSUnMapTal[OSRdyTbl[y]]; 11000000000000y = OSUnMapTal[OSRdyGrp]; 图5-6 在就绪表中查找最高优先级别任务的过程从任务就绪表中获取优先级别最高的就绪任务可用如下类似的代码:
y = OSUnMapTal[OSRdyGrp]; //D5、D4、D3位
x = OSUnMapTal[OSRdyTbl[y]]; //D2、D1、D0位
prio = (y<<3)+x; //优先级别
或
y = OSUnMapTbl[OSRdyGrp];
prio = (INT8U)((y << 3)
+ OSUnMapTbl[OSRdyTbl[y]]);
优先级判定表OSUnMapTbl[256] (os_core.c)优先级判定表OSUnMapTbl[256] (os_core.c)INT8U const OSUnMapTbl[] = {
0, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
6, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
7, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
6, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0
};举例:
如OSRdyGrp的值为00101000B,即0X28,则查得OSUnMapTbl[OSRdyGrp]的值是3,它相应于OSRdyGrp中的第3位置1;
如OSRdyTbl[3]的值是11100100B,即0XE4,则查OSUnMapTbl[OSRdyTbl[3]]的值是2,则进入就绪态的最高任务优先级
Prio=3*8+2=26
null小结系统通过查找任务就绪表来
获取待运行任务的优先级优先级null任务切换过程获得待运行任务的任务控制块如何获得待运行任务的任务控制块?根据就绪表获得待运行任务的任务控制块指针其实,调度器在进行调度时,在这个位置还要进行一下判断:究竟是待运行任务是否为当前任务,如果是,则不切换;如果不是才切换,而且还要保存被中止任务的运行环境。null1.
任务切换宏
OS_TASK_SW( )
任务切换就是中止正在运行的任务(当前任务),转而去运行另外一个任务的操作,当然这个任务应该是就绪任务中优先级别最高的那个任务 先保护被中止任务的断点数据后恢复待运行任务的断点数据
不要企图用PUSH和POP指令来使程序计数器PC压栈和出栈,因为没有这样的指令。
只好变通一下了。
中断动作和过程调用指令可以使PC压栈;
中断返回指令可以使PC出栈。
因此任务切换OSCtxSw( )必定是一个中断服务程序。
需要由宏OS_TASK_SW( ) 来引发一次中断或者一次调用来使 OSCtxSw( )
执行任务切换工作
null调度时机
很容易想到的调度时机就
是定时调度。
对于实时系统来说,应该尽
可能地实现即时调度。
null
用函数OSTaskCreate( )
创建任务
应用程序通过调用OSTaskCreate( ) 函数来创建一个任务,OSTaskCreate( )函数的原型如下:
INT8U OSTaskCreate (
void (*task)(void *pd),//指向任务的指针
void *pdata, //传递给任务的参数
OS_STK *ptos, //指向任务堆栈栈顶的指针
INT8U prio //任务的优先级
)
null
创建任务
的一般方法
一般来说,任务可以在调用函数OSStart( )启动任务调度之前来创建,也可以在任务中来创建。但是,μC/OS-II有一个规定:在调用启动任务函数OSStart( )之前,必须已经创建了至少一个任务。因此,人们习惯上在调用函数OSStart( )之前先创建一个任务,并赋予它最高的优先级别,从而使它成为起始任务。然后在这个起始任务中,再创建其他各任务。
如果要使用系统提供的统计任务,则统计任务的初始化函数也必须在这个起始任务中来调用
void main(void)
{ ……
OSInit( ); //对μC/OS-II进行初始化
……
OSTaskCreate (TaskStart,……);//创建任务TaskStart
OSStart( ); //开始多任务调度
}
void TaskStart(void*pdata)
{
……//在这个位置安装并启动μC/OS-II的时钟
OSStatInit( ); //初始化统计任务
……//在这个位置创建其他任务
for(;;)
{
起始任务TaskStart的代码
}
} null
μC/OS-II的
初始化
在使用μC/OS-II的所有服务之前,必须要调用μC/OS-II的初始化函数OSInit( )对μC/OS-II自身的运行环境进行初始化。 函数OSInit( )将对μC/OS-II的所有的全局变量和数据结构进行初始化,同时创建空闲任务OSTaskIdle,并赋之以最低的优先级别和永远的就绪状态。如果用户应用程序还要使用统计任务的话(常数OS_TASK_STAT_EN=1),则OSInit( )还要以优先级别为OS_LOWEST_PRIO-1来创建统计任务 初始化函数OSInit( )对数据结构进行初始化时,主要要创建包括空任务控制块链表在内的5个空数据缓冲区。同时,为了可以快速地查询任务控制块链表中的各个元素,初始化函数OSInit( )还要创建一个数组OSTCBPrioTbl[OS_LOWEST_PRIO + 1],在这个数组中,按任务的优先级别的顺序把任务控制块的指针存放在了对应的元素中 null
μC/OS-II的启动
μC/OS-II进行任务的管理是从调用启动函数OSStart( )开始的,当然其前提条件是在调用该函数之前至少创建了一个用户任务 null 第3章 μC/OS-Ⅱ的中断和时钟 null本章主要内容:nullμC/OS-II系统
响应中断的过程μC/OS-II系统响应中断的过程为:系统接收到中断请求后,这时如果CPU处于中断允许状态(即中断是开放的),系统就会中止正在运行的当前任务,而按照中断向量的指向转而去运行中断服务子程序;当中断服务子程序的运行结束后,系统将会根据情况返回到被中止的任务继续运行或者转向运行另一个具有更高优先级别的就绪任务。
注意!中断服务子程序运行结束之后,系统将会根据情况进行一次任务调度去运行优先级别最高的就绪任务,而并不是一定要接续运行被中断的任务的。null中断请求关闭中断转到中断向量保存CPU寄存器通知内核退出ISRISR给任务发信号中断返回恢复CPU寄存器中断响应中断恢复中断恢复任务响应时间任务响应时间中断的响应过程无新高级任务则返回原任务通知内核进入ISR
nullvoid 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( );
}
} null在中断服务程序中调用的负责任务切换工作的函数OSIntCtxSw( )叫做中断级任务切换函数 OSIntCtxSw( )
{
OSTCBCur = OSTCBHighRdy; //任务控制块的切换
OSPrioCur=OSPrioHighRdy;
SP = OSTCBHighRdy->OSTCBStkPtr; //SP指向待运行任务堆栈
用出栈指令把R1,R2,……弹入CPU的通用寄存器;
RETI; //中断返回,使PC指向待运行任务
}
null
应用程序中的临界段
在应用程序中经常有一些代码段必须不受任何干扰地连续运行,这样的代码段叫做临界段。因此,为了使临界段在运行时不受中断所打断,在临界段代码前必须用关中断指令使CPU屏蔽中断请求,而在临界段代码后必须用开中断指令解除屏蔽使得CPU可以响应中断请求 由于各厂商生产的CPU和C编译器的关中断和开中断的方法和指令不尽相同,为增强μC/OS-II的可移植性(即在μC/OS-II的各个C函数中尽可能地不出现汇编语言代码),μC/OS-II用两个宏来实现中断的开放和关闭,而把与系统的硬件相关的关中断和开中断的指令分别封装在这两个宏中:
OS_ENTER_CRITICAL( )
OS_EXIT_CRITICAL( )
第一种方法最简单,即直接使用处理器的开中断和关中断指令来实现宏,这时需要令常数OS_CRITICAL_METHOD=1。其示意性代码为:
#define OS_ENTER_CRITICAL( )\
asm(“DI”) \\关中断
#define OS_EXIT_CRITICAL( )\
asm(“EI”) \\开中断 第二种方法稍微复杂一些,但可以使CPU中断允许标志的状态,在临界段前和临界段后不发生改变。在宏OS_ENTER_CRITICAL( )中,把CPU的允许中断标志保持到堆栈中,然后再关闭中断,这样在临界段结束时,即在调用宏OS_EXIT_CRITICAL( )时只要把堆栈中保存的CPU允许中断状态恢复就可以了。这两个宏的示意性代码如下:
#define OS_ENTER_CRITICAL( ) \
asm(“PUSH PSW”) \ /*通过保存程序状态字来保存中 断允许标志*/
asm(“DI”) //关中断
#define OS_EXIT_CRITICAL( )
asm(“POP PSW”) //恢复中断允许标志 null
μC/OS-II的系统时钟
μC/OS-II与大多数计算机系统一样,用硬件定时器产生一个周期为ms级的周期性中断来实现系统时钟,最小的时钟单位就是两次中断之间相间隔的时间,这个最小时钟单位叫做时钟节拍(Time Tick)。
硬件定时器以时钟节拍为周期定时地产生中断,该中断的中断服务程序叫做OSTickISR( )。中断服务程序通过调用函数OSTimeTick( )来完成系统在每个时钟节拍时需要做的工作。
void OSTickISR(void)
{
保存CPU寄存器;
调用OSIntEnter( ); //记录中断嵌套层数
if (OSIntNesting = = 1;
{
OSTCBCur->OSTCBStkPtr = SP; //保存堆栈指针
}
调用OSTimeTick( ); //节拍处理
清除中断;
开中断;
调用OSIntExit( ); //中断嵌套层数减一
恢复CPU寄存器;
中断返回;
}
这是系统时钟中断服务程序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( );
}
} 时钟节拍服务函数函数OSTimeTick( )的任务,就是在每个时钟节拍了解每个任务的延时状态,使其中已经到了延时时限的非挂起任务进入就绪状态。 null
任务的延时
由于嵌入式系统的任务是一个无限循环,并且μC/OS-II还是一个抢占式内核,所以为了使高优先级别的任务不至于独占CPU,可以给其他任务优先级别较低的任务获得CPU使用权的机会,μC/OS-II规定:除了空闲任务之外的所有任务必须在任务中合适的位置调用系统提供的函数OSTimeDly( ),使当前任务的运行延时(暂停)一段时间并进行一次任务调度,以让出CPU的使用权。 void OSTimeDly (INT16U ticks)
{
#if OS_CRITICAL_METHOD = = 3
OS_CPU_SR cpu_sr;
#endif
if (ticks > 0)
{
OS_ENTER_CRITICAL( );
if ((OSRdyTbl[OSTCBCur->OSTCBY]
&= ~OSTCBCur->OSTCBBitX) = = 0)
{
OSRdyGrp
&= ~OSTCBCur->OSTCBBitY; //取消当前任务的就绪状态
}
OSTCBCur->OSTCBDly = ticks;//延时节拍数存入任务控制块
OS_EXIT_CRITICAL( );
OS_Sched( ); //调用调度函数
}
} 这是系统提供的延时函数null
其他用来管理时间的函数
INT8U OSTimeDlyResume( INT8U prio);
INT32U OSTimeGet( void );
void OSTimeSet( INT32U ticks );
取消任务延时函数获得系统时间函数设置系统时间函数null第4章
任务的同步与通信
系统中的多个任务在运行时,经常需要互相无冲突地访问同一个共享资源,或者需要互相支持和依赖,甚至有时还要互相加以必要的限制和制约,才保证任务的顺利运行。因此,操作系统必须具有对任务的运行进行协调的能力,从而使任务之间可以无冲突、流畅地同步运行,而不致导致灾难性的后果。
与人们依靠通信来互相沟通,从而使人际关系和谐、工作顺利的做法一样,计算机系统是依靠任务之间的良好通信来保证任务与任务的同步的。 例如,两个任务:任务A和任务B,它们需要通过访问同一个数据缓冲区合作完成一项工作,任务A负责向缓冲区写入数据,任务B负责从缓冲区读取该数据。显然,当任务A还未向缓冲区写入数据时(缓冲区为空时),任务B因不能从缓冲区得到有效数据而应该处于等待状态,只有等任务A向缓冲区写入了数据之后,才应该通知任务B去取数据。
例如,任务A和任务B共享一台打印机,如果系统已经把打印机分配给了任务A,则任务B因不能获得打印机的使用权而应该处于等待状态,只有当任务A把打印机释放后,系统才能唤醒任务B使其获得打印机的使用权。如果这两个任务不这样做,那么也会造成极大的混乱 。
总之,多个任务共享同一资源或有工作顺序要求时,在正式工作之前要互相打招呼 。
黄宏:别走啊!
宋丹丹:我自己的腿,我爱走就走,你管不着!
黄宏:腿是你自己的,但手是咱俩的呀!
null
事件
任务间的同步依赖于任务间的通信。在μC/OS-II中,是使用信号量、邮箱(消息邮箱)和消息队列这些被称作事件的中间环节来实现任务之间的通信的。 一个简单的信号量一个简单的信号量1/0收信方发信方共享资源null
事件控制块
为了把描述事件的数据结构统一起来,μC/OS-II使用叫做事件控制块ECB的数据结构来描述诸如信号量、邮箱(消息邮箱)和消息队列这些事件。事件控制块中包含包括等待任务表在内的所有有关事件的数据 typedef struct
{
INT8U OSEventType; //事件的类型
INT16U OSEventCnt; //信号量计数器
void *OSEventPtr; //消息或消息队列的指针
INT8U OSEventGrp; //等待事件的任务组
INT8U OSEventTbl[OS_EVENT_TBL_SIZE];//任务等待表
} OS_EVENT;
把一个任务置于等待状态要调用OS_EventTaskWait( )函数。该函数的原型为:
void OS_EventTaskWait (
OS_EVENT *pevent //事件控制块的指针
);
函数OS_EventTaskWait ( ),将在任务调用函数OS×××Pend( ) 请求一个事件时,被OS×××Pend( )所调用。 如果一个正在等待的任务具备了可以运行的条件,那么就要使它进入就绪状态。这时要调用OS_EventTaskRdy( )函数。该函数的作用就是把调用这个函数的任务在任务等待表中的位置清0(解除等待状态)后,再把任务在任务就绪表中对应的位置1,然后引发一次任务调度。
OS_EventTaskRdy( )函数的原型为:
INT8U OS_EventTaskRdy (
OS_EVENT *pevent, //事件控制块的指针
void *msg, //未使用
INT8U msk //清除TCB状态标志掩码
);
函数OS_EventTaskRdy ( )将在任务调用函数OS×××Post ( ) 发送一个事件时,被函数OS×××Post ( )所调用。 如果一个正在等待事件的任务已经超过了等待的时间,却仍因为没有获取事件等原因而未具备可以运行的条件,却又要使它进入就绪状态,这时要调用OS_EventTO( )函数。
OS_EventTO( )函数的原型为:
void OS_EventTO (
OS_EVENT *pevent //事件控制块的指针
);
函数OS_EventTO ( )将在任务调用OS×××Pend( ) 请求一个事件时,被函数OS×××Pend( )所调用。 null
空事件控制块链表
在μC/OS-II初始化时,系统会在初始化函数OSInit( )中按应用程序使用事件的总数OS_MAX_EVENTS(在文件OS_CFG.H中定义),创建OS_MAX_EVENTS个空事件控制块并借用成员OSEventPtr作为链接指针,把这些空事件控制块链接成一个单向链表。由于链表中的所有控制块尚未与具体事件相关联,故该链表叫做空事件控制块链表。以后,每当应用程序创建一个事件时,系统就会从链表中取出一个空事件控制块,并对它进行初始化以描述该事件。而当应用程序删除一个事件时,就会将该事件的控制块归还给空事件控制块链表 null
信号量及其操作
在使用信号量之前,应用程序必须调用函数OSSemCreate( )来创建一个信号量,OSSemCreate( )的原型为:
OS_EVENT *OSSemCreate (
INT16U cnt //信号量计数器初值
);
函数的返回值为已创建的信号量的指针。 任务通过调用函数OSSemPend( )请求信号量,函数OSSemPend( )的原型如下:
void OSSemPend ( OS_EVENT *pevent, //信号量的指针
INT16U timeout, //等待时限
INT8U *err); //错误信息
参数pevent是被请求信号量的指针。
为防止任务因得不到信号量而处于长期的等待状态,函数OSSemPend允许用参数timeout设置一个等待时间的限制,当任务等待的时间超过timeout时可以结束等待状态而进入就绪状态。如果参数timeout被设置为0,则表明任务的等待时间为无限长。
任务获得信号量,并在访问共享资源结束以后,必须要释放信号量,释放信号量也叫做发送信号量,发送信号量需调用函数OSSemPost ( )。OSSemPost ( )函数在对信号量的计数器操作之前,首先要检查是否还有等待该信号量的任务。如果没有,就把信号量计数器OSEventCnt加一;如果有,则调用调度器OS_Sched( )去运行等待任务中优先级别最高的任务。
函数OSSemPost ( )的原型为:
INT8U OSSemPost (
OS_EVENT *pevent //信号量的指针
);
调用函数成功后,函数返回值为OS_ON_ERR,否则会根据具体错误返回OS_ERR_EVENT_TYPE、OS_SEM_OVF。 应用程序如果不需要某个信号量了,那么可以调用函数OSSemDel( )来删除该信号量,这个函数的原型为:
OS_EVENT *OSSemDel (
OS_EVENT *pevent, //信号量的指针
INT8U opt, //删除条件选项
INT8U *err //错误信息
); null互斥型信号量和任务优先级反转 在可剥夺型内核中,当任务以独占方式使用共享资源时,会出现低优先级任务先于高优先级任务而被运行的现象,这种现象叫做任务优先级反转。在一般情况下是不允许出现这种任务优先级反转现象的,下面就对优先级的反转现象做一个详细的
分析
定性数据统计分析pdf销售业绩分析模板建筑结构震害分析销售进度分析表京东商城竞争战略分析
,以期找出原因及解决方法。 图4-15描述了A、B、C三个任务的运行情况。其中,任务A的优先级别高于任务B,任务B的优先级别高于任务C。任务A和任务C都要使用同一个共享资源S,而用于保护该资源的信号量在同一时间只能允许一个任务以独占的方式对该资源进行访问,即这个信号量是一个互斥型信号量。 通过例子可以发现,使用信号量的任务是否能够运行是受任务的优先级别和是否占用信号量两个条件约束的,而信号量的约束高于优先级别的约束。于是当出现低优先级别的任务与高优先级别的任务使用同一个信号量,而系统中还存有别的中等优先级别的任务时,如果低优先级别的任务先获得了信号量,就会使高级别的任务处于等待状态,而那些不使用该信号量的中等级别的任务却可以剥夺低优先级别的任务的CPU使用权而先于高优先级别的任务而运行了。 解决问题的办法之一,是使获得信号量任务的优先级别在使用共享资源期间暂时提升到所有任务最高优先级的高一个级别上,以使该任务不被其他的任务所打断,从而能尽快地使用完共享资源并释放信号量,然后在释放了信号量之后