ucos ii操作系统学习笔记一——系统简述及任务初始化等相关概念
简述
实时性操作系统概念
~~ 实时性操作系统分为硬件实时性与软件实时性系统。~~
硬件实时性操作系统对于操作系统中的任务完成时间的以及准确度有着严格的要求,不能出现任务执行时间超出或者准确度极差的情况。
软件实时性操作系统对于任务完成时间就要求没有那么高了,对于软件实时性的系统,计算机对于任务执行时间超出的情况可以有一定的容忍度。即任务执行时间超出的话,计算得到的结果也具有一定的可信度与利用价值。
简单描述一下,硬件实时性操作系统对于任务实时性的要求就如同期末考试,你必须准时参与并且保证完成准确度。软件实时性操作系统如同平时的作业,你可以因为一些特殊原因迟交作业而得到一定的容许。
实时性操作系统具有三个条件:
1、实时性操作系统必须是多任务操作系统
2、任务执行时间应与任务数量无关
3、中断延时的时间应该可预知并且尽可能短
具有私有空间的叫进程,没有私有空间的叫线程。uc/os ii的所有任务都是线程。
uc/os ii的任务有两种,用户任务和系统任务。目前该系统最高拥有64个任务,包括用户任务和系统任务。
好吧,其实上面都是废话。
uc/os ii的5种状态:
任务的状态 | 说明 |
---|---|
睡眠状态 | 任务只是以代码的形式驻留在程序空间(ROM或RAM),还没有交给操作系统管理时的情况叫做睡眠状态。简单地说,任务在没有被配备任务控制块或被剥夺了任务控制块时的状态叫做任务的睡眠状态 |
就绪状态 | 如果系统为任务配备了任务控制块且在任务就绪表中进行了就绪登记,则任务就具备了运行的充分条件,这时任务的状态叫做就绪状态 |
运行状态 | 处于就绪状态的任务如果经调度器判断获得了CPU的使用权,则任务就进入运行状态任何时刻只能有一个任务处于运行状态,就绪的任务只有当所有优先级高于本任务的任务都转为等待状态时,才能进入运行状态 |
等待状态 | 正在运行的任务,需要等待一段时间或需要等待一个事件发生再运行时,该任务就会把CPU的使用权让给其他任务而使任务进入等待状态 |
中断服务状态 | 一个正在运行的任务一旦响应中断申请就会中止运行而去执行中断服务程序,这时任务的状态叫做中断服务状态 |
用户任务代码的一般结构
任务的执行代码通常是一个无限循环结构(对于一些一次性任务不一样),并且在这个循环中可以响应中断,我们称这个结构为超循环结构。
OS_ENTER_CRITICCAL()和OS_EXIT_CRITICAL()是uc/os ii中定义的两个宏。OS_ENTER_CRITICCAL()这个里面封装了关闭中断的代码,OS_EXIT_CRITICAL封装了打开中断的代码,这也就意味着在这两个宏之间的函数将不可被打断。这之间的代码称为临界宏段,两段代码分别成为进入临界宏段与退出临界宏段。
用户应用代码的一般结构
从程序代码的形式来看,用户任务就是一个C语言程序。但是这个程序并不由main函数调用,它在程序中与主函数处于同等地位,何时被调用由操作系统决定,但是代码是从主函数开始执行的,主函数要负责任务的创建并将他们交给操作系统。
示例如下:
void my_task1(void *data)
{
for(;;)
{
}
}
void my_task2(void *data)
{
for(;;)
{
}
}
void main(void)
{
OSInit();//初始化系统
.......
OSTaskCreate(my_task1,....);//创建系统任务1
OSTaskCreate(my_task2,....);//创建系统任务2
.......
OSStart();//启动系统
}
系统任务
uc/os预定了两个系统任务,空闲任务(OSTaskIdle)和统计任务(OSStatInit)。空闲任务是每个应用程序必须使用的,而统计任务则是可以根据需要是否进行使用的。
空闲任务
空闲任务即是无可用的用户任务运行时的一个状态。
统计任务
统计任务是每秒计算一次CPU在单位时间内被使用的时间,并将结果放在变量OSCPUsage中,以便其他程序了解CPU的执行情况。
如果用户程序决定使用统计任务,则必须定义在系统头文件OS_CFG.H中的系统配置常数OS_TASK_STAT_EN设置为一,并且要调用函数OSStatInit()对统计任务进行初始化。
任务的优先权及优先级别、
由于us/os-ii最多可以创建64个任务,所以任务优先级有64个级别,从0到63,数字越小,它的执行优先级越高。系统的配置文件OS_CFG.H中定义了一个定义最低优先级别的常数OS_LOWEST_PRIO,对应的任务的总数也不能超过OS_LOWEST_PRIO-1个。
任务堆栈
堆栈:在存储器中按照先进后出原则组织的连续储存空间。为了在相应系统调度以及中断的时候能够保存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创建任务时,可以将数组的指针传给OSTaskCreate()的栈顶参数ptos,就可以将该数组与任务关联起来做任务的堆栈。
OSTaskCreate()的原型:
INT8U OSTaskCreate (void (*task)(void *p_arg),//指向任务的指针
void *p_arg,//传递给任务的参数
OS_STK *ptos,//任务堆栈栈顶的指针
INT8U prio);//指定任务优先级的参数
任务:创建一个堆栈,堆栈长度为128字节,任务优先级20,任务参数pdata的实参为MyTaskAgu。请写出main函数的代码。
#define MyTaskStkN 64
OS_STK MyTaskStk[MyTaskStkN];
void main(void)
{
//如果处理器支持堆栈的生长模式是向下的用下面的示例:
OSTaskCreate(
MyTask,
&MyTaskAgu,
&MyTaskStk[MyTaskStkN-1],
20
);
//反之,则改为:
OSTaskCreate(
MyTask,
&MyTaskAgu,
&MyTaskStk[0],
20
);
}
如果想增加代码移植性,可以考虑一下两种写法都写,通过OS_CFG.H文件中的OS_STK_GROWTH作为选择开关。
#if OS_STK_GROWTH==1
OSTaskCreate(
MyTask,
&MyTaskAgu,
&MyTaskStk[MyTaskStkN-1],
20
);
#else
OSTaskCreate(
MyTask,
&MyTaskAgu,
&MyTaskStk[0],
20
);
#endif
任务堆栈的初始化
CPU的各个寄存器总是需要预置一些功能,如指向任务的指针,程序状态字PSW等。所以应该将他们都放在任务堆栈里。这样本任务获得CPU的使用权时,应该将堆栈的内容复制到CPU的各个寄存器中,使得任务可以正常启动并运行。
将任务的初始化数据放到任务堆栈的工作就叫做任务堆栈的初始化。为了完成这个任务,uc/os ii操作系统提供了任务堆栈初始化函数OSTaskStkInit()。该函数原型如下:
OS_STK *OSTaskStkInit (
void (*task)(void *p_arg),
void *p_arg,
OS_STK *ptos,
INT16U opt);
其实用户一般不接触这个函数,这个函数一般由OSTaskCreate进行调用。另外由于各个处理器对堆栈的处理方式并不相同,所以我们通常在移植系统时要按照对应的处理器来编写这个函数的实现细节。
任务控制块及其链表
uc/osii中用来记录堆栈指针,任务当前状态,任务优先级别等一些与任务管理有关属性的表就叫任务控制块。任务控制块相当于一个任务的身份认证,系统就是通过任务控制块感知和管理任务的,没有任务控制块的任务不被系统承认。uc/osii控制块将所有任务控制块连接成两个链表,并通过它们管理各个任务。
任务控制块结构
任务控制块是一个结构类型数据,当用户调用OSTaskCreate()函数创建一个任务时,该函数就会对任务控制块中的所有成员赋予与该任务相关的数据,并驻留在RAM中。任务控制块的结构的定义如下:
typedef struct os_tcb {
OS_STK *OSTCBStkPtr; //指向任务堆栈栈顶的指针
#if OS_TASK_CREATE_EXT_EN > 0u
void *OSTCBExtPtr; //指向任务控制块扩展的指针
OS_STK *OSTCBStkBottom; //指向任务堆栈栈底的指针
INT32U OSTCBStkSize; //任务堆栈的长度
INT16U OSTCBOpt; //创建任务堆栈时的选择项
INT16U OSTCBId; //目前该域未被使用
#endif
struct os_tcb *OSTCBNext; //指向后一个任务控制块的指针
struct os_tcb *OSTCBPrev; //指向前一个任务控制块的指针
#if (OS_EVENT_EN)
OS_EVENT *OSTCBEventPtr; //指向事件控制块的指针
#endif
#if (OS_EVENT_EN) && (OS_EVENT_MULTI_EN > 0u)
OS_EVENT **OSTCBEventMultiPtr; //指向多个事件控制块的指针
#endif
#if ((OS_Q_EN > 0u) && (OS_MAX_QS > 0u)) || (OS_MBOX_EN > 0u)
void *OSTCBMsg; //指向传递给任务消息的指针
#endif
#if (OS_FLAG_EN > 0u) && (OS_MAX_FLAGS > 0u)
#if OS_TASK_DEL_EN > 0u
OS_FLAG_NODE *OSTCBFlagNode; //指向事件标志节点的指针
#endif
OS_FLAGS OSTCBFlagsRdy; //使任务准备好运行的事件标志
#endif
INT32U OSTCBDly; //任务等待的时限(节拍数)
INT8U OSTCBStat; //任务的当前状态标志
INT8U OSTCBStatPend; //任务挂起状态
INT8U OSTCBPrio; //任务优先级,(0最高)
INT8U OSTCBX; //用于快速访问就绪表的数据
INT8U OSTCBY; //用于快速访问就绪表的数据
OS_PRIO OSTCBBitX; //用于快速访问就绪表的数据
OS_PRIO OSTCBBitY; //用于快速访问就绪表的数据
#if OS_TASK_DEL_EN > 0u
INT8U OSTCBDelReq; //请求删除任务时用到的标志位
#endif
#if OS_TASK_PROFILE_EN > 0u
INT32U OSTCBCtxSwCtr; //任务切换的时间
INT32U OSTCBCyclesTot; //任务已经运行的时钟周期总数
INT32U OSTCBCyclesStart; //任务恢复开始时的循环计数器快照
OS_STK *OSTCBStkBase; //指向任务堆栈开始的指针
INT32U OSTCBStkUsed; //从堆栈中使用的字节数
#endif
#if OS_TASK_NAME_EN > 0u
INT8U *OSTCBTaskName;
#endif
#if OS_TASK_REG_TBL_SIZE > 0u
INT32U OSTCBRegTbl[OS_TASK_REG_TBL_SIZE];
#endif
} OS_TCB;
其中成员OSTCBStat用来存放当前任务的状态,该成员变量的可能值见表:
值 | 说明 |
---|---|
OS_STAT_RDY | 表示任务处于就绪状态 |
OS_STAT_SEM | 表示任务处于等待信号量状态 |
OS_STAT_MBOX | 表示任务处于等待消息邮箱状态 |
OS_STAT_Q | 表示任务处于等待消息队列状态 |
OS_STAT_SUSPEND | 表示任务处于被挂起状态 |
OS_STAT_MUTEX | 表示信号处于等待互斥型信号量状态 |
任务控制块链表
在任务控制块的管理上uc/osii需要两条链表,一条空任务链表(其中的任务控制块都还没有分配给任务),一条任务链表(其中的任务控制块已经分配给任务)。
具体做法:系统在调用OSInit()对系统进行初始化时,就现在RAM中建立一个OS_TCB结构类型的数组OSTCBTbl[],然后把各个元素连接成一个链表,从而形成一个空任务块链表。
系统初始化时建立的空任务链表可以是OS_MAX_TASKS+OS_N_SYS_TASKS个。其中定义在OS_CFG.H中的常数OS_MAX_TASKS指明了用户任务的最大数目,而OS_N_SYS_TASKS指明了UCOS_II.H文件中系统任务的数目(其值一般为2,即空闲任务以及统计任务)。
以后每当我们调用OS_TaskCreate()或者OS_TaskCreateExt()创建一个任务时,系统会将空任务控制块链表的头指针OSTCBFreeList指向的任务控制块分配给该任务。在给任务控制块各个成员赋值后,我们就可以将任务添加到任务控制块链表中。
为了加快对任务链表的访问速度,我们不仅将任务链表设置为双向链表,在这个同时,我们还定义了一个数据类型为OS_TCB*的数组OSPrioTbl[]。该数组以任务优先级的形式存放了指向各个任务控制块的指针,这样子在访问任务控制块时就不必访问整个链表了。(对于这一段,有不理解的同学可以去看看链表的知识,了解链表读取某个成员数据的原理,大概就能够理解uc/osii系统这种写法的妙用了)。
另外,由于当前执行的任务的任务控制块是访问速度最高的任务控制块,所以我们通常使用另一个变量OSTCBCur来存储当前的任务控制块指针。
同时我们有一个叫OSTaskDel()的函数来删除一个任务。这个函数的本质是将该任务的任务控制块从任务控制链表中删掉,并将其返回给空任务控制块链表。由此可见,任务控制块的重要性,没有任务控制块,系统就不会理会这个任务,因为这意味着它已经被“吊销”了。
任务控制模块的初始化
在任务调用OSTaskCreate()进行初始化时,同时会调用OSTCBInit()来对系统任务控制块进行初始化。
INT8U OS_TCBInit (INT8U prio,//任务的优先级别,保存在OSTCBPrio中
OS_STK *ptos,//任务堆栈栈顶指针,保存至OSTCBStkPtr中
OS_STK *pbos,//任务堆栈栈低指针,保存在OSTCBStkBottom中
INT16U id,//任务的标识符,保存在任务的TCBId中
INT32U stk_size,//任务堆栈的长度,OSTCBStkSize
void *pext,//任务堆栈的扩展指针,保存在OSTCBExtPtr
INT16U opt)//任务堆栈的选择项,保存在OSTCBOpt中
该函数作用如下:
为被创建任务从空任务控制块链表中取出一个任务控制块
用任务控制块属性对任务控制块各个成员进行赋值
把这个控制块链入到任务控制块链表中