UCOSIII

您所在的位置:网站首页 ucosii任务切换时间 UCOSIII

UCOSIII

2024-07-08 02:57| 来源: 网络整理| 查看: 265

在上一章节中,任务体内的延时使用的是软件延时,即还是让CPU 空等来达到延时的效果。使用RTOS 的很大优势就是榨干CPU 的性能,永远不能让它闲着,任务如果需要延时也就不能再让CPU 空等来实现延时的效果。

1、什么是阻塞延时?

阻塞延时就是当任务需要延时的时候,任务会放弃CPU 的使用权,CPU 可以去干其他的事情,当任务延时时间到,重新获取 CPU 使用权,任务继续运行,这样就充分地利用了CPU 的资源,而不是干等着。

2、什么是空闲任务?

当任务需要延时,进入阻塞状态,那 CPU 又去干什么事情了?如果没有其他任务可以运行,RTOS 通常会为 CPU 创建一个空闲任务,这个时候 CPU 就运行空闲任务。在 μC/OS-III 中,空闲任务是系统在初始化的时候创建的优先级最低的任务,空闲任务主体很简单,只是对一个全局变量进行计数。鉴于空闲任务的这种特性,在实际应用中,当系统进入空闲任务的时候,可在空闲任务中让单片机进入休眠或者低功耗等操作。

μC/OS中的两个系统任务分别是空闲任务和统计任务。

3、实现空闲任务 1、定义空闲任务栈

空闲任务栈在 os_cfg_app.c(os_cfg_app.c 第一次使用需要自行在文件夹 μC/OS-IIISource 中新建并添加到工程的μC/OS-III Source 组)文件中定义。

CPU_STK OSCfg_IdleTaskStk [OS_CFG_IDLE_TASK_STK_SIZE];

声明在 os_cfg_app.h 中。

#ifndef OS_CFG_APP_H #define OS_CFG_APP_H /* ************************************************************************************************************************ * 常量 ************************************************************************************************************************ */ /* 空闲任务堆栈大小 */ #define OS_CFG_IDLE_TASK_STK_SIZE 128u #endif /* OS_CFG_APP_H */

空闲任务的栈是一个定义好的数组, 大小由OS_CFG_IDLE_TASK_STK_SIZE 这个宏来控制。OS_CFG_IDLE_TASK_STK_SIZE 在os_cfg_app.h 这个头文件定义,大小为128字节。

这里有与普通任务不一样的一点是 空闲任务的栈的起始地址和大小均被定义成一个常量,不能被修改。 在 os_cfg_app.c 文件中进行定义。

/* ************************************************************************************************************************ * CONSTANTS ************************************************************************************************************************ */ /* 空闲任务堆栈起始地址 */ CPU_STK * const OSCfg_IdleTaskStkBasePtr = (CPU_STK *)&OSCfg_IdleTaskStk[0]; /* 空闲任务堆栈大小 */ CPU_STK_SIZE const OSCfg_IdleTaskStkSize = (CPU_STK_SIZE)OS_CFG_IDLE_TASK_STK_SIZE;

此外,变量 OSCfg_IdleTaskStkBasePtr 和 OSCfg_IdleTaskStkSize 同时还在 os.h 中声明,这样就具有全局属性,可以在其他文件里面被使用。

/* ************************************************************************************************************************ ************************************************************************************************************************ * 外部声明 ************************************************************************************************************************ ************************************************************************************************************************ */ /* 空闲任务堆栈起始地址 */ extern CPU_STK * const OSCfg_IdleTaskStkBasePtr; /* 空闲任务堆栈大小 */ extern CPU_STK_SIZE const OSCfg_IdleTaskStkSize; 2、定义空闲任务函数

空闲任务正如其名,空闲,任务体里面只是对全局变量 OSIdleTaskCtr ++ 操作。 空闲任务函数定义在 os_core.c 文件中。

