特别说明:
1. 本教程是安富莱电子原创。
2. 安富莱STM32F407开发板资料已经全部开源,开源地址:地址链接
3. 当前共配套300多个实例,4套用户手册。
第11章
uCOS-III内核函数分析(下)
本期教程开始分析μCOS-III的内核函数,源码的分析采用先对源码进行注释,然后讲解函数实现的功能和相关的原理分析,最后是举一个例子(如果这个函数是供外部函数调用的)。内核函数很重要,是学习任务管理,任务间通信机制的基础。希望初学的同学认真学习,这部分应该算是μCOS-III的核心代码。
11.1
系统配置文件
11.2
源码文件
11.3
μCOS-III初始化
11.4
μCOS-III启动
11.5
获取系统版本
11.6
空闲任务
11.7
临界段
11.8
安全关键IEC61508
11.9
任务切换
11.10
调度锁
11.11
Round-Robin调度
11.12
总结
11.10
调度锁
给调度器加锁后将禁止任务调度,直到任务完成后,调用调度器解锁函数才能重新开始任务调度。这里一定要明白一点,调度锁只是将调度器关闭,并不影响中断的执行,该进中断还是要进的,只是不会执行任务切换。
11.10.1 调度器加锁OSSchedLock()
void
OSSchedLock (OS_ERR *p_err)
{
CPU_SR_ALLOC();
#ifdef
OS_SAFETY_CRITICAL
if (p_err == (OS_ERR *)0) {
OS_SAFETY_CRITICAL_EXCEPTION();
return;
}
#endif
#if
OS_CFG_CALLED_FROM_ISR_CHK_EN > 0u
if (OSIntNestingCtr > (OS_NESTING_CTR)0)
{
(1)
*p_err = OS_ERR_SCHED_LOCK_ISR;
return;
}
#endif
if (OSRunning != OS_STATE_OS_RUNNING)
{
*p_err = OS_ERR_OS_NOT_RUNNING;
return;
}
if (OSSchedLockNestingCtr >= (OS_NESTING_CTR)250u)
{
(2)
*p_err = OS_ERR_LOCK_NESTING_OVF;
return;
}
CPU_CRITICAL_ENTER();
OSSchedLockNestingCtr++;
(3)
#if
OS_CFG_SCHED_LOCK_TIME_MEAS_EN > 0u
OS_SchedLockTimeMeasStart(); (4)
#endif
CPU_CRITICAL_EXIT();
*p_err = OS_ERR_NONE;
}
1. 这里表示不允许在中断服务程序中调用调度锁函数。
2. 调度锁的嵌套次数范围0~250,用户不能超出250。
3. 嵌套次数加一。
4. 开启调度锁时间测量,这里是记录一下起始时间。
11.10.2 调度器解锁OSSchedUnlock
void
OSSchedUnlock (OS_ERR *p_err)
{
CPU_SR_ALLOC();
#ifdef
OS_SAFETY_CRITICAL
if (p_err == (OS_ERR *)0) {
OS_SAFETY_CRITICAL_EXCEPTION();
return;
}
#endif
#if
OS_CFG_CALLED_FROM_ISR_CHK_EN > 0u
if (OSIntNestingCtr > (OS_NESTING_CTR)0)
{
*p_err = OS_ERR_SCHED_UNLOCK_ISR;
return;
}
#endif
if (OSRunning != OS_STATE_OS_RUNNING)
{
*p_err = OS_ERR_OS_NOT_RUNNING;
return;
}
if (OSSchedLockNestingCtr == (OS_NESTING_CTR)0)
{
*p_err = OS_ERR_SCHED_NOT_LOCKED;
return;
}
CPU_CRITICAL_ENTER();
OSSchedLockNestingCtr--;
(1)
if (OSSchedLockNestingCtr > (OS_NESTING_CTR)0) {
CPU_CRITICAL_EXIT();
*p_err = OS_ERR_SCHED_LOCKED;
return;
}
#if
OS_CFG_SCHED_LOCK_TIME_MEAS_EN > 0u
OS_SchedLockTimeMeasStop();
#endif
CPU_CRITICAL_EXIT();
OSSched();
(2)
*p_err = OS_ERR_NONE;
}
1. 调度锁嵌套计数减一。
2. 调度器被锁期间,有些任务可能会就绪,所以这里加上了调度函数。
11.10.3 调度器被锁时间测量OS_SchedLockTimeMeas
函数比较简单,这里就直接将内容贴上,特别注意函数的注释。
#if
OS_CFG_SCHED_LOCK_TIME_MEAS_EN > 0u
void
OS_SchedLockTimeMeasStart (void)
{
if (OSSchedLockNestingCtr == 1u) {
OSSchedLockTimeBegin = CPU_TS_TmrRd();
}
}
void
OS_SchedLockTimeMeasStop (void)
{
CPU_TS_TMR delta;
if (OSSchedLockNestingCtr == (OS_NESTING_CTR)0)
{
delta =
CPU_TS_TmrRd()
- OSSchedLockTimeBegin;
if
(OSSchedLockTimeMax
< delta)
{
OSSchedLockTimeMax
= delta;
}
if (OSSchedLockTimeMaxCur < delta)
{
OSSchedLockTimeMaxCur = delta;
}
}
}
#endif
11.10.4 函数使用举例
下面这个例子是为了防止截屏的过程中被其它任务打断,加入了调度锁功能。
static void
AppTaskGUIUpdate(void *p_arg)
{
OS_ERR
err;
uint8_t
Pic_Name = 0;
char buf[20];
CPU_BOOLEAN SemFlag;
(void)p_arg;
while(1)
{
SemFlag = BSP_OS_SemWait(&SEM_SYNCH, 0);
if(SemFlag == DEF_OK)
{
sprintf(buf,"0:/Picture/%d.bmp",Pic_Name);
OSSchedLock(&err);
GUI_SaveBMP(0, 0, LCD_GetXSize(), LCD_GetYSize(),buf);
OSSchedUnlock(&err);
}
}
}
11.11
Round-Robin调度
μCOS-III中的时间片调度功能做的很完善,支持全局的时间片设置,也支持每个任务的单独设置。关于时间片调度,咱们在前几期教程也有讲解。
11.11.1 配置参数OSSchedRoundRobinCfg()
#if OS_CFG_SCHED_ROUND_ROBIN_EN
> 0u
void
OSSchedRoundRobinCfg
(CPU_BOOLEAN en,
OS_TICK
dflt_time_quanta,
OS_ERR
*p_err)
{
CPU_SR_ALLOC();
#ifdef
OS_SAFETY_CRITICAL
if (p_err == (OS_ERR *)0) {
OS_SAFETY_CRITICAL_EXCEPTION();
return;
}
#endif
CPU_CRITICAL_ENTER(); (1)
if (en != DEF_ENABLED)
{ (2)
OSSchedRoundRobinEn = DEF_DISABLED;
} else {
OSSchedRoundRobinEn = DEF_ENABLED;
}
if (dflt_time_quanta > (OS_TICK)0)
{ (3)
OSSchedRoundRobinDfltTimeQuanta = dflt_time_quanta;
} else {
OSSchedRoundRobinDfltTimeQuanta = (OS_TICK)(OSCfg_TickRate_Hz /
(OS_RATE_HZ)10);
}
CPU_CRITICAL_EXIT();
*p_err = OS_ERR_NONE;
}
#endif
1. 由于这几个参数是全局变量,所以必须关闭中断。
2. 根据形参设置是否使能时间片调度。
3. 变量OSSchedRoundRobinDfltTimeQuanta是用来设置默认的时间片个数,也就是说,如果程序中没有单独配置任务的时间片个数,就会使用这个默认时间片个数。
11.11.2 放弃剩余时间片OSSchedRoundRobinYield
()
这个函数的主要功能就是任务在完成工作的情况下,如果还有剩余的时间片,可以放弃这些时间去执行另外的同优先级任务(切记,是另外的同优先级任务)
#if OS_CFG_SCHED_ROUND_ROBIN_EN
> 0u
void
OSSchedRoundRobinYield (OS_ERR *p_err)
{
OS_RDY_LIST *p_rdy_list;
OS_TCB
*p_tcb;
CPU_SR_ALLOC();
#ifdef
OS_SAFETY_CRITICAL
if (p_err == (OS_ERR *)0) {
OS_SAFETY_CRITICAL_EXCEPTION();
return;
}
#endif
#if
OS_CFG_CALLED_FROM_ISR_CHK_EN > 0u
if (OSIntNestingCtr > (OS_NESTING_CTR)0)
{
*p_err = OS_ERR_YIELD_ISR;
return;
}
#endif
if (OSSchedLockNestingCtr > (OS_NESTING_CTR)0)
{
*p_err = OS_ERR_SCHED_LOCKED;
return;
}
if (OSSchedRoundRobinEn != DEF_TRUE)
{
*p_err = OS_ERR_ROUND_ROBIN_DISABLED;
return;
}
CPU_CRITICAL_ENTER();
p_rdy_list =
&OSRdyList[OSPrioCur];
(1)
if (p_rdy_list->NbrEntries < (OS_OBJ_QTY)2) {
CPU_CRITICAL_EXIT();
*p_err = OS_ERR_ROUND_ROBIN_1;
return;
}
OS_RdyListMoveHeadToTail(p_rdy_list);
(2)
p_tcb =
p_rdy_list->HeadPtr;
if (p_tcb->TimeQuanta == (OS_TICK)0)
{
(3)
p_tcb->TimeQuantaCtr =
OSSchedRoundRobinDfltTimeQuanta;
} else {
p_tcb->TimeQuantaCtr =
p_tcb->TimeQuanta;
}
CPU_CRITICAL_EXIT();
OSSched();
(4)
*p_err = OS_ERR_NONE;
}
#endif
1. 获取此优先级的就绪链表。从而得到此优先级下任务的个数,如果同优先级下只有一个任务,将退出这个函数。
2. 移动同优先级就绪链表中任务的位置,从实现同优先级下任务的切换。
3. 参数p_tcb->TimeQuanta
= 0的时候就会使用默认的时间片个数,如果非0,就会给这个任务的时间片计数器赋予相应的时间片个数。
4. 执行任务调度。
11.11.3 Round-Robin调度算法OS_SchedRoundRobin
()
当多个任务有相同的优先级时,μCOS-III允许任务在切换到另一个任务前运行特定的时间,也就是大家常说的时间片。这个过程就是Round-Robin调度或者时间片调度。如果任务不需要将所有的时间片用完,可以调用上面讲的函数OSSchedRoundRobinYield
(),放弃剩余时间片从而切换到同优先级的另一个任务。μCOS-III支持用户在系统运行过程中使能或者禁止时间片调度,同时也支持全局的时间片设置,也支持每个任务的单独设置。
为了更好的说明Round-Robin调度算法,下面举一个例子(截图来自官方书籍):Task #1,Task #2,Task
#3都运行在优先级 X,任务运行的时间片个数都是4。

