0. 阐述对任务的理解,对比任务与C函数的异同点。
- 从代码上看,uCOS-II的任务就是一个函数。但是只是采用函数的形式,仅仅借用了函数的一些特性,用函数来存储代码而已。它不是通过不同的代码来区分不同的任务,而是通过不同的任务堆栈和不同的任务优先级来进行区分。类似与C++和JAVA中的函数重载。 例如,我们可以创建10个任务的任务代码的指针都是Task,这意味着这10个任务共用一段代码,但它们有不同的任务初始化参数、不同的任务堆栈和不同的优先级。
-
从结构上看,uCOS-II的任务由任务程序代码(函数)、任务私有堆栈和任务控制块三部分组成。任务控制块用于关联任务代码,记录任务属性;任务私有堆栈用于保存任务的工作环境虚拟CPU寄存器;任务程序代码就是任务的执行部分了。
问:任务堆栈中有PC指向任务程序代码,为什么在任务控制块中还有指向任务的指针呢?
答:PC的值总是指向待执行指令的地址,它反映的是一个程序的运行进度(任务切换和中断时的断点都需保存真正CPU的PC到任务堆栈的PC中)。而TCB中指向任务的指针是描述任务的人口地址,在创建完首次运行任务时需要通过这个指针找到任务程序代码在内存中的位置。 -
任务与C函数的相同点:都有返回值和形式参数
- 任务与C函数的不同点:任务绝不会被其他任务调用,即使是main函数也不能调用,在系统中各个任务与main( )处于平等地位,它们何时被运行以及何时被中止是OS调度的。也绝不会有返回值,所以返回参数定义为void。形式参数声明为void *类型,该指针用于装载传递任务的参数,可以是变量的地址、数据结构或函数的入口地址等任何需要的参数。这个参数很重要,可以利用它建立许多不同的任务,但这些不同的任务共用一段代码。
- 注意:main( )毕竟是一个应用程序的主函数,是程序运行的入口点,所以它虽然不调用任务,但是要负责任务的创建并将它们交给系统,至于何时运行它们,则与主函数无关!
1. 什么是任务控制块(TCB)?
- uCOS-II中用于记录任务信息(任务堆栈指针、任务当前状态、任务优先级别等)的数据结构。相当于任务在系统中的身份证,系统就是通过任务控制块来感知和管理任务的,没有任务控制块的任务不能被系统承认和管理。
- uCOS-II 将系统中的所有 TCB 构成两个链表(OSTCBList与OSTCBFreeList)进行任务管理。
- 空任务控制块链表 —-未被分配的 TCB 链 OSTCBFreeList。
- 任务控制块链表 —- 已分配的 TCB 链 OSTCBList。
2. 任务控制块是怎样的数据结构?
- 是一个结构体类型数据,通过typedef 把struct os_tcb { }结构体类型重命名为OS_TCB
/*
*********************************************************************************************************
TASK CONTROL BLOCK
*********************************************************************************************************
*/
typedef struct os_tcb {
/* OSTCBStkPtr指向当前任务栈顶的指针。把它放在数据结构的最前面,即偏移量为0的位置,从而使得在汇编语言中
处理这个变量较为容易,因为任务切换的核心工作是:任务堆栈指针的切换,使用更频繁。 */
OS_STK *OSTCBStkPtr; /* Pointer to current top of stack */
struct os_tcb *OSTCBNext; /* Pointer to next TCB in the TCB list */
struct os_tcb *OSTCBPrev; /* Pointer to previous TCB in the TCB list */
INT32U OSTCBDly; /* Nbr ticks to delay task or, timeout waiting for event */
INT8U OSTCBStat; /* Task status */
INT8U OSTCBStatPend; /* Task PEND status */
INT8U OSTCBPrio; /* Task priority (0 == highest) */
INT8U OSTCBX; /* Bit position in group corresponding to task priority */
INT8U OSTCBY; /* Index into ready table corresponding to task priority */
OS_PRIO OSTCBBitX; /* Bit mask to access bit position in ready table */
OS_PRIO OSTCBBitY; /* Bit mask to access bit position in ready group */
} OS_TCB;
3.如何用C语言建立一个任务控制块数组?
- uCOSII在初始化时按照配置文件所设定的任务数事先定义一批空白任务块(全局变量),这样当程序创建一个任务需要一个任务控制块时,只要拿一个空白块填上任务的属性即可。
-
OS_TCB OSTCBTbl [ OS_MAX_TASKS + OS_N_SYS_TASKS ]; /* Table of TCBs */
OS_CFG.H 文件中的宏常数 OS_MAX_TASKS —- 用于定义最大用户任务数。
UCOS_II.H 文件中的宏常数 OS_N_SYS_TASKS —- 用于约定系统任务数,其值固定为 2 。
4. 如何加快对建立的任务控制块数组的访问?
- 任务控制块创建为双向链表。
- 创建一个结构体指针数组OSTCBPrioTbl[ ]任务优先表 :OS_TCB* 数据类型的数组,以 优先级Prio为下标存放指向各个已使能任务控制块的指针,来加速 TCB 的访问。(注,理解:OSTCBPrioTbl【】数组存放的是结构体指针,该数组以优先级为索引,对应优先级的结构体指针存放的是对应优先级OSTCBTBL结构体的地址。ucosii通过该数组接口对TCB的访问)。
- 人们把正在占有CPU而处于运行状态的任务所属的控制块叫当前任务控制块。由于它是uCOS-II访问频率最高的控制块,所以专门定义了一个全局变量OS_TCB *OSTCBCur指向当前正在运行的任务的 TCB 。
5. 论述任务控制块空闲链表的初始化构建过程
- 系统在调用函数OSInit( )对uCOS-II系统进行初始化时,就先在RAM中建立一个OS_TCB结构类型的数组OSTCBTbl[ ](见上面3),然后把数组各个结构体元素链接成一个如下图的单向空任务链表,即相当于是一些空白的身份证。任务一旦建立,空任务控制块指针OSTCBFreeList指向的任务控制块便赋给了该任务,然后OSTCBFreeList指向链表中下一个空的任务控制块。一旦任务被删除,任务控制块就还给空任务链表。
void OSInit (void)
{
……
OS_InitTCBList(); /* Initialize the free list of OS_TCBs */
……
}
/*
*********************************************************************************************************
6. INITIALIZATION
7. INITIALIZE THE FREE LIST OF TASK CONTROL BLOCKS
8. - Description: This function is called by OSInit() to initialize the free list of OS_TCBs.
9. - Arguments : none
10. - Returns : none
*********************************************************************************************************
*/
static void OS_InitTCBList (void)
{
INT8U ix;
INT8U ix_next;
OS_TCB *ptcb1;
OS_TCB *ptcb2;
OS_MemClr((INT8U *)&OSTCBTbl[0], sizeof(OSTCBTbl)); /* Clear all the TCBs */
OS_MemClr((INT8U *)&OSTCBPrioTbl[0], sizeof(OSTCBPrioTbl)); /* Clear the priority table */
for (ix = 0u; ix < (OS_MAX_TASKS + OS_N_SYS_TASKS - 1u); ix++)
{ /* Init. list of free TCBs */
ix_next = ix + 1u;
ptcb1 = &OSTCBTbl[ix];
ptcb2 = &OSTCBTbl[ix_next];
ptcb1->OSTCBNext = ptcb2;
}
ptcb1 = &OSTCBTbl[ix];
ptcb1->OSTCBNext = (OS_TCB *)0; /* Last OS_TCB的Next指针指向NULL */
OSTCBList = (OS_TCB *)0; /* 任务块链表头指针设置为NULL */
OSTCBFreeList = &OSTCBTbl[0]; /* 空闲任务块链表头指针指向数组第一个元素 */
}
/*
*********************************************************************************************************
* CLEAR A SECTION OF MEMORY
*
* Description: This function is called by other uC/OS-II services to clear a contiguous block of RAM.
*
* Arguments : pdest is the start of the RAM to clear (i.e. write 0x00 to)
*
* size is the number of bytes to clear.
*
* Returns : none
*
* Notes : 1) This function is INTERNAL to uC/OS-II and your application should not call it.
* 2) Note that we can only clear up to 64K bytes of RAM. This is not an issue because none
* of the uses of this function gets close to this limit.
* 3) The clear is done one byte at a time since this will work on any processor irrespective
* of the alignment of the destination.
*********************************************************************************************************
*/
void OS_MemClr (INT8U *pdest,
INT16U size)
{
while (size > 0u) {
*pdest++ = (INT8U)0;
size--;
}
}
6. 解析在创建任务时对该任务控制块的初始化过程(三步走)
- 为被创建任务从空闲任务控制链表(OSTCBFreeList)中获取一个任务控制块
- 用任务的属性对任务控制块各个成员进行赋值
- 把这个任务控制块链入到任务控制块链表(OSTCBList),并在任务就绪表中登记(见9.登记)该任务
见9.登记 查表法,下面为移位法。
OSRdyGrp |= ptcb->OSTCBBitY; /* Make task ready to run */
OSRdyTbl[ptcb->OSTCBY] |= ptcb->OSTCBBitX;
/*
*********************************************************************************************************
- INITIALIZE TCB 详细代码
- - Description: This function is internal to uC/OS-II and is used to initialize a Task Control Block when
- a task is created (see OSTaskCreate() and OSTaskCreateExt()).
- - Arguments : prio is the priority of the task being created
- - ptos is a pointer to the task's top-of-stack assuming that the CPU registers
- have been placed on the stack. Note that the top-of-stack corresponds to a
- 'high' memory location is OS_STK_GROWTH is set to 1 and a 'low' memory
- location if OS_STK_GROWTH is set to 0. Note that stack growth is CPU
- specific.
- - pbos is a pointer to the bottom of stack. A NULL pointer is passed if called by
- 'OSTaskCreate()'.
- - id is the task's ID (0..65535)
- - stk_size is the size of the stack (in 'stack units'). If the stack units are INT8Us
- then, 'stk_size' contains the number of bytes for the stack. If the stack
- units are INT32Us then, the stack contains '4 * stk_size' bytes. The stack
- units are established by the #define constant OS_STK which is CPU
- specific. 'stk_size' is 0 if called by 'OSTaskCreate()'.
- - pext is a pointer to a user supplied memory area that is used to extend the task
- control block. This allows you to store the contents of floating-point
- registers, MMU registers or anything else you could find useful during a
- context switch. You can even assign a name to each task and store this name
- in this TCB extension. A NULL pointer is passed if called by OSTaskCreate().
- - opt options as passed to 'OSTaskCreateExt()' or,
- 0 if called from 'OSTaskCreate()'.
- - Returns : OS_ERR_NONE if the call was successful
- OS_ERR_TASK_NO_MORE_TCB if there are no more free TCBs to be allocated and thus, the task cannot
- be created.
- - Note : This function is INTERNAL to uC/OS-II and your application should not call it.
*********************************************************************************************************
*/
INT8U OS_TCBInit (INT8U prio,
OS_STK *ptos,
OS_STK *pbos,
INT16U id,
INT32U stk_size,
void *pext,
INT16U opt)
{
OS_TCB *ptcb;
OS_ENTER_CRITICAL();
ptcb = OSTCBFreeList; /* Get a free TCB from the free TCB list */
if (ptcb != (OS_TCB *)0)
{
OSTCBFreeList = ptcb->OSTCBNext; /* Update pointer to free TCB list */
OS_EXIT_CRITICAL();
ptcb->OSTCBStkPtr = ptos; /* Load Stack pointer in TCB */
ptcb->OSTCBPrio = prio; /* Load task priority into TCB */
ptcb->OSTCBStat = OS_STAT_RDY; /* Task is ready to run */
ptcb->OSTCBStatPend = OS_STAT_PEND_OK; /* Clear pend status */
ptcb->OSTCBDly = 0u; /* Task is not delayed */
/* Pre-compute X, Y */
ptcb->OSTCBY = (INT8U)(prio >> 3u);
ptcb->OSTCBX = (INT8U)(prio & 0x07u);
/* Pre-compute BitX and BitY */
ptcb->OSTCBBitY = (OS_PRIO)(1uL << ptcb->OSTCBY);
ptcb->OSTCBBitX = (OS_PRIO)(1uL << ptcb->OSTCBX);
OSTCBInitHook(ptcb);
OSTaskCreateHook(ptcb); /* Call user defined hook */
OS_ENTER_CRITICAL();
OSTCBPrioTbl[prio] = ptcb;
ptcb->OSTCBNext = OSTCBList; /* Link into TCB chain */
ptcb->OSTCBPrev = (OS_TCB *)0;
if (OSTCBList != (OS_TCB *)0) {
OSTCBList->OSTCBPrev = ptcb;
}
OSTCBList = ptcb;
OSRdyGrp |= ptcb->OSTCBBitY; /* Make task ready to run */
OSRdyTbl[ptcb->OSTCBY] |= ptcb->OSTCBBitX;
OSTaskCtr++; /* Increment the #tasks counter */
OS_EXIT_CRITICAL();
return (OS_ERR_NONE);
}
OS_EXIT_CRITICAL();
return (OS_ERR_TASK_NO_MORE_TCB);
}
7.就绪表OSRdyTbl[ ]和就绪组OSRdyGrp的用途是什么?论述他们之间的关系。
#define OS_RDY_TBL_SIZE ((OS_LOWEST_PRIO) / 8 + 1)
OS_EXT INT8U OSRdyGrp;
OS_EXT INT8U OSRdyTbl[OS_RDY_TBL_SIZE];
/* 就绪表OSRdyTbl[]数组的大小取决于OS_LOWEST_PRIO。任务数目较少时,可以减少OS_LOWEST_PRIO的值,以降低uCOS-IIdui数据存储的需求 */
- 就绪表OSRdyTbl[ ] :μCOS-II进行任务调度的依据就是任务就绪表。为了能够使系统清楚地知道,系统中哪些任务已经就绪,哪些还没有就绪,μCOS-II 在 RAM中设立了一个记录表,系统中的每个任务都在这个表中占据一 Bit 的位置,并用这个位置的状态(1或者0)来表示任务是否处于就绪状态,这个表就叫做任务就绪状态表,简称叫任务就绪表 —- 一个 INT8U 的数组 OSRdyTbl[ ] 充当,最多8个元素,可以标示64个任务。
- 就绪组OSRdyGrp:为加快访问任务就绪表的速度,系统定义了一个变量OSRdyGrp来表明就绪表每行中是否存在就绪任务。
- 就绪表与就绪组的关系:如图
8.如何根据任务的优先级来找到任务在就绪表中的位置呢?
- uCOS-II 最多管理64个任务,Prio值域:0 ~ 63。
-
prio.D[5:3] —对应 OSRdyGrp 元素的位号,对应 OSRdyTbl 的下标(任务组号 x )
-
prio.D[2:0] —对应 OSRdyTbl 元素的位号
备注:这就是 prio 的值和任务就绪表的映射关系。
9.就绪表的三大操作(登记、注销和查找)
-
登记
OSRdyGrp | = OSMapTbl[ prio >> 3 ];
OSRdyTbl[ prio >> 3 ] | = OSMapTbl[ prio & 0x07 ];其中,OSMapTbl[ ]是uCOS-II为加快运算速度定义的一个数组,以空间换时间。uCOS-II是一个实时系 统,在操作时间上它的所有操作都必须是常量,也就是说系统的任何操作都必须具有时间上的承诺,即时间确定性!
OSMapTbl[0] = 0000 0001B
OSMapTbl[1] = 0000 0010B
OSMapTbl[2] = 0000 0100B
OSMapTbl[3] = 0000 1000B
OSMapTbl[4] = 0001 0000B
OSMapTbl[5] = 0010 0000B
OSMapTbl[6] = 0100 0000B
OSMapTbl[7] = 1000 0000B -
注销
if( ( OSRdyTbl[ prio>>3 ] &= ~ OSMapTbl[ prio & 0x07 ] ) == 0 )
OSRdyGrp &= ~ OSMapTbl[ prio >> 3 ];
如果这个任务组的任务全为0了,则需要将OSRdyGrp 对应位清0。 -
查找最高优先级就绪任务
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]]);在 uC/OS-II 中预定义了一个 OSUnMapTbl[ ] 数组:
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,
… …
};
备注:该数组共计256个元素,(亦称之为《优先级判定表》)uCOS-II经常使用类似与就绪表形式的表来记录任务的某种状态,因此一定要通过7.8.9三个知识点熟悉这种表的结构和对这种表的操作!!
10. 解析任务调度过程(重点)
- uCOS-II任务调度思想:近似地每时每刻让优先级最高的就绪任务处于运行状态。
-
具体做法:在系统或用户任务调用系统函数及执行中断服务程序结束时调用调度器,以确定应该运行的任务并运行它。
-
调度器的主要工作
1、uCOS-II 中的任务切换由任务调度器来完成。uCOS-II 中有两个任务调度器:任务级的调度器OS_Sched( ) ,中断级的调度器OSIntCtxSw( )
2、任务调度器的两项主要工作:从任务就绪表中查找优先级最高的就绪任务 和 实现任务切换
3、调度器进行任务切换的两个工作步骤:获得待运行任务 TCB 的指针 和 断点数据切换 -
获得待运行任务 TCB 的指针
1、首先从任务就绪表中查得最高优先权就绪任务的“优先级”—OSPrioHighRdy
2、OSTCBHighRdy = OSTCBPrioTbl[ OSPrioHighRdy ]通过“优先级”为下标,获取任务优先表对应元素赋值给OSTCBHighRdy ,得到待运行任务TCB的指针OSTCBHighRdy 。
3、判断这个就绪任务是否为当前任务OSTCBCur,是则退出,否则进行任务切换。
void OS_Sched (void)
{
OS_ENTER_CRITICAL();
if (OSIntNesting == 0u) /* 不是中断服务程序调用调度器,因为中断中不能进行任务切换*/
{
if (OSLockNesting == 0u) /* 调度器未加锁 */
{
OS_SchedNew(); /* 获得了最高优先权就绪任务的“优先级”,存放在全局系统变量OSPrioHighRdy*/
OSTCBHighRdy = OSTCBPrioTbl[OSPrioHighRdy]; /*获得了待运行任务TCB的指针 */
if (OSPrioHighRdy != OSPrioCur) /* 如果获取的最高优先级任务就是当前任务,则不需任务切换 */
{
OSCtxSwCtr++; /* 统计任务切换次数 */
OS_TASK_SW(); /* 重点来了,启动任务切换 */
}
}
}
OS_EXIT_CRITICAL();
}
static void OS_SchedNew (void)
{
INT8U y;
y = OSUnMapTbl[OSRdyGrp];
OSPrioHighRdy = (INT8U)((y << 3u) + OSUnMapTbl[OSRdyTbl[y]]);
}
- 断点数据切换—任务切换宏 OS_TASK_SW( )
其主要工作:完成当前任务到待运行任务的切换。
几个概念:断点 — 当前任务被终止运行的位置。
断点数据 — 断点处 CPU 的 PC、SP、PSW 以及通用寄存器(R0~R12)的当前数据。
任务切换本质:是通过把待运行程序的地址赋予程序计数器PC来实现程序的切换的
任务切换的核心工作是:任务堆栈指针的切换
任务切换宏 OS_TASK_SW( ) 依次完成的7项工作:
1、被中止Task的断点指针(PC)入栈保护
2、被中止Task标志和通用寄存器入栈保护
3、被中止Task堆栈指针 SP →该TCB->OSTCBStkPtr
4、获得待运行任务TCB
5、获得待运行任务 TCB->OSTCBStkPtr
6、恢复待运行任务CPU现场(不包含PC)
7、切换CPU.PC,使CPU获得待运行Task的PC(上一次被调度器终止运行时保留在任务堆栈中的),使CPU接续运行待运行Task断点。
备注:Step1 — 中断方式保护PC入栈;Step7 — IRET指令恢复PC;汇编语言编程OS_TASK_SW( ) — 直接操作寄存器。
/*********************************************************************************************************
; PERFORM A CONTEXT SWITCH (From task level)
; void OSCtxSw(void)
;
; Note(s): 1) 此函数为手动任务切换函数
;
; 2) The stack frame of the task to suspend looks as follows:
;
; SP -> OFFSET of task to suspend (Low memory)
; PSW of task to suspend (High memory)
;
; 3) The stack frame of the task to resume looks as follows:
;
; OSTCBHighRdy->OSTCBStkPtr --> EDI (Low memory)
; ESI
; EBP
; ESP
; EBX
; EDX
; ECX
; EAX
; Flags to load in PSW
; OFFSET of task code address (High memory)
;*********************************************************************************************************/
void OSCtxSw(void)
{
_asm{
lea eax, nextstart ;任务切换回来后从nextstart开始
push eax
pushfd ;2、被中止Task标志寄存器入栈保护FLAGS即PSW
pushad ;2、被中止Task通用寄存器入栈保护保存EAX -- EDI
mov ebx, [OSTCBCur]
mov [ebx], esp ;3、被中止Task堆栈指针SP保存到该TCB->OSTCBStkPtr
}
OSTaskSwHook();
OSTCBCur = OSTCBHighRdy; /* 4、最高优先级就绪任务切换为当前任务,获得待运行任务TCB */
OSPrioCur = OSPrioHighRdy;
_asm{
mov ebx, [OSTCBCur]
mov esp, [ebx] ;5、通过获得的待任务控制块,得到它的任务堆栈指针TCB->OSTCBStkPtr
popad ;恢复所有通用寄存器,共8个
popfd ;恢复标志寄存器 6、恢复待运行任务CPU现场(不包含PC)
ret ; 7、切换CPU.PC,使CPU获得待运行Task的PC(上一次被调度器终止运行时保留在任务堆栈中的),使CPU接续运行待运行Task断点。
}
nextstart: /* 任务切换回来的运行地址 */
return;
}
11. 任务的创建
- 创建任务的实质:是创建一个任务控制块TCB,并通过TCB把Code和Stack关联起来形成一个完整的任务Task。
- 创建任务时要注意: 核算Task.Stack任务栈的大小 和 核定Task.Prio任务优先级必须保证不重复。
INT8U OSTaskCreate (void (*task)(void *p_arg),
void *p_arg,
OS_STK *ptos,
INT8U prio)
{
OS_STK *psp;
INT8U err;
OS_ENTER_CRITICAL();
if (OSIntNesting > 0u) /* 确保不在中断中创建任务 */
{
OS_EXIT_CRITICAL();
return (OS_ERR_TASK_CREATE_ISR);
}
if (OSTCBPrioTbl[prio] == (OS_TCB *)0) /* 确保这个优先级的任务并不存在,即这个优先级未被使用 */
{
OSTCBPrioTbl[prio] = (OS_TCB *)1);/* 在这个任务被创建好之前,保留这个优先级防止其他在做相同的动作 */
/* 如果某个优先级是空闲不存在的,uCOS-II通过放置一个非空指针(OS_TCB *)1在OSTCBPrioTbl[prio]中来保留该优先级。
这就使得OSTaskCreat在创建任务设置数据结构的其他部分时可重新允许中断OS_EXIT_CRITICAL(),因此这个优先级已被占用,
其他任务即使并行创建也无法在使用该优先级 */
OS_EXIT_CRITICAL();
psp = OSTaskStkInit(task, p_arg, ptos, 0u); /* 初始化任务堆栈 */
err = OS_TCBInit(prio, psp, (OS_STK *)0, 0u, 0u, (void *)0, 0u);/* 获得并初始化TCB,并在就绪表中登记该任务 */
if (err == OS_ERR_NONE)
{
if (OSRunning == OS_TRUE) /* 如果启用了多任务,即操作系统已经起来,则寻找最高优先级任务 */
{
OS_Sched(); /* 任务调度 */
}
}
else
{
OS_ENTER_CRITICAL();
OSTCBPrioTbl[prio] = (OS_TCB *)0;/* 如果TCB获取失败,放弃任务创建,将该任务优先表清NULL留给其他任务使用 */
OS_EXIT_CRITICAL();
}
return (err);
}
OS_EXIT_CRITICAL();
return (OS_ERR_PRIO_EXIST);
}
-
创建任务的一般方法:
1、Task 可在主函数 main( ) 中操作系统调度启动OSStart( )前创建。
2、也可在Task中创建其他的Task,应用中习惯使用此结构。
uCOS-II要求:在OSStart( )前,必须至少创建一个用户Task。重要理解(高优先级任务内核抢占):if(OSRunning == Ture) { OSSched( ); }
如果OSTaskCreate任务创建是在某个任务的执行过程中被调用(此时的OSRunning一定已经为Ture, 即多任务已经启动),任务调度函数就会调用,用来判断是否这个新建的任务比当前正在执行的任务优先 级要高,如果更高则内核会进行一次任务切换。如果在多任务系统起来之前创建新任务,此时的OSRunning一定为False,故不会启用任务切换。
void main( )
{
… … /* 主要是系统硬件初始化 */
OSInit( ); /* uC/OS-II 初始化 */
… … /* 创建消息机制 */
OSTaskCreate(Task_A,……); /* 创建任务A */
OSStart( ); /* 启动多任务调动 */
}
void Task_A(void * pdata) /* 任务A */
{
…… /* 安装并启动 uC/OS-II 系统时钟 */
OSStatInit( ); /* 初始化统计任务(如果需要的话)*/
…… /* 在此处可以创建其他任务 */
For ( ; ; ) /* Task_A 任务体 */
{
……
}
}
12. uCOS-II的初始化
在使用uCOS-II之前,必须对其运行环境初始化OSInit( )。
void main (void)
{
......
OSInit(); /* Initialize uCOS-II */
...... /* Craete Task */
OSStart(); /* Start Multitasking */
}
OSInit( ) 的主要工作:
1、对 uCOS-II 的所有全局变量和数据结构初始化。
2、创建空间任务 OSTaskIdel( )
3、根据系统设置( OS_TASK_STAT_EN = 1 / 0 ) 启动统计任务。
void OSInit (void)
{
OS_InitMisc(); /* 1、初始化各种全局变量 */
OS_InitRdyList(); /* 1、初始化就绪表数据结构 */
OS_InitTCBList(); /* 1、初始化空闲链表数据结构 */
OS_InitTaskIdle(); /* 2、创建空间任务OSTaskIdel() */
#if OS_TASK_STAT_EN > 0u
OS_InitTaskStat(); /* 3、创建统计任务OSTaskStat() */
#endif
}
13. uCOS-II的启动
*********************************************************************************************************
* START MULTITASKING启动多任务
*
* Description: 这个函数用来启动多任务程序,该程序令uCOS-II开始接管由用户创建的任务。但在调用OSStart()前,
* 用户必须调用OSInit()并且必须创建至少一个用户任务。
*
* Note : OSStartHighRdy() MUST:
* a) 调用OSTaskSwHook()
* b) 设置多任务启动标志OSRunning为OS_TRUE.
* c) 加载OSTCBHighRdy任务的上下文
* d) 执行任务
*********************************************************************************************************
*/
void OSStart (void)
{
if (OSRunning == OS_FALSE) {
OS_SchedNew(); /* 查询最高优先级任务的优先级数 */
OSPrioCur = OSPrioHighRdy;
OSTCBHighRdy = OSTCBPrioTbl[OSPrioHighRdy]; /* 获取最高优先级就绪任务指针,准备运行 */
OSTCBCur = OSTCBHighRdy;
OSStartHighRdy(); /* 指定目标特定代码来启动最高优先级就绪任务 */
}
}
/**********************************************************************************************************
; START MULTITASKING
; void OSStartHighRdy(void)
;
; The stack frame is assumed to look as follows:
;
; OSTCBHighRdy->OSTCBStkPtr --> EDI (Low memory)
; ESI
; EBP
; ESP
; EBX
; EDX
; ECX
; EAX
; Flags to load in PSW
; OFFSET of task code address
; 4 empty byte
; OFFSET of 'pdata' (High memory)
;
;
; Note : OSStartHighRdy() MUST:
; a) Call OSTaskSwHook() then,
; b) 设置多任务启动标志OSRunning为RUE,
; c) 切换到最高优先级任务
;*********************************************************************************************************/
void OSStartHighRdy(void)
{
OSTaskSwHook();
OSRunning = TRUE;
_asm{
mov ebx, [OSTCBCur] ;OSTCBCur结构的第一个参数就是esp
mov esp, [ebx] ;恢复堆栈
popad ;恢复所有通用寄存器,共8个
popfd ;恢复标志寄存器
ret ;ret 指令相当于pop eip 但保护模式下不容许使用eip
;永远都不返回
}
}
14. 总结
- Task 由 TCB、Task_Stack、Task_Code 三部分组成。
- uCOS-II使用 TCB 对任务进行控制。
- Task_Stack 用于任务切换时私有现场保护。
- uCOS-II 按任务就绪表和任务优先级对任务进行调度。
- uCOS-II 每调用一次API函数(即OS开头的函数)和中断返回都会进行一次任务调度。
- 任务切换的核心工作是任务堆栈指针的切换。
- 任务调度器代码的设计,使得它的运行时间与系统中的任务数无关,从而使它满足了实时系统的要求。 任务的优先级(prio)也是任务的标识。
- 应用程序首先应该调用OSInit( )函数对全局变量和数据结构进行初始化,以建立uCOS-II的运行环境。
- 应用程序是通过调用函数OSStart( )开始进入多任务管理的,但在调用OSStart( ) 前必须至少创建了一个任务。