1、轮询、中断、多任务对比
2、什么是任务
如果您学过linux,那么任务可以理解为线程。在代码中的体现就是线程函数,一个函数中有个无限循环函数,并且永不返回。例如:
void Task (void *arg)
{
while(1)
{
。。。
}
}
3、任务栈
3.1 栈
栈stack是一块程序运行时用来存储临时变量的内存RAM空间。栈一般静态分配,并且后进先出,栈的生命周期从程序的起始直到程序结束。一个函数返回,其用到的栈空间就被释放给后续函数使用。
不带操作系统的裸机中,可以视为只有一个任务,任务栈也只有一个,可以在启动文件中看到相关代码:
Stack_Size EQU 0x400
AREA STACK, NOINIT, READWRITE, ALIGN=3
Stack_Mem SPACE Stack_Size
Stack栈的大小为:0x400(1024Byte),一个函数中定义的所有局部变量,加起来不能大于工程的栈大小,否则程序肯定会出现内存溢出,导致复位。
3.2 堆
与栈相类似的还有堆空间,当工程中使用了malloc动态分配内存空间时,这时分配的空间就为堆的空间,同样在启动代码中也能看到堆空间的分配的代码:
Heap_Size EQU 0x00000200
AREA HEAP, NOINIT, READWRITE, ALIGN=3
__heap_base
Heap_Mem SPACE Heap_Size
__heap_limit
Heap堆的大小为:0x200(512Byte)
3.3 多任务中栈分配
在操作系统,如ucosIII中,每个任务都需要分配堆栈(如果不需要动态分配内存,可以不分配堆)。
栈空间的分配其实就是,创建一个数组,即连续的内存分配,例如:
__align(8) CPU_STK STkTask1[512];
宏CPU_STK其实就是 unsigned int ,源码如下
typedef CPU_INT32U CPU_STK;
typedef unsigned int CPU_INT32U;
4、任务控制块TCB
任务函数写好后,uCOSIII系统如何调度我们写的任务函数?这就需要通过任务控制块TCB来让uCOSIII识别、调度任务。
任务控制块TCB相当于任务的身份证,里面存有任务的所有信息,比如任务的堆栈,任务名称,任务的形参等。
任务控制块TCB源码如下:
最主要的有两个任务函数指针 (CPU_STK *StkPtr)和任务栈大小(CPU_STK_SIZE StkSize;)
struct os_tcb {
CPU_STK *StkPtr; /* Pointer to current top of stack */
void *ExtPtr; /* Pointer to user definable data for TCB extension */
CPU_STK *StkLimitPtr; /* Pointer used to set stack 'watermark' limit */
#if (OS_CFG_DBG_EN == DEF_ENABLED)
CPU_CHAR *NamePtr; /* Pointer to task name */
#endif
OS_TCB *NextPtr; /* Pointer to next TCB in the TCB list */
OS_TCB *PrevPtr; /* Pointer to previous TCB in the TCB list */
#if (OS_CFG_TICK_EN == DEF_ENABLED)
OS_TCB *TickNextPtr;
OS_TCB *TickPrevPtr;
#endif
#if ((OS_CFG_DBG_EN == DEF_ENABLED) || (OS_CFG_STAT_TASK_STK_CHK_EN == DEF_ENABLED) || (OS_CFG_TASK_STK_REDZONE_EN == DEF_ENABLED))
CPU_STK *StkBasePtr; /* Pointer to base address of stack */
#endif
#if defined(OS_CFG_TLS_TBL_SIZE) && (OS_CFG_TLS_TBL_SIZE > 0u)
OS_TLS TLS_Tbl[OS_CFG_TLS_TBL_SIZE];
#endif
#if (OS_CFG_DBG_EN == DEF_ENABLED)
OS_TASK_PTR TaskEntryAddr; /* Pointer to task entry point address */
void *TaskEntryArg; /* Argument passed to task when it was created */
#endif
OS_TCB *PendNextPtr; /* Pointer to next TCB in pend list. */
OS_TCB *PendPrevPtr; /* Pointer to previous TCB in pend list. */
OS_PEND_OBJ *PendObjPtr; /* Pointer to object pended on. */
OS_STATE PendOn; /* Indicates what task is pending on */
OS_STATUS PendStatus; /* Pend status */
OS_STATE TaskState; /* See OS_TASK_STATE_xxx */
OS_PRIO Prio; /* Task priority (0 == highest) */
...
5、任务创建函数
任务控制块TCB就是一个结构体,需要封装了任务信息,uCOS提供一个函数将任务信息填充到TCP中,并将它注册到uCOS操作系统中去。让uCOS知道它的存在,并调度它。这个函数在uCOS中就是OSTaskCreate,函数原型如下:
void OSTaskCreate (OS_TCB *p_tcb, /* 任务控制块地址 */
CPU_CHAR *p_name, /* 任务名 */
OS_TASK_PTR p_task, /* 启动任务函数地址 */
void *p_arg, /* 传递给任务的参数 */
OS_PRIO prio, /* 任务优先级 */
CPU_STK *p_stk_base, /* 堆栈基地址 */
CPU_STK_SIZE stk_limit, /* 堆栈监测区 */
CPU_STK_SIZE stk_size, /* 堆栈空间大小 */
OS_MSG_QTY q_size, /* 本任务支持接受的最大消息数 */
OS_TICK time_quanta, /* 设置时间片 */
void *p_ext, /* 堆栈空间大小 */
OS_OPT opt, /* 选择设置 */
OS_ERR *p_err) /* 错误返回值 */
6、任务就绪列表
所有任务都在一个列表中,供系统切换,这个列表叫做任务就绪列表:OSRdyList
任务就绪列表的定义:OS_RDY_LIST OSRdyList[OS_CFG_PRIO_MAX]
节点个数最大64个,也就是说,最多可以创建64个任务:#define OS_CFG_PRIO_MAX 64u
节点类型 OS_RDY_LIST 其实就是组成双向列表的结构体,原代码如下
typedef struct os_rdy_list OS_RDY_LIST;
struct os_rdy_list{
OS_TCB *HeadPtr; /* Pointer to task that will run at selected priority */
OS_TCB *TailPtr; /* Pointer to last task at selected priority */
};
7、任务初始化OSInit
系统初始化一般都是在硬件初始化完后再执行。代码如下,系统初始化函数OSInit在时钟初始化、外部设备初初始化以后运行
int main(void)
{
OS_ERR err;
/* HAL库,MPU,Cache,时钟等系统初始化 */
System_Init();
/*初始化GPIO引脚*/
MX_GPIO_Init();
/* 初始化uC/OS-III 内核 */
OSInit(&err);
。。。
}
OSInit主要完成的工作:初始化全局变量、初始化任务就绪列表。
初始化的全局变量包括:
OSRunning 系统的运行状态,默认是停止状态OS_STATE_OS_STOPPED
OSTCBCurPtr:当前正在运行的任务TCB指针
OSTCBHighRdyPtr:任务就绪列表中,优先级最高的任务TCB指针
8、任务启动OSStart
先装载最高优先级任务到当前任务指针,然后执行任务切换函数:OSStartHighRdy
void OSStart (OS_ERR *p_err)
{
OS_OBJ_QTY kernel_task_cnt;
kernel_task_cnt = 0u;
if (OSRunning == OS_STATE_OS_STOPPED) {
OSPrioHighRdy = OS_PrioGetHighest();
OSPrioCur = OSPrioHighRdy;
OSTCBHighRdyPtr = OSRdyList[OSPrioHighRdy].HeadPtr;
OSTCBCurPtr = OSTCBHighRdyPtr;
OSRunning = OS_STATE_OS_RUNNING;
OSStartHighRdy();
*p_err = OS_ERR_FATAL_RETURN;
} else {
*p_err = OS_ERR_OS_RUNNING;
}
}
9、任务切换OSStartHighRdy
任务切换函数OSStartHighRdy是汇编写的,在os_cpu_a.asm中,部分源码如下,本人不懂汇编,不在展开解释
主要完成的工作是:
保存上下文(将当前任务的各个CPU寄存器中值保存到任务栈中)
切换上下文(将下个任务栈中的内容加到CPU寄存器中)
OSStartHighRdy
CPSID I ; Prevent interruption during context switch
MOV32 R0, NVIC_SYSPRI14 ; Set the PendSV exception priority
MOV32 R1, NVIC_PENDSV_PRI
STRB R1, [R0]
。。。
10、任务调度
在每个任务函数的循环中会需要执行一个函数: OSTimeDly;
OSTimeDly中会调用OSSched;
OSSched会切换下一个任务