/* 空闲任务 */ void OS_IdleTask (void *p_arg) { /* 自己给自己赋值是为了防止编译器警告 */ p_arg = p_arg; /* 空闲任务什么都不做,只对全局变量OSIdleTaskCtr ++ 操作 */ for(;;) { OSIdleTaskCtr++; } }

全局变量 OSIdleTaskCtr 称为空闲任务计数变量。在 os.h 中定义。

OS_EXT OS_IDLE_CTR OSIdleTaskCtr;//空闲任务计数变量

其中的 OS_IDLE_CTR 是在 os_type.h 中重新定义的数据类型。

typedef CPU_INT32U OS_IDLE_CTR; 3、定义空闲任务控制块

在这里插入图片描述

OS_EXT OS_TCB OSIdleTaskTCB;

需要特别注意的是:这里的 OS_TCB 中多了一个成员 TaskDelayTicks,它的作用在后面的 实现阻塞延时 中会被提到。

typedef struct os_tcb OS_TCB; struct os_tcb { CPU_STK *StkPtr; CPU_STK_SIZE StkSize; /* 任务延时周期个数 */ OS_TICK TaskDelayTicks; };

OS_TICK是在os_type.h中重定义的变量。

typedef CPU_INT32U OS_TICK; 4、空闲任务初始化 – 包括空闲任务创建函数

该函数在 os_core.c 中定义。