1. 一开始是Task
#3在运行,运行期间每个嘀嗒定时器中断都会让Task #3的时间片计数减一。
2. 第四次进入嘀嗒定时器中断后,Task
#3的4个时间片已经用完。
3. 切换到同优先级就绪链表中下一个任务Task
#1。
4. Task
#1开始运行直到时间片用完。
5. 切换到Task
#3运行。
6. Task
#3运行一段时间后,调用函数OSSchedRoundRobinYield ()放弃剩余时间片。
7. 切换到Task
#1运行。
8. 这里要特别注意:Task
#1会运行4个时间片,图片上面画的不是很准确。
有了上面基础后,在解析一下相关函数。
#if OS_CFG_SCHED_ROUND_ROBIN_EN
> 0u
void
OS_SchedRoundRobin (OS_RDY_LIST
*p_rdy_list)
{
OS_TCB *p_tcb;
CPU_SR_ALLOC();
if (OSSchedRoundRobinEn != DEF_TRUE)
{
(1)
return;
}
CPU_CRITICAL_ENTER();
p_tcb =
p_rdy_list->HeadPtr;
if (p_tcb == (OS_TCB *)0)
{ (2)
CPU_CRITICAL_EXIT();
return;
}
if (p_tcb == &OSIdleTaskTCB)
{ (3)
CPU_CRITICAL_EXIT();
return;
}
if (p_tcb->TimeQuantaCtr > (OS_TICK)0)
{
(4)
p_tcb->TimeQuantaCtr--;
}
if (p_tcb->TimeQuantaCtr > (OS_TICK)0)
{
(5)
CPU_CRITICAL_EXIT();
return;
}
if (p_rdy_list->NbrEntries < (OS_OBJ_QTY)2) {
(6)
CPU_CRITICAL_EXIT();
return;
}
if (OSSchedLockNestingCtr > (OS_NESTING_CTR)0)
{
(7)
CPU_CRITICAL_EXIT();
return;
}
OS_RdyListMoveHeadToTail(p_rdy_list);
(8)
p_tcb =
p_rdy_list->HeadPtr;
if (p_tcb->TimeQuanta == (OS_TICK)0)
{
(9)
p_tcb->TimeQuantaCtr =
OSSchedRoundRobinDfltTimeQuanta;
} else {
p_tcb->TimeQuantaCtr =
p_tcb->TimeQuanta;
}
CPU_CRITICAL_EXIT();
}
#endif
1. 检测Round-Robin调度是否使能。
2. 确保此优先级下存在任务。
3. 这句话的意思是说:不允许用户将应用任务的优先级设置的和空闲任务优先级一样,也就是说空闲任务的优先级下不能有其它任务。
4. 时间片减一。
5. 任务的时间片还没有用完,退出继续执行。
6. 同优先级下必须有两个及其以上的任务才能继续往下执行。
7. 如果调度器被锁,不能执行Round-Robin调度。
8. 通过调整同优先级下的就绪链表获得下一个要执行的任务。
9. 判断使用全局的默认时间片个数还是使用单独设计的时间片个数。
11.11.4 函数使用举例
int main(void)
{
OS_ERR err;
OSInit(&err);
OSSchedRoundRobinCfg(DEF_ENABLED,
8,
&err);
OSTaskCreate((OS_TCB
*)&AppTaskStartTCB,
(CPU_CHAR
*)"App Task Start",
(OS_TASK_PTR
)AppTaskStart,
(void
*)0,
(OS_PRIO
)APP_CFG_TASK_START_PRIO,
(CPU_STK
*)&AppTaskStartStk[0],
(CPU_STK_SIZE )APP_CFG_TASK_START_STK_SIZE /
10,
(CPU_STK_SIZE
)APP_CFG_TASK_START_STK_SIZE,
(OS_MSG_QTY
)0,
(OS_TICK
)0,
(void
*)0,
(OS_OPT
)(OS_OPT_TASK_STK_CHK | OS_OPT_TASK_STK_CLR),
(OS_ERR
*)&err);
OSStart(&err);
return (0);
}
static void AppTaskCOM(void
*p_arg)
{
OS_ERR err;
(void)p_arg;
while(1)
{
……
OSSchedRoundRobinYield
(&err);
……
}
}
11.12
总结
本期教程涉及到的内容较多,如果是初学的,一定要多花点时间消耗下,如果学习过程中遇到很多问题,不要担心,随着后面教程的进行会理解的更深刻。
加载中,请稍候......