BSP是板级支持包,是介于主板硬件和操作系统之间的一层,应该说是属于操作系统的一部分,主要
目的是为了支持操作系统,使之能够更好的运行于硬件主板。BSP是相对于操作系统而言的,不同
的操作系统对应于不同定义形式的BSP,例如VxWorks的BSP和Linux的BSP相对于某一CPU来说尽管实
现的功能一样,可是写法和接口定义是完全不同的,所以写BSP一定要按照该系统BSP的定义形式来
写(BSP的编程过程大多数是在某一个成型的BSP模板上进行修改)。这样才能与上层OS保持正确的
接口,良好的支持上层OS。
纯粹的BSP所包含的内容一般说来是和系统有关的驱动和程序,如网络驱动和系统中网络协议有关
,串口驱动和系统下载调试有关等等。离开这些驱动系统就不能正常工作。
uC/OS的性能特点:
1、公开源代码
2、可移植性
3、可固化
4、可裁剪
5、多任务
6、占先式
7、可确定性
8、任务栈
9、系统服务
10、中断管理
11、稳定性与可靠性
uC/OS提供的系统服务:
信号量
带互斥机制的信号量
优化优先级倒置的问题
事件标志
消息信箱
消息队列
内存管理
时钟管理
任务管理
uC/OSII 内核结构:
软件:
应用程序(用户代码)
μC/OS-II(与处理器无关代码) μC/OS-II配置(与应用无关)
μC/OS-II移植(与处理器相关代码):移植时需要编写的代码
硬件:
CPU 定时器(用于产生系统时钟)
uC/OSII的任务:
任务控制块包含:前一个任务控制块的指针、后一个任务控制块的指针、指向任务的指针、指向任
务堆栈的指针、任务的优先级别
从应用程序设计的角度来看,任务就是一个线程。就是一个用来解决用户问题的C语言函数和与之
相关联的一些数据结构而构成的一个实体
从任务存储结构来看,由三部分构成:任务程序代码、任务堆栈和任务控制块。任务控制块用来保
存任务属性,任务堆栈用来保存任务工作环境,任务程序代码是任务的执行部分
uC/OS-II的任务有两种:用户任务和系统任务。由应用程序设计者编写的任务叫做用户任务,由系
统提供的任务叫做系统任务。用户任务是为解决应用问题而编写的,系统任务是为应用程序来提供
某种服务的。
任务的状态:
正在运行的任务是可以被中断的,除非该任务将中断关了,或者μC/OS-Ⅱ将中断关了。被中断了
的任务就进入了中断服务态(ISR)。响应中断时,正在执行的任务被挂起,中断服务子程序控制
了CPU的使用权。中断服务子程序可能会报告一个或多个事件的发生,而使一个或多个任务进入就
绪态。在这种情况下,从中断服务子程序返回之前,μC/OS-Ⅱ要判定,被中断的任务是否还是优
先级最高的。如果中断服务子程序使一个优先级更高的任务进入了就绪态或唤醒了一个优先级更高
的挂起任务,则这个优先级更高的任务将得以运行,否则原来被中断了的任务才能继续运行。
正在运行的任务,需要等待一段时间或需要等待一个事件发生再运行时,该任务就会把CPU的使用
权让给其他任务而使任务进入等态状态。
处于就绪态的任务如果经调度器判断获得了CPU的使用权,则任务就进入运行态。任何时刻只能有
一个任务处于运行态,就绪的任务只有当所有优先级高于本任务的任务都转为等待状态时,才能进
入运行态。
用户任务代码的一般结构:
根据嵌入式系统任务的工作特点,任务的执行代码通常是一个无限循环结构,并且在这个循环中可
以响应中断,这种结构也叫超循环结构。
为了有效的对中断进行控制,在任务的代码里可使用UC/OS-II定义的宏OS_ENTER_CRITICAL()和
OS_EXIT_CRITICAL()来控制何时响应中断,何时屏蔽中断。在运行这两个宏之间的代码时是不会响
应中断的,这种受保护的代码段叫临界段。
从程序设计角度来看,一个UC/OS-II任务的代码就是一个C语言函数。为了可以传递各种不同类型
的数据甚至是函数,任务的参数是一个VOID类型的指针。
为了有效的对中断进行控制,在任务的代码里可使用UC/OS-II定义的宏OS_ENTER_CRITICAL()和
OS_EXIT_CRITICAL()来控制何时响应中断,何时屏蔽中断。在运行这两个宏之间的代码时是不会响
应中断的,这种受保护的代码段叫临界段。在具体应用中可以,根据实际需要在一个任务中使用这
对宏设置多个临阶段。
因此可以说,UC/OS-II任务的代码是一个带有临阶段的无限循环。
从代码来看,用户任务似乎就是一个C语言函数,但是这个函数不是一般的C语言函数,他是一个任
务(线程)。因此它不是被主函数或其它函数调用的,主函数main()只负责创建和启动它们,而由
操作系统负责来调度运行它们。
使用函数OSStart()启动任务之后,任务就交由操作系统管理和调度了。
系统任务之空闲任务:
C/OS-II总是要建立一个空闲任务(idle task),这个任务在没有其它任务进入就绪状态时投入运
行。空闲任务永远为最低优先级。
uC/OS-II预定义了两个为应用程序服务的系统任务:空闲任务和统计任务。其中空闲任务是每个应
用程序必须使用的,而统计任务则是应用程序可以根据实际需要来选择使用的。
空闲任务只是做了一个计数工作
注意!空闲任务中没有调用任务延时函数
系统任务之统计任务:
OSTaskStat()每秒运行1次,计算CPU利用率,即告诉用户应用程序使用了多少CPU时间,用百分比
表示,精确度为1%。
如果将系统OS_TASK_STAT_EN设置为1,统计任务就会在操作系统初始化过程建立。
如果应用程序打算使用统计任务,在调用系统多任务启动函数OSStart()之前,用户初始代码中必
须先建立一个优先级最高也是在启动前唯一的任务,在这个任务中调用系统统计初始化函数
OSStatInit(),然后再建立应用程序中的其他任务。
也应该在这个优先级最高的任务中启动时钟节拍。
任务的优先级:
uC/OS-II分为64个优先级别,每一个级别都用一个数字表示。
数字0的级别最高,数字越大优先级别越低。
通常,一个应用程序的任务数小于64,用户可根据应用程序的需要,在OS_CFG.H中设置
OS_LOWEST_PRIO,即定义了可供使用的优先级别共OS_LOWEST_PRIO +1个。
固定地,系统总是把最低优先级别自动赋给空闲任务,如果系统中还有统计任务,则其优先级别为
OS_LOWEST_PRIO -1。
给某一个用户任务定义优先级别,需要在调用系统函数OSTaskCreate()来创建任务时,用该函数的
第4个参数prio来指定。
由于每个任务都具有唯一的优先级别,因此uC/OS-II通常也用任务的优先级别来作为这个任务的标
示。
任务堆栈:
所谓堆栈,就是在存储器中按数据“后进先出(LIFO)”的原则组织的连续存储空间。为了满足任务
切换和响应中断时保存CPU寄存器中的内容及存储任务私有数据的需要,每个任务都应该配有自己
的堆栈。任务堆栈是任务的重要组成部分。
任务堆栈的创建:
为方便定义任务堆栈,在文件OS_CPU.H中专门定义了一个数据类型OS_STK:
Typedef unsigned int OS_STK; // 该类型长度为16位
这样,在应用程序中定义任务堆栈的栈区就非常简单,即定义一个OS_STK类型的一个数组即可。例
如:
#define TASK_STK_SIZE 512 // 定义堆栈长度(1024字节)
OS_STK TaskStk[TASK_STK_SIZE]; //定义一个数组来作为任务堆栈
创建任务函数OSTaskCreate()原型如下:
INT8U OSTaskCreate(
void (*task)(void *pd), //指向任务的指针
void *pdata, //传递给任务的参数
OS_STK *ptos, //任务堆栈栈顶的指针
INT8U prio ); //指定任务优先级别的参数
创建一个任务MyTask,堆栈的长度为128字节,优先级别为20,任务参数pdata的实参为ttt。试写
出需要的代码。
#define MyTaskStkN 64
OS_STK MyTaskStk[MyTaskStkN];
void main(void)
{
……
OSTaskCreate(
MyTask,
&ttt,
&MyTaskStk[0],
20);
……
}
任务堆栈的初始化:
当CPU启动运行一个任务时,CPU的各寄存器总是需要预置一些初始数据,例如指向任务的指针、任
务堆栈指针、程序状态字等。那么,系统启动任务时,CPU从何处可以获得这些数据呢?
最方便的方法就是让CPU从这个任务的任务堆栈里获得这些数据!
其实,任务堆栈的初始化就是对该任务的虚拟处理器的初始化(复位)。
由于各处理器的寄存器及对堆栈的操作方式不同,因此该函数需要用户在进行uC/OS-II的移植时,
按所使用的处理器由用户来编写。
任务控制块(TCB)及任务控制块链表
一旦任务建立,一个任务控制块OS_TCB就被赋值。
任务控制块是一个数据结构,保存该任务的相关参数,包括任务堆栈指针,任务的当前状态,任务
的优先级等。
任务CPU使用权被剥夺时,TCB保存该时刻任务状态;任务重新得到CPU控制权时,TCB确保任务从当
时被中断的那一点丝毫不差地继续执行。
OS_TCB全部驻留在RAM中。
任务控制块就相当于是一个任务的身份证,没有任务控制块的任务是不能被系统承认和管理的。
任务控制块链表:
uC/OS-II用两条链表来管理任务控制块:
空任务块链表(所有任务控制块还没有分配给任务),是在应用程序调用函数OSInit()对系统进行
初始化时建立的。
任务块链表(所有任务控制块已经分配给任务),是在调用函数OSTaskCreate()创建任务时建立的
。建立任务控制块链表的具体做法是,从空链表摘取一个空任务控制块,然后填充上任务属性后再
形成新的链表。
系统在调用函数OSInit()对uC/OS-II系统进行初始化时,先在RAM中建立一个OS_TCB结构类型的数
组OSTCBTbl[ ],每个数组元素就是一个任务控制块,然后把这些控制块链接成一个如图所示的链表
。由于链表中的这些控制块还没有与具体任务相关联,因此这个链表叫做空任务块链表。
从图中可以看到,uC/OS-II初始化时建立的空任务链表元素一共是OS_MAX_TASKS+OS_N_SYS_TASKS
个。其中定义在文件OS_CFG.H中的常数OS_MAX_TASKS 指明了用户任务的最大数目;而定义在文件
UCOS_II.H中的常数OS_N_SYSTASKS指明了系统任务的数目(在图中,其值为2:一个空闲任务,一
个统计任务)。
每当应用程序调用系统函数OSTaskCreate()或OSTaskCreateExt()创建一个任务时,系统就会将任
务控制块链表头指针OSTCBFreeList指向的任务控制块分配给该任务。在给任务控制块中的各成员
赋值后,就按任务控制块链表的头指针OSTCBList 将其加入到任务控制块链表中。
任务控制块的初始化:
当用户程序调用函数OSTaskCreate()创建一个任务时,这个函数会调用系统函数OSTCBInit()来为
任务控制块进行初始化。这个函数首先为被创建任务从空任务控制块链表获取一个任务控制块,然
后利用任务的属性对任务控制块各个成员进行赋值,最后再把这个任务控制块链入到任务控制块链
表的头部。
就绪任务表及任务调度:
多任务操作系统的核心:任务调度。
调度定义:就是通过一个算法在多个任务中确定那个任务来运行。做这项工作的就是调度器。
任务调度的思想:总是让优先级最高的就绪任务处于运行状态。
uC/OS-I进行任务调度的依据:任务就续表。
为了能使系统清楚地知道系统中哪些任务已经就绪,那些还没有就绪,UC/OS-II在RAM中设立了一
个记录表,系统中的每个任务都在这个表占据一个位置,并用这个位置的状态(1或者0)来表示任
务是否处于就绪状态。这个表就叫做任务就绪表。
任务就绪表的结构:
每个就绪的任务都放入就绪表中,就绪表有两个变量:OSRdyGrp、OSRdyTbl[]
数据类型为INT8U的变量OSRdyGrp,该变量的每一个位都对应OSRdyTbl[]的一个任务组(即数组的
一个元素)。
由于每个任务的就绪状态只占一位,因此OSRdyTbl[]数组的一个元素可表达8个任务的就绪状态。
即每一个数组元素描述了8个任务的就绪状态,于是这8个任务就可以看成一个任务组。
如何根据任务的优先级别查找任务在就绪表的位置呢
例:已知某一个已经就绪的任务优先级别为prio=30,试判断应该在就绪表的哪一位置上置1。
分析:由于优先级别是一个单字节的数字,而且最大值不会超过63,即二进制00111111,因此,可
以把优先级别看成是一个6位的二进制数,这样就可以用高3位(D5、D4、D3)来指明变量OSRdyGrp的
具体数据位,并用来确定就绪表数组元素的下标;用低3位(D2、D1、D0)来指明该数组元素的具体
数据位。
答:30的二进制形式为00011110,其低6位为011110,于是可知应该在OSRdyTbl[3]的D6位上置1,
同时要把变量OSRdyGrp的D3位置1。
任务的调度:
任务切换:在多任务系统中,令CPU中止当前正在运行的任务而去运行另一个任务的工作。
任务调度:按某种规则进行任务切换的工作。
任务调度由任务调度器完成,调度器主要工作:
1. 在任务就绪表中查找具有最高优先级别的就绪任务。
2. 实现任务切换。
uC/OS-II有两种调度器:
任务级调度器(由OSSched()实现)、中断级调度器(由OSIntExt()实现)。
任务切换的两个步骤:
1. 获得待运行任务的TCB指针。
2. 进行断点数据的切换。
任务切换过程
根据就绪表获得待运行任务的任务控制块指针-》处理器的SP=任务块中保存的SP-》恢复待运行任
务的运行环境-》处理器的PC=任务堆栈中的断点地址
其实,调度器在进行调度时,在这个位置还要进行一下判断:究竟是待运行任务是否为当前任务,
如果是,则不切换;如果不是才切换,而且还要保存被中止任务的运行环境。
任务的创建:
uC/OS-II是通过任务控制块来管理任务的。
uC/OS-II有两个用来创建任务的函数:TaskCreate()、OSTaskCreateExt()
OSTaskCreate()向下兼容,OSTaskCreateExt()是前一函数的扩展,提供 了一些附加功能。
任务可以在多任务调度开始前建立,也可以在其他任务执行过程中建立。
任务不能由中断服务程序建立。
用函数OSTaskCreateExt()创建任务:
应用程序还可以通过调用函数OSTaskCreateExt()来创建一个任务,用此函数创建任务将更加灵活
,但也会增加开销,函数OSTaskCreateExt()源代码如下:
INT8U OSTaskCreateExt(
void (*task)(void *pd), // 指向任务的指针
void *pdata, // 传递给任务的参数
OS_STK *ptos, // 指向任务堆栈栈顶的指针
INT8U prio, // 任务的优先级
INT16U id, // 任务的标示
OS_STK *pbos, // 任务堆栈栈底的指针
INT32U stk_size, // 任务堆栈的容量
void *pext, // 指向附加数据域的指针
INT16U opt // 用于设置操作选项
);
创建任务的一般方法
可在调用函数OSStart()启动任务调度之前来创建。
在任务中来创建。
uC/OS-II的规定:在OSStart()启动任务调度之前必须创建至少一个任务。
这样,在OSStart()之前先创建一个任务,并赋予它最高的优先级,从而使它 成为起始任
务,然后在这个起始任务中,在创建其它各任务。
如果要使用系统提供的统计任务,则统计任务的初始化函数必须在这个起始任务中来调用。
uC/OS-II不允许在中断服务程序中创建任务
任务的挂起和恢复:
所谓挂起一个任务,就是停止这个任务的运行。
在uC/OS-II中,用户任务可通过调用系统提供的函数OSTaskSuspend()来挂起自身或者除空闲任务
之外的其他任务。用函数OSTaskSuspend()挂起的任务,只能在其它任务中通过调用恢复函数
OSTaskResume()使其恢复为就绪状态。
改变任务优先级:
在用户建立任务的时候会分配给任务一个优先级。在程序运行期间,用户可以通过调用
OSTaskChangePrio()来改变任务的优先级。换句话说,就是µC/OS-Ⅱ允许用户动态的改变任务的优
先级。
函数OSTaskChangePrio()的原型如下:
INT8U OSTaskChangePrio(
INT8U oldprio, // 任务现在的优先级
INT8U newprio // 要修改的优先级
);
任务的删除:
删除任务,就是把该任务处于休眠状态。
并不是说任务的代码真的被删除了,只是任务的代码不再被操作系统调用。
通过调用OSTaskDel()就可以完成删除任务自身或除了空闲任务之外的其他任务。
删除任务具体做法是,把被删除任务的任务控制块从任务控制块链表中删除,并归还给空任务控制
块链表,然后在任务就绪表中把该任务的就绪状态位置0,于是该任务就不能再被调度器调用了。
函数OSTaskDel()的原型如下
#if OS_TASK_DEL_EN
INT8U OSTaskDel (
INT8U prio // 要删除任务的优先级
);
如果任务删除自己,则应在调用函数时令函数的参数prio为OS_PRIO_SELF。
查询任务的信息
有时,在应用程序运行中需要了解一个任务的指针、堆栈等信息,这时可通过调用函数
OSTaskQuery()来获取选定的任务的信息。
若调用函数OSTaskQuery()查询成功,则返回OS_NO_ERR,并把查询得到的任务信息存放在结构
OS_TCB类型的变量中。
函数OSTaskQuery()的原型如下:
INT8U OSTaskQuery(
INT8U prio, // 待查询任务的优先级
OS_TCB *pdata // 存储任务信息的结构
);
uC/OS-II的初始化
系统首先调用初始化函数OSIint()。OSIint()初始化μC/OS-Ⅱ所有的变量和数据结构。
OSInit()建立空闲任务idle task,这个任务总是处于就绪态的。空闲任务OSTaskIdle()的优先级
总是设成最低,即OS_LOWEST_PRIO。
如果统计任务允许OS_TASK_STAT_EN和任务建立扩展允许都设为1,则OSInit()还得建立统计任务
OSTaskStat()并且让其进入就绪态。OSTaskStat的优先级总是设为OS_LOWEST_PRIO-1。
初始化时主要是创建包括空任务控制块链表在内的5个空数据缓冲区,及按任务优先级存放任务控
制块指针的数组OSTCBPrioTbl[OS_LOWEST_PRIO+1]。
1. 任务由任务控制块、任务堆栈和任务代码三部分组成。系统通过任务控制块来感知和控制任务
;任务堆栈主要用来保护断点和恢复断点;任务代码是一个超循环结构,它描述了任务的执行过程
。在创建一个任务时,函数OSTaskCreate()或OSTaskCreateExt()负责给任务分配任务控制块和任
务堆栈,并对它们进行初始化,然后把任务控制块任务堆栈、任务代码三者关联起来形成一个完整任
务。
2. 系统是按任务就绪表和任务的优先级别来调度任务的。执行任务调度工作的是调度器,它负责
查找具有最高优先级别的就绪任务并运行它,同时把这个任务TCB的指针存放在OSTABCur中。通常
,系统在调用API函数和运行中断服务程序之后都要调用函数OSSched()来进行一次任务调度。
3. 任务切换的核心工作是任务堆栈指针的切换。
4. 任务调度器代码的设计,使得它的运行时间与系统中的任务数无关,从而使它满足了实时系统
的要求。
5. 任务的优先级别也是任务的标识。
6. 应用程序首先应该调用函数OSInit()对全局变量和数据结构进行初始化已建立uC/OS-II的运行
环境。
7. 应用程序是通过调用函数OSStart()开始进入多任务管理的,但在调用函数OSStart()之前,必
须至少创建了一个任务。
目的是为了支持操作系统,使之能够更好的运行于硬件主板。BSP是相对于操作系统而言的,不同
的操作系统对应于不同定义形式的BSP,例如VxWorks的BSP和Linux的BSP相对于某一CPU来说尽管实
现的功能一样,可是写法和接口定义是完全不同的,所以写BSP一定要按照该系统BSP的定义形式来
写(BSP的编程过程大多数是在某一个成型的BSP模板上进行修改)。这样才能与上层OS保持正确的
接口,良好的支持上层OS。
纯粹的BSP所包含的内容一般说来是和系统有关的驱动和程序,如网络驱动和系统中网络协议有关
,串口驱动和系统下载调试有关等等。离开这些驱动系统就不能正常工作。
uC/OS的性能特点:
1、公开源代码
2、可移植性
3、可固化
4、可裁剪
5、多任务
6、占先式
7、可确定性
8、任务栈
9、系统服务
10、中断管理
11、稳定性与可靠性
uC/OS提供的系统服务:
信号量
带互斥机制的信号量
优化优先级倒置的问题
事件标志
消息信箱
消息队列
内存管理
时钟管理
任务管理
uC/OSII 内核结构:
软件:
应用程序(用户代码)
μC/OS-II(与处理器无关代码) μC/OS-II配置(与应用无关)
μC/OS-II移植(与处理器相关代码):移植时需要编写的代码
硬件:
CPU 定时器(用于产生系统时钟)
uC/OSII的任务:
任务控制块包含:前一个任务控制块的指针、后一个任务控制块的指针、指向任务的指针、指向任
务堆栈的指针、任务的优先级别
从应用程序设计的角度来看,任务就是一个线程。就是一个用来解决用户问题的C语言函数和与之
相关联的一些数据结构而构成的一个实体
从任务存储结构来看,由三部分构成:任务程序代码、任务堆栈和任务控制块。任务控制块用来保
存任务属性,任务堆栈用来保存任务工作环境,任务程序代码是任务的执行部分
uC/OS-II的任务有两种:用户任务和系统任务。由应用程序设计者编写的任务叫做用户任务,由系
统提供的任务叫做系统任务。用户任务是为解决应用问题而编写的,系统任务是为应用程序来提供
某种服务的。
任务的状态:
正在运行的任务是可以被中断的,除非该任务将中断关了,或者μC/OS-Ⅱ将中断关了。被中断了
的任务就进入了中断服务态(ISR)。响应中断时,正在执行的任务被挂起,中断服务子程序控制
了CPU的使用权。中断服务子程序可能会报告一个或多个事件的发生,而使一个或多个任务进入就
绪态。在这种情况下,从中断服务子程序返回之前,μC/OS-Ⅱ要判定,被中断的任务是否还是优
先级最高的。如果中断服务子程序使一个优先级更高的任务进入了就绪态或唤醒了一个优先级更高
的挂起任务,则这个优先级更高的任务将得以运行,否则原来被中断了的任务才能继续运行。
正在运行的任务,需要等待一段时间或需要等待一个事件发生再运行时,该任务就会把CPU的使用
权让给其他任务而使任务进入等态状态。
处于就绪态的任务如果经调度器判断获得了CPU的使用权,则任务就进入运行态。任何时刻只能有
一个任务处于运行态,就绪的任务只有当所有优先级高于本任务的任务都转为等待状态时,才能进
入运行态。
用户任务代码的一般结构:
根据嵌入式系统任务的工作特点,任务的执行代码通常是一个无限循环结构,并且在这个循环中可
以响应中断,这种结构也叫超循环结构。
为了有效的对中断进行控制,在任务的代码里可使用UC/OS-II定义的宏OS_ENTER_CRITICAL()和
OS_EXIT_CRITICAL()来控制何时响应中断,何时屏蔽中断。在运行这两个宏之间的代码时是不会响
应中断的,这种受保护的代码段叫临界段。
从程序设计角度来看,一个UC/OS-II任务的代码就是一个C语言函数。为了可以传递各种不同类型
的数据甚至是函数,任务的参数是一个VOID类型的指针。
为了有效的对中断进行控制,在任务的代码里可使用UC/OS-II定义的宏OS_ENTER_CRITICAL()和
OS_EXIT_CRITICAL()来控制何时响应中断,何时屏蔽中断。在运行这两个宏之间的代码时是不会响
应中断的,这种受保护的代码段叫临界段。在具体应用中可以,根据实际需要在一个任务中使用这
对宏设置多个临阶段。
因此可以说,UC/OS-II任务的代码是一个带有临阶段的无限循环。
从代码来看,用户任务似乎就是一个C语言函数,但是这个函数不是一般的C语言函数,他是一个任
务(线程)。因此它不是被主函数或其它函数调用的,主函数main()只负责创建和启动它们,而由
操作系统负责来调度运行它们。
使用函数OSStart()启动任务之后,任务就交由操作系统管理和调度了。
系统任务之空闲任务:
C/OS-II总是要建立一个空闲任务(idle task),这个任务在没有其它任务进入就绪状态时投入运
行。空闲任务永远为最低优先级。
uC/OS-II预定义了两个为应用程序服务的系统任务:空闲任务和统计任务。其中空闲任务是每个应
用程序必须使用的,而统计任务则是应用程序可以根据实际需要来选择使用的。
空闲任务只是做了一个计数工作
注意!空闲任务中没有调用任务延时函数
系统任务之统计任务:
OSTaskStat()每秒运行1次,计算CPU利用率,即告诉用户应用程序使用了多少CPU时间,用百分比
表示,精确度为1%。
如果将系统OS_TASK_STAT_EN设置为1,统计任务就会在操作系统初始化过程建立。
如果应用程序打算使用统计任务,在调用系统多任务启动函数OSStart()之前,用户初始代码中必
须先建立一个优先级最高也是在启动前唯一的任务,在这个任务中调用系统统计初始化函数
OSStatInit(),然后再建立应用程序中的其他任务。
也应该在这个优先级最高的任务中启动时钟节拍。
任务的优先级:
uC/OS-II分为64个优先级别,每一个级别都用一个数字表示。
数字0的级别最高,数字越大优先级别越低。
通常,一个应用程序的任务数小于64,用户可根据应用程序的需要,在OS_CFG.H中设置
OS_LOWEST_PRIO,即定义了可供使用的优先级别共OS_LOWEST_PRIO +1个。
固定地,系统总是把最低优先级别自动赋给空闲任务,如果系统中还有统计任务,则其优先级别为
OS_LOWEST_PRIO -1。
给某一个用户任务定义优先级别,需要在调用系统函数OSTaskCreate()来创建任务时,用该函数的
第4个参数prio来指定。
由于每个任务都具有唯一的优先级别,因此uC/OS-II通常也用任务的优先级别来作为这个任务的标
示。
任务堆栈:
所谓堆栈,就是在存储器中按数据“后进先出(LIFO)”的原则组织的连续存储空间。为了满足任务
切换和响应中断时保存CPU寄存器中的内容及存储任务私有数据的需要,每个任务都应该配有自己
的堆栈。任务堆栈是任务的重要组成部分。
任务堆栈的创建:
为方便定义任务堆栈,在文件OS_CPU.H中专门定义了一个数据类型OS_STK:
Typedef unsigned int OS_STK; // 该类型长度为16位
这样,在应用程序中定义任务堆栈的栈区就非常简单,即定义一个OS_STK类型的一个数组即可。例
如:
#define TASK_STK_SIZE 512 // 定义堆栈长度(1024字节)
OS_STK TaskStk[TASK_STK_SIZE]; //定义一个数组来作为任务堆栈
创建任务函数OSTaskCreate()原型如下:
INT8U OSTaskCreate(
void (*task)(void *pd), //指向任务的指针
void *pdata, //传递给任务的参数
OS_STK *ptos, //任务堆栈栈顶的指针
INT8U prio ); //指定任务优先级别的参数
创建一个任务MyTask,堆栈的长度为128字节,优先级别为20,任务参数pdata的实参为ttt。试写
出需要的代码。
#define MyTaskStkN 64
OS_STK MyTaskStk[MyTaskStkN];
void main(void)
{
……
OSTaskCreate(
MyTask,
&ttt,
&MyTaskStk[0],
20);
……
}
任务堆栈的初始化:
当CPU启动运行一个任务时,CPU的各寄存器总是需要预置一些初始数据,例如指向任务的指针、任
务堆栈指针、程序状态字等。那么,系统启动任务时,CPU从何处可以获得这些数据呢?
最方便的方法就是让CPU从这个任务的任务堆栈里获得这些数据!
其实,任务堆栈的初始化就是对该任务的虚拟处理器的初始化(复位)。
由于各处理器的寄存器及对堆栈的操作方式不同,因此该函数需要用户在进行uC/OS-II的移植时,
按所使用的处理器由用户来编写。
任务控制块(TCB)及任务控制块链表
一旦任务建立,一个任务控制块OS_TCB就被赋值。
任务控制块是一个数据结构,保存该任务的相关参数,包括任务堆栈指针,任务的当前状态,任务
的优先级等。
任务CPU使用权被剥夺时,TCB保存该时刻任务状态;任务重新得到CPU控制权时,TCB确保任务从当
时被中断的那一点丝毫不差地继续执行。
OS_TCB全部驻留在RAM中。
任务控制块就相当于是一个任务的身份证,没有任务控制块的任务是不能被系统承认和管理的。
任务控制块链表:
uC/OS-II用两条链表来管理任务控制块:
空任务块链表(所有任务控制块还没有分配给任务),是在应用程序调用函数OSInit()对系统进行
初始化时建立的。
任务块链表(所有任务控制块已经分配给任务),是在调用函数OSTaskCreate()创建任务时建立的
。建立任务控制块链表的具体做法是,从空链表摘取一个空任务控制块,然后填充上任务属性后再
形成新的链表。
系统在调用函数OSInit()对uC/OS-II系统进行初始化时,先在RAM中建立一个OS_TCB结构类型的数
组OSTCBTbl[ ],每个数组元素就是一个任务控制块,然后把这些控制块链接成一个如图所示的链表
。由于链表中的这些控制块还没有与具体任务相关联,因此这个链表叫做空任务块链表。
从图中可以看到,uC/OS-II初始化时建立的空任务链表元素一共是OS_MAX_TASKS+OS_N_SYS_TASKS
个。其中定义在文件OS_CFG.H中的常数OS_MAX_TASKS 指明了用户任务的最大数目;而定义在文件
UCOS_II.H中的常数OS_N_SYSTASKS指明了系统任务的数目(在图中,其值为2:一个空闲任务,一
个统计任务)。
每当应用程序调用系统函数OSTaskCreate()或OSTaskCreateExt()创建一个任务时,系统就会将任
务控制块链表头指针OSTCBFreeList指向的任务控制块分配给该任务。在给任务控制块中的各成员
赋值后,就按任务控制块链表的头指针OSTCBList 将其加入到任务控制块链表中。
任务控制块的初始化:
当用户程序调用函数OSTaskCreate()创建一个任务时,这个函数会调用系统函数OSTCBInit()来为
任务控制块进行初始化。这个函数首先为被创建任务从空任务控制块链表获取一个任务控制块,然
后利用任务的属性对任务控制块各个成员进行赋值,最后再把这个任务控制块链入到任务控制块链
表的头部。
就绪任务表及任务调度:
多任务操作系统的核心:任务调度。
调度定义:就是通过一个算法在多个任务中确定那个任务来运行。做这项工作的就是调度器。
任务调度的思想:总是让优先级最高的就绪任务处于运行状态。
uC/OS-I进行任务调度的依据:任务就续表。
为了能使系统清楚地知道系统中哪些任务已经就绪,那些还没有就绪,UC/OS-II在RAM中设立了一
个记录表,系统中的每个任务都在这个表占据一个位置,并用这个位置的状态(1或者0)来表示任
务是否处于就绪状态。这个表就叫做任务就绪表。
任务就绪表的结构:
每个就绪的任务都放入就绪表中,就绪表有两个变量:OSRdyGrp、OSRdyTbl[]
数据类型为INT8U的变量OSRdyGrp,该变量的每一个位都对应OSRdyTbl[]的一个任务组(即数组的
一个元素)。
由于每个任务的就绪状态只占一位,因此OSRdyTbl[]数组的一个元素可表达8个任务的就绪状态。
即每一个数组元素描述了8个任务的就绪状态,于是这8个任务就可以看成一个任务组。
如何根据任务的优先级别查找任务在就绪表的位置呢
例:已知某一个已经就绪的任务优先级别为prio=30,试判断应该在就绪表的哪一位置上置1。
分析:由于优先级别是一个单字节的数字,而且最大值不会超过63,即二进制00111111,因此,可
以把优先级别看成是一个6位的二进制数,这样就可以用高3位(D5、D4、D3)来指明变量OSRdyGrp的
具体数据位,并用来确定就绪表数组元素的下标;用低3位(D2、D1、D0)来指明该数组元素的具体
数据位。
答:30的二进制形式为00011110,其低6位为011110,于是可知应该在OSRdyTbl[3]的D6位上置1,
同时要把变量OSRdyGrp的D3位置1。
任务的调度:
任务切换:在多任务系统中,令CPU中止当前正在运行的任务而去运行另一个任务的工作。
任务调度:按某种规则进行任务切换的工作。
任务调度由任务调度器完成,调度器主要工作:
1. 在任务就绪表中查找具有最高优先级别的就绪任务。
2. 实现任务切换。
uC/OS-II有两种调度器:
任务级调度器(由OSSched()实现)、中断级调度器(由OSIntExt()实现)。
任务切换的两个步骤:
1. 获得待运行任务的TCB指针。
2. 进行断点数据的切换。
任务切换过程
根据就绪表获得待运行任务的任务控制块指针-》处理器的SP=任务块中保存的SP-》恢复待运行任
务的运行环境-》处理器的PC=任务堆栈中的断点地址
其实,调度器在进行调度时,在这个位置还要进行一下判断:究竟是待运行任务是否为当前任务,
如果是,则不切换;如果不是才切换,而且还要保存被中止任务的运行环境。
任务的创建:
uC/OS-II是通过任务控制块来管理任务的。
uC/OS-II有两个用来创建任务的函数:TaskCreate()、OSTaskCreateExt()
OSTaskCreate()向下兼容,OSTaskCreateExt()是前一函数的扩展,提供 了一些附加功能。
任务可以在多任务调度开始前建立,也可以在其他任务执行过程中建立。
任务不能由中断服务程序建立。
用函数OSTaskCreateExt()创建任务:
应用程序还可以通过调用函数OSTaskCreateExt()来创建一个任务,用此函数创建任务将更加灵活
,但也会增加开销,函数OSTaskCreateExt()源代码如下:
INT8U OSTaskCreateExt(
void (*task)(void *pd), // 指向任务的指针
void *pdata, // 传递给任务的参数
OS_STK *ptos, // 指向任务堆栈栈顶的指针
INT8U prio, // 任务的优先级
INT16U id, // 任务的标示
OS_STK *pbos, // 任务堆栈栈底的指针
INT32U stk_size, // 任务堆栈的容量
void *pext, // 指向附加数据域的指针
INT16U opt // 用于设置操作选项
);
创建任务的一般方法
可在调用函数OSStart()启动任务调度之前来创建。
在任务中来创建。
uC/OS-II的规定:在OSStart()启动任务调度之前必须创建至少一个任务。
这样,在OSStart()之前先创建一个任务,并赋予它最高的优先级,从而使它 成为起始任
务,然后在这个起始任务中,在创建其它各任务。
如果要使用系统提供的统计任务,则统计任务的初始化函数必须在这个起始任务中来调用。
uC/OS-II不允许在中断服务程序中创建任务
任务的挂起和恢复:
所谓挂起一个任务,就是停止这个任务的运行。
在uC/OS-II中,用户任务可通过调用系统提供的函数OSTaskSuspend()来挂起自身或者除空闲任务
之外的其他任务。用函数OSTaskSuspend()挂起的任务,只能在其它任务中通过调用恢复函数
OSTaskResume()使其恢复为就绪状态。
改变任务优先级:
在用户建立任务的时候会分配给任务一个优先级。在程序运行期间,用户可以通过调用
OSTaskChangePrio()来改变任务的优先级。换句话说,就是µC/OS-Ⅱ允许用户动态的改变任务的优
先级。
函数OSTaskChangePrio()的原型如下:
INT8U OSTaskChangePrio(
INT8U oldprio, // 任务现在的优先级
INT8U newprio // 要修改的优先级
);
任务的删除:
删除任务,就是把该任务处于休眠状态。
并不是说任务的代码真的被删除了,只是任务的代码不再被操作系统调用。
通过调用OSTaskDel()就可以完成删除任务自身或除了空闲任务之外的其他任务。
删除任务具体做法是,把被删除任务的任务控制块从任务控制块链表中删除,并归还给空任务控制
块链表,然后在任务就绪表中把该任务的就绪状态位置0,于是该任务就不能再被调度器调用了。
函数OSTaskDel()的原型如下
#if OS_TASK_DEL_EN
INT8U OSTaskDel (
INT8U prio // 要删除任务的优先级
);
如果任务删除自己,则应在调用函数时令函数的参数prio为OS_PRIO_SELF。
查询任务的信息
有时,在应用程序运行中需要了解一个任务的指针、堆栈等信息,这时可通过调用函数
OSTaskQuery()来获取选定的任务的信息。
若调用函数OSTaskQuery()查询成功,则返回OS_NO_ERR,并把查询得到的任务信息存放在结构
OS_TCB类型的变量中。
函数OSTaskQuery()的原型如下:
INT8U OSTaskQuery(
INT8U prio, // 待查询任务的优先级
OS_TCB *pdata // 存储任务信息的结构
);
uC/OS-II的初始化
系统首先调用初始化函数OSIint()。OSIint()初始化μC/OS-Ⅱ所有的变量和数据结构。
OSInit()建立空闲任务idle task,这个任务总是处于就绪态的。空闲任务OSTaskIdle()的优先级
总是设成最低,即OS_LOWEST_PRIO。
如果统计任务允许OS_TASK_STAT_EN和任务建立扩展允许都设为1,则OSInit()还得建立统计任务
OSTaskStat()并且让其进入就绪态。OSTaskStat的优先级总是设为OS_LOWEST_PRIO-1。
初始化时主要是创建包括空任务控制块链表在内的5个空数据缓冲区,及按任务优先级存放任务控
制块指针的数组OSTCBPrioTbl[OS_LOWEST_PRIO+1]。
1. 任务由任务控制块、任务堆栈和任务代码三部分组成。系统通过任务控制块来感知和控制任务
;任务堆栈主要用来保护断点和恢复断点;任务代码是一个超循环结构,它描述了任务的执行过程
。在创建一个任务时,函数OSTaskCreate()或OSTaskCreateExt()负责给任务分配任务控制块和任
务堆栈,并对它们进行初始化,然后把任务控制块任务堆栈、任务代码三者关联起来形成一个完整任
务。
2. 系统是按任务就绪表和任务的优先级别来调度任务的。执行任务调度工作的是调度器,它负责
查找具有最高优先级别的就绪任务并运行它,同时把这个任务TCB的指针存放在OSTABCur中。通常
,系统在调用API函数和运行中断服务程序之后都要调用函数OSSched()来进行一次任务调度。
3. 任务切换的核心工作是任务堆栈指针的切换。
4. 任务调度器代码的设计,使得它的运行时间与系统中的任务数无关,从而使它满足了实时系统
的要求。
5. 任务的优先级别也是任务的标识。
6. 应用程序首先应该调用函数OSInit()对全局变量和数据结构进行初始化已建立uC/OS-II的运行
环境。
7. 应用程序是通过调用函数OSStart()开始进入多任务管理的,但在调用函数OSStart()之前,必
须至少创建了一个任务。