小白痴对UCOSIII的些许整理
UCOSIII是一个可裁剪、可固化、可剥夺的多任务系统,没有任务数目的限制
主要特性:
可剥夺多任务管理:总是执行当前就绪的最高优先级任务
同优先级任务的时间片轮转调度:允许一个任务优先级被多个任务使用,当这个优先级处于最高就绪态的时候,UCOS3就会轮流调度处于这个优先级的所有任务,让每个任务运行一段由用户指定的时间长度---时间片
极短的关中断时间:UCOS3可以采用锁定内核调度的方式来保护临界段代码,可以将关中断的时间降到最低,使之能够非常快速的响应中断请求。
任务数目不受限制:没有任务数目限制的,从实际应用角度考虑,任务数目会受到CPU所使用的存储空间限制,包括代码空间和数据空间。
优先级数量不受限制:支持无限多的任务优先级
内核对象数目不受限制:允许定义任意数目的内核对象。内核对象指任务、信号量、互斥信号量、事件标志组、消息队列、定时器和存储块。
软件定时器:可以任意定义单次 和周期 型定时器,定时器是一个递减计数器,递减到零就会执行预先定义好的操作。每个定时器可以指定所需操作,周期型定时器在递减到0时会执行指定操作,并自动重置计数器值。
同时等待多个内核对象:允许一个任务同时等待多个事件。一个任务能够挂在多个信号量或消息队列上,当其中任何一个等待的事件发生时,等待任务就会被唤醒。
直接向任务发送信号:允许中断或任务直接给另一个任务发送信号,避免创建和使用诸如信号量或时间标志内核对象作为向其他任务发送信号的中介,有效的提高了系统性能
直接向任务发送消息:允许中断或任务直接给另一个任务发送消息,避免创建和使用队列作为中介
任务寄存器:每个任务都可以设定若干个 任务寄存器,主要用来保存各个任务的错误信息,ID识别信息,中断关闭时间的测量结果等
任务级时钟节拍处理:时钟节拍是通过一个专门任务完成的,定时中断仅触发该任务。将延迟处理和超时判断放在任务级代码完成,能极大的减少中断延迟时间。
防止死锁:所有的UCOS3的等待功能都提供了超时检测机制,有效的避免了思索。
时间戳:UCOS3需要一个16或32位的自由运行计数器(时基计数器)来实现时间测量,在系统运行时,可以通过读取该计数器来测量某一个事件的时间信息。例子:当ISR给任务发送消息时,会自动读取该计数器的数值并将其附加在消息中。当任务读取消息时,可得到该消息携带的时标,再通过读取当前的时标,并计算两个时标的差值,就可以确定传递这条消息所花费的确切时间。
3.03UCOS3
cpu_core.c 包含了适用于所有CPU架构的C代码,该文件包含了用来测量中断关闭事件的函数;中断关闭由宏CPU_CRITICAL_ENTER()实现;中断打开由宏CPU_CRITICAL_EXIT()实现。包含以一个可模仿前导码0计算的函数(为了防止CPU不提供这样的指令以及其他的一些函数)。
cpu_core.h包含了.c文件中函数的原型声明,用来测量中断关闭时间变量的定义。
cpu_def.h包含uC/CPU模块使用的各种#define常量
cpu.h包含了一些类型的定义,使UCOS3和其他模块可与CPU架构和编译器字宽度无关。这个文件用户可以找到CPU_INT16U,CPU_INT32U,CPU_FP32等数据类型的定义。还指定了CPU使用的是大端模式还是小端模式,定义了UCOS3使用的CPU_STK数据类型,定义了CPU_CRITICAL_ENTER()和CPU_CRITICAL_EXIT(),包括一些与CPU架构相关的函数声明。
cpu_a.asm包含了一些用会边语言编写的函数,可用来开中断和关中断,计算前导0(前提是CPU支持这条指令),以及其他一些只能用汇编语言编写的与CPU相关的函数,这个文件中的函数可以从C代码调用
cpu_c.c包含了一些基于特定CPU架构但为了可移植而用C语言编写的函数C代码,普通原则:除非汇编语言能显著提高性能,反之尽量使用C语言编写函数。
Lib开头的文件是由一些可移植并与编译器无关的函数组成,UCOS3不适用LIB但是却是被UCOS3和CPU模块假定lib_def.h存在的。
lib_ascii.h lib_ascii.c提供ASCII_ToLower(),ASCII_ToUpper(),ASCII_IsAlpha()和ASCII_IsDig()函数,可以分别替代标准库函数tolower(),toupper(),isalpha(),isdigit().(备用候补作用)
lib_def.h定义了许多常量,例子:RTUE/FALSE YES/NO ENABLE/DISABLE以及各种进制常量。文件中的所有#define都以DEF_开头,所以上述的名字实际上为DEF_TRUE/DEF_FALSE。该文件还为常用数学计算定义了宏
lib_math.h和lib_math.c包含了Math_Rand() Math_SetRand()等函数的源代码,可以用来替代标准库函数rand()和srand().
lib_mem.c和lib_mem.h包含了Mem_Clr(),Mem_Set(),Mem_Copy()和Mem_Cmp()等函数的源代码,可以用来替代标准库函数memclr(),memset(),memcpy(),memcmp().
lib_str.c和lib_str.h包含了Str_Lenr(),Str_Copy(),Str_Cmp()等函数的源代码,可以替代标准库函数srtlen(),strcopy(),strcmp().
lib_mem_a.asm文件包含了lib_mem.c函数的汇编优化版
表4.2
bsp.c bsp.h
在CM3中有三种跟踪源:ETM,ITM,DWT;要想使用ETM,ITM,DWT就要将DEMCR寄存器的TRCENA位(bit24)置1,DEMCR寄存器地址为0XE000EDFC(书的页数P501);在DWT组件中有一个CYCCNT寄存器,这个寄存器用来对时钟周期计数,可以用来测量执行某个任务所花费的时间。
DWT组件由多个寄存器,而本次实验只使用DWT的控制寄存器CTRL(寄存器地址为0XE0001000),CYCCNT(寄存器地址为0XE0001004);如果使用时钟计数功能需要将 CTRL寄存器的bit0置1.
第三章
os_cfg_app.h主要是对UCOS3内部一些系统任务的配置,比如任务优先级,任务堆栈,UCOS3的系统时钟节拍等,都是一些宏定义
os_cpu_c.c修改堆栈初始化函数OSTaskStkInit().
UCOS3中的五个系统任务:空闲任务,时钟节拍任务,统计任务,定时任务和中断服务管理任务,在系统初始化的时候至少创建2个任务:空闲任务和时钟节拍任务。空闲任务优先级为最低(OS_CFG_PRIO_MAX-1;中断服务管理任务的优先级为最高0;其他三个任务的优先级可以任意设置。(注:本次实验中已分配的优先级OS_CFG_PRIO_MAX-2(定时器任务)和OS_CFG_PRIO_MAX-1(时钟节拍任务)是不能用户使用的);
任务管理---多任务操作系统最主要的是:任务的管理,任务的创建,挂起,删除和调度。
UCOS3必须按照一定的顺序初始化并打开使用:
1.调用CPU_Init()初始化UCOS3
2.创建任务,一般在main()函数中创建一个开始任务,其他任务在开始任务中创建,在调用OSTaskCreate()函数创建任务的时候一定调用OS_CRITICAL_ENTER()函数进入临界区,在任务创建完以后调用OS_CRITICAL_EXIT()函数退出临界区。
3.调用OSStart()函数开启UCOS3
(注:在调用OSStart()开启UCOS3之前一定要至少创建一个任务,其实在调用OSInit()函数初始化UCOS3的时候已经创建一个空闲任务了)
任务状态-----UCOS3支持的是单核CPU,在某一时刻只有一个任务会获得CPU使用权进入运行态,其他的任务就会进入其他态,UCOS3中的任务有多个态
任务状态(UCOS3在这五个状态中转换):
休眠:任务只是以任务函数的方式存在,只是存储区中的一段代码,并未用OSTaskCreate()函数创建这个任务,不受UCOS3管理
就绪:任务在就绪表中已经登记,等待获取CPU使用权
运行:正在运行的任务就处于运行态
等待:正在运行的任务需要等待某一个事件,比如信号量,消息,事件标志组等,就会暂时让出CPU使用权,进入等待事件状态
中断服务:一个正在执行的任务被中断打断,CPU转而执行中断服务程序,这个时候这个任务就会被挂起,进入中断服务状态
任务控制块----------UCOS3中重要的数据结构:任务控制块OS_TCB。用来保存任务的信息,使用OSTaskCreate()函数来创建任务的时候就会给任务分配一个任务控制块(一个结构体)。
任务堆栈----非常重要的概念,用来在切换任务和调用其他函数的时候保存现场,每个任务都应有自己的堆栈,创建堆栈的步骤:
1.定义一个CPU_STK(4个字节,意味着任务的实际堆栈大小应是用户定义的4倍)变量,在UCOS3中用CPU_STK数据类型来定义任务堆栈,CPU_STK在cpu.h中有定义,CPU_STK等同CPU_INT32U;例子:CPU_STK TASK_STK[64] 堆栈大小为64*4=256;
使用OSTaskCreate()函数创建任务的时候可以把创建的堆栈传递给任务。
例子:创建的堆栈传递给任务,将堆栈的基地址传递给OSTaskCreate()函数的参数p_stk_base,将堆栈深度传递给参数stk_limit,堆栈深度(作用:主要用来检测堆栈是否为空,将堆栈大小传递给参数stk_size)通常为堆栈大小的十分之一;代码如下:
OSTaskCreate((OS_TCB* )&StartTaskTCB, //任务控制块
(CPU_CHAR* )"start task",//任务名字
(OS_TASK_PTR)start_task, //任务函数
(void* )0, //传递给任务函数的参数
(OS_PRIO )START_TASK_PRIO, //任务优先级
(CPU_STK* )&TASK_STK[0], //任务堆栈基地址
(CPU_STK_SIZE )TASK_STK_SIZE/10,//任务堆栈深度限位
(CPU_STK_SIZE )TASK_STK_SZIE,//任务堆大小
(OS_MSG_QTY )0,
(OS_TICK )0,
(void* )0,//用户补充的存储区
(OS_OPT )OS_OPT_TASK_STK_CHK|OS_OPT_TASK_STK_CLR,
(OS_ERR* )&err); //存放该函数错误时的返回值
创建任务的时候会初始化任务的堆栈,需要提前将CPU的寄存器保存在任务堆栈中,完成这个任务的是OSTaskStkInit()函数,但是用户不能调用,是被OSTaskCreate()函数在创建任务的时候调用的
任务就绪表-----将已经就绪的任务放进去,分为两部分:优先级位映射表OSPrioTbl[]和就绪任务列表OSRdyList[].
映射表的表元素的宽度可以是8,16,32,根据CPU_DATA(cpu.h)中的数值不同而不同。
OSPrioTbl[]数组的优先级是从左到右优先级逐渐降低,但是每个元素的最低位在右,OSPrioTbl[]的bit31最高优先级为0,bit0为优先级31;这样做的主要目的是为了支持一条特殊指令“计算前导0(CLZ)”,这条指令可以快速找到最高位优先级任务。
UCOS3中任务数目由宏OS_CFG_PRIO_MAX(os_cfg.h)配置。
优先级位映射表:当某一个任务就绪以后会将优先级位映射表中相应的置1.
关于优先级的三个函数操作:OS_PrioGetHighest(),OS_PrioInsert()和OS_PrioRemove().作用分别为:获取就续表中最高优先级任务,将某个任务在就续表中相对应的位置1和将某个任务在就续表中相对应的位清0.
OS_PrioGetHighest()函数代码:
OS_PRIO OS_prioGetHighest(void){
CPU_DATA *p_tbl;
OS_PRIO prio;
prio =(O_PRIO)0;
p_tbl=&OSPrioTbl[0];//从OSPrioTbl[0]开始扫描映射表,一直遇到非零项
while(*p_tbl==(CPU_DATA)0){//当数组OSPrioTbl[]中的某个元素为0时,就继续扫描下一个数组元素,prio加DEF_INT_CPU_NBR_BITS位,根据CPU_DATA长度的不同DEF_INT_CPU_NBR_BITS数值不同,定义CPU_DATA为32位,DEF_INT_CPU_NBR_BITS为32,prio加32.
prio+=DEF_INT_CPU_NBR_BITS;
p_tbl++;//p_tbl加1,继续寻找OSPrioTbl[]数组的下一个元素
}
//一旦找到一个非零项,在加上该项的前导0数量就找到了最高优先级任务了
prio+=(OS_PRIO)CPU_CntLeadZeros(*p_tbl);
return(prio);
}
以上函数可以看出 计算前导0使用的函数Zeros()是由汇编编写的,
而在cpu_a.asm中,代码:
CPU_CntLeadZeros
CLZ R0,R0 ; 计算前导0
BX LR
函数OS_PrioInsert()和OS_PrioRemove()分别将指定优先级任务相对应的优先级映射表中的位置1和清0,代码:
//将参数prio对应的优先级映射表中的位置1.
void OS_PrioInsert(OS_PRIO prio)
{
CPU_DATA bit;
CPU_DATA bit_nbr;
OS_PRIO ix;
ix =prio/DEF_INT_CPU_NBR_BITS;
bit_nbr =(CPU_DATA)prio&(DEF_INT_CPU_NBR_BITS -1u);
bit =1u;
bit <<=(DEF_INT_CPU_NBR_BITS -1u)-bit_nbr;
OSPrioTbl[ix]|=bit;
}
//将参数prio对应的优先级映射表中的位清0
void OS_PrioRemove(OS_PRIO prio)
{
CPU_DATA bit;
CPU_DATA bit_nbr;
OS_PRIO ix;
ix =prio/DEF_INT_CPU_NBR_BITS;
bit_nbr =(CPU_DATA)prio&(DEF_INT_CPU_NBR_BITS-1u);
bit =1u;
bit <<=(DEF_INT_CPU_NBR_BITS-1u)-bit_nbr;
OSPrioTbl[ix]&=~bit;
}
就绪任务列表--------主要用来标记拿些任务就绪了的。就绪任务列表OSRdyList[]是用来记录每一个优先级下所有就绪的任务,在os.h中有定义,数组元素的类型为OS_RDY_LIST且为一个结构体。
struct os_rdy_list{
OS_TCB *HeadPtr;//用于创建链表,指向链表头
OS_TCB *TailPtr;//用于创建链表,指向链表尾
OS_OBJ_QTY NbrEnteries;//此优先级下的任务数量
};
UCOS3支持时间片轮转调度,所以在一个优先级下会有多个任务,就需要对多个任务做一个管理,本次实验中使用OSRdyList[]数组来管理任务。在这个数组中每个元素对应一个优先级。例子:OSRdyList[0]管理优先级0下的所有任务。OSRdyList[0]为OS_RDY_LIST类型,此类型为结构体可以看到成员变量:HeadPtr和TailPtr分别指向OS_TCB,所指向的东西是用来构造链表的,所以在同一个优先级下的所有任务是通过链表来管理的,HeadPtr和TailPtr分别指向链表的头和尾部,NbrEnteries用来记录此优先级下的任务数量。
(注意:有些优先级只能一个任务,比如UCOS3的系统任务,空闲任务OS_TmrTask(),时钟节拍任务OS_TickTask,统计任务OS_StatTask(),定时任务OS_TmrTask(),中断服务管理任务OS_IntQTask());
而针对就绪列表的操作有六个函数都在os_core.c文件,全为UCOS3内部使用。分别是:
OS_RdyListInit():由OSInit()调用用来初始化并清空任务就绪列表
OS_RdyListInsertHead():向某一优先级下的任务双向链表头部添加一个任务控制块TCB;
OS_RdyListInsertTail():向某一优先级下的任务双向链表尾部添加一个任务控制块TCB;
OS_RdyListRemove():将任务控制块TCB从任务就绪列表中删除
OS_RdyListInsertTail():将一个任务控制块TCB从双向链表的头部移到尾部
OS_RdyListInsert():在就绪表中添加一个任务控制块TCB;
任务调度和切换
可剥夺型调度------让就续表中优先级最高的任务获得CPU的使用权,UCOS3是可剥夺型,抢占式的,可以抢了低优先级任务的CPU使用权,任务的调度是由一个叫做任务调度器的东西来完成的;任务调度器分为两种:任务级调度器和中断级调度器
任务级调度器分为OSSched()且函数代码在os_core.c中。
代码:
void OSSched(void){
CPU_SR_ALLOC();
//OSSched()为任务级调度器,如果是在中断服务函数中不能使用
if(OSIntNestingCtr>(OS_NESTING_CTR)0{
return;
}
//调度器是否上锁
if(OSSchedLockNestingCtr>(OS_NESTING_CTR)0){
return;
}
CPU_INT_DIS();
OSPrioHighRdy =OS_PrioGetHighest();
OSTCBHighRdyPtr=OSRdyList[OSPrioHighRdy].HeadPtr;
if(OSTCBHighRdyPtr==OSTCBCurPtr){
CPU_INT_EN();
return;
}
#if OS_CFG_TASK_PROFILE_EN>0u
OSTCBHighRdyPtr->CtxSwCtr++;
#endif
OSTaskCtxSwCtr++;
#if defined(OS_CFG_TLS_TBL_SIZE)&&(OS_CFG_TLS_TBL_SIZE>0u)
OS_TLS_TaskSw();
#endif
OS_TASK_SW();
CPU_INT_EN();
}

浙公网安备 33010602011771号