/* 空闲任务初始化 */ void OS_IdleTaskInit(OS_ERR *p_err) { /* 初始化空闲任务计数器 ,我们知道,这个是预先在os.h 中定义好的全局变量。初始化时让其初值为 0 */ OSIdleTaskCtr = (OS_IDLE_CTR)0; /* 创建空闲任务 -- 把栈,TCB,任务函数联系在一起 */ OSTaskCreate( (OS_TCB *)&OSIdleTaskTCB, // 空闲任务控制块 (OS_TASK_PTR )OS_IdleTask, // 空闲任务主体 (void *)0, // 形参设置为0,即 NULL (CPU_STK *)OSCfg_IdleTaskStkBasePtr, // 空闲任务堆栈的基地址 (CPU_STK_SIZE)OSCfg_IdleTaskStkSize, // 空闲任务堆栈的大小 (OS_ERR *)p_err ); // 错误的返回码 } 5、在 OSInit 函数中调用空闲任务初始化函数 OS_IdleTaskInit

在前面,普通任务的任务创建函数是在 main 函数中被调用的。这里是 OSInit 中被调用。这么做的目的是表明在系统还没有启动之前空闲任务就已经创建好。

OSInit 函数也在 os_core.c 中定义。

/* RTOS初始化 ** 初始化全局变量 */ void OSInit (OS_ERR *p_err) { /* 配置OS初始状态为停止态 */ OSRunning = OS_STATE_OS_STOPPED; /* 初始化两个全局TCB,这两个TCB用于任务切换 */ OSTCBCurPtr = (OS_TCB *)0; OSTCBHighRdyPtr = (OS_TCB *)0; /* 初始化就绪列表 */ OS_RdyListInit(); /* 初始化空闲任务 */ OS_IdleTaskInit(p_err); if (*p_err != OS_ERR_NONE) { return; } } 6、实现阻塞延时

阻塞延时的阻塞是指任务调用该延时函数后,任务会被剥离 CPU 使用权,然后进入阻塞状态,直到延时结束,任务重新获取 CPU 使用权才可以继续运行。在任务阻塞的这段时间,CPU 可以去执行其他的任务,如果其他的任务也在延时状态,那么 CPU 就将运行空闲任务。

阻塞延时函数在 os_time.c 中定义并实现。

/* 阻塞延时 */ void OSTimeDly(OS_TICK dly) { /* 设置延时时间 */ OSTCBCurPtr->TaskDelayTicks = dly; /* 进行任务调度 */ OSSched(); }

TaskDelayTicks 是任务控制块的一个成员,用于记录任务需要延时的时间,单位为 SysTick 的中断周期。比如当SysTick 的中断周期为 10 ms,调用 OSTimeDly(2) 则完成 2*10ms 的延时。

函数 OSSched() 需要重新实现,在 os_core.c 中。

/* 任务切换,实际就是触发PendSV异常,然后在PendSV异常中进行上下文切换 */ void OSSched(void) { /* 如果当前任务是空闲任务,那么就去尝试执行任务1或者任务2,看看他们的延时时间是否结束 如果任务的延时时间均没有到期,那就返回继续执行空闲任务 */ if( OSTCBCurPtr == &OSIdleTaskTCB ) { if(OSRdyList[0].HeadPtr->TaskDelayTicks == 0) { OSTCBHighRdyPtr = OSRdyList[0].HeadPtr; } else if(OSRdyList[1].HeadPtr->TaskDelayTicks == 0) { OSTCBHighRdyPtr = OSRdyList[1].HeadPtr; } else { return; /* 任务延时均没有到期则返回,继续执行空闲任务 */ } } else { /*如果是task1或者task2的话,检查下另外一个任务,如果另外的任务不在延时中,就切换到该任务 否则,判断下当前任务是否应该进入延时状态,如果是的话,就切换到空闲任务。否则就不进行任何切换 */ if(OSTCBCurPtr == OSRdyList[0].HeadPtr) { if(OSRdyList[1].HeadPtr->TaskDelayTicks == 0) { OSTCBHighRdyPtr = OSRdyList[1].HeadPtr; } else if(OSTCBCurPtr->TaskDelayTicks != 0) { OSTCBHighRdyPtr = &OSIdleTaskTCB; } else { return; /* 返回,不进行切换,因为任务一结束延时等待状态 */ } } else if(OSTCBCurPtr == OSRdyList[1].HeadPtr) { if(OSRdyList[0].HeadPtr->TaskDelayTicks == 0) { OSTCBHighRdyPtr = OSRdyList[0].HeadPtr; } else if(OSTCBCurPtr->TaskDelayTicks != 0) { OSTCBHighRdyPtr = &OSIdleTaskTCB; } else { return; /* 返回,不进行切换,因为任务二结束延时等待状态 */ } } } /* 任务切换 */ OS_TASK_SW(); }

主要操作时通过当前控制块的指针 OSTCBCurPtr (指向当前正在运行的一个任务)和最高优先级控制块的指针 OSTCBHighRdyPtr (指向下一个要运行的任务)来进行控制和判断的。

画图来理解下上述代码。 在这里插入图片描述

7、编写 main 函数 int main(void) { OS_ERR err; /* 关闭中断 */ CPU_IntDis(); /* 配置SysTick 10ms 中断一次 */ OS_CPU_SysTickInit (10); /* 初始化相关的全局变量 */ OSInit(&err); /* 创建任务 */ OSTaskCreate ((OS_TCB*) &Task1TCB, (OS_TASK_PTR ) Task1, (void *) 0, (CPU_STK*) &Task1Stk[0], (CPU_STK_SIZE) TASK1_STK_SIZE, (OS_ERR *) &err); OSTaskCreate ((OS_TCB*) &Task2TCB, (OS_TASK_PTR ) Task2, (void *) 0, (CPU_STK*) &Task2Stk[0], (CPU_STK_SIZE) TASK2_STK_SIZE, (OS_ERR *) &err); /* 将任务加入到就绪列表 */ OSRdyList[0].HeadPtr = &Task1TCB; OSRdyList[1].HeadPtr = &Task2TCB; /* 启动OS,将不再返回 */ OSStart(&err); }

需要注意:空闲任务初始化函数在OSInint 中调用,在系统启动之前创建好空闲任务。

8、修改 任务一 和 任务二 函数 /* 任务1 */ void Task1( void *p_arg ) { for( ;; ) { flag1 = 1; OSTimeDly(2); flag1 = 0; OSTimeDly(2); } } /* 任务2 */ void Task2( void *p_arg ) { for( ;; ) { flag2 = 1; OSTimeDly(2); flag2 = 0; OSTimeDly(2); } } 9、这一切最为关键的主线其实还是 时基 SysTick

在 SysTick 中断服务函数中

/* SysTick 中断服务函数 */ void SysTick_Handler(void) { OSTimeTick(); }

而 OSTimeTick();

void OSTimeTick (void) { unsigned int i; /* 扫描就绪列表中所有任务的TaskDelayTicks,如果不为0,则减1 */ for(i=0; iTaskDelayTicks > 0) { OSRdyList[i].HeadPtr->TaskDelayTicks --; } } /* 任务调度 */ OSSched(); }

需要注意这个任务调度是非常有必要的,如果不进行任务调度,执行完 for 循环之后就结束了,直到发生下一次的SysTick中断,再来执行 for 循环,以此往复,但是就是不切换回到任务。所以说这个任务调度函数 OSSched() 是非常重要的。

总结

为了方便下次看到能迅速反应过来,我把自己理解的程序运行流程再详细描述如下。

1、程序总是从main函数开始执行,在main函数中配置了 SysTick 以及初始化了 OSInit(&err);以后,启动OS,程序将不再返回。(注意:当 SysTick 初始化完成之后,计时就开始了,只不过在程序一开始的时候 SysTick 中断被关闭了,在 系统启动 OSStart() 函数里面的 OSStartHighRdy() 中才被重新开启。)

2、程序首先执行任务一,当执行完 flag1 = 1;开始执行 OSTimeDly(2); 在这个程序中设置了任务一的延时时间以及为了实现阻塞延时做了任务切换到了任务二,然后任务二开始执行,任务一失去 CPU ,同样的, 当任务二执行完 flag2 = 1;开始执行 OSTimeDly(2); 在这个程序中设置了任务二的延时时间以及为了实现阻塞延时做了任务切换到了空闲任务(因为任务一仍然还处在阻塞状态),随着第一次 SysTick 中断产生(10ms时间到),系统执行中断服务程序 OSTimeTick,将每个任务的 TaskDelayTicks 减去 1 ,之后,通过任务调度函数 OSSched() 才切换到空闲任务(因为此时任务一和任务二依然处于阻塞状态)。当第二次 SysTick 中断产生(又一个10ms时间到),系统再次执行中断服务程序 OSTimeTick,将每个任务的 TaskDelayTicks 再减去 1 ,之后通过任务切换发现,任务一的TaskDelayTicks等于0(其实任务二的 TaskDelayTicks 也等于 0 了,但是在程序中的 if 条件语句中优先处理任务一了,通过颠倒顺序,仿真结果验证了,我在最后贴图。)之后,任务一再次往下执行,执行到 flag1 = 0 后,开始执行 OSTimeDly(2); 再次进入延时阻塞状态,才切换到任务二 …

好了,程序过程描述至此。

仿真验证疑问 if( OSTCBCurPtr == &OSIdleTaskTCB ) { if(OSRdyList[0].HeadPtr->TaskDelayTicks == 0) { OSTCBHighRdyPtr = OSRdyList[0].HeadPtr; } else if(OSRdyList[1].HeadPtr->TaskDelayTicks == 0) { OSTCBHighRdyPtr = OSRdyList[1].HeadPtr; } else { return; /* 任务延时均没有到期则返回,继续执行空闲任务 */ } }

在这里插入图片描述 调整顺序

if( OSTCBCurPtr == &OSIdleTaskTCB ) { if(OSRdyList[1].HeadPtr->TaskDelayTicks == 0) { OSTCBHighRdyPtr = OSRdyList[1].HeadPtr; } else if(OSRdyList[0].HeadPtr->TaskDelayTicks == 0) { OSTCBHighRdyPtr = OSRdyList[0].HeadPtr; } else { return; /* 任务延时均没有到期则返回,继续执行空闲任务 */ } }

在这里插入图片描述 需要注意的是,

1、从宏观上来看,任务一和任务二看似是同时运行的,但是实际上任务间还是有先后之分的。

2、在实际使用 μC/OS 时,我们并不会这样使用延时阻塞。我们会插入一个 时基列表 来进行维护。



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3