直接开始说明ucos创建任务时的步骤:
1, 初始化任务堆栈
2, 初始化任务控制块
3, 把刚创建的任务设置为就绪态(即置位就绪表)
上面提到的任务堆栈,控制块,就绪表我们前面已经说过了,下面就直接看代码。
INT8U OSTaskCreate (void (*task)(void *pd), void *p_arg, OS_STK *ptos, INT8U prio)
{
OS_STK *psp;
INT8U err;
#if OS_CRITICAL_METHOD == 3 /* Allocate storage for CPU status register */
OS_CPU_SR cpu_sr;
cpu_sr = 0; /* Prevent compiler warning */
#endif
#if OS_ARG_CHK_EN > 0
if (prio > OS_LOWEST_PRIO) { /* Make sure priority is within allowable range */
return (OS_PRIO_INVALID);
}
#endif
OS_ENTER_CRITICAL(); //关闭中断
if (OSIntNesting > 0) { /* Make sure we don't create the task from within an ISR */
OS_EXIT_CRITICAL();
return (OS_ERR_TASK_CREATE_ISR);
}
if (OSTCBPrioTbl[prio] == (OS_TCB *)0) { /* Make sure task doesn't already exist at this priority */
OSTCBPrioTbl[prio] = (OS_TCB *)1; /* Reserve the priority to prevent others from doing ... */
/* ... the same thing until task is created. */
OS_EXIT_CRITICAL();
psp = (OS_STK *)OSTaskStkInit(task, p_arg, ptos, 0); /* Initialize the task's stack *///初始化任务的堆栈
err = OS_TCBInit(prio, psp, (OS_STK *)0, 0, 0, (void *)0, 0);
if (err == OS_NO_ERR) {
if (OSRunning == TRUE) { /* Find highest priority task if multitasking has started */
OS_Sched(); //如果创建任务时ucos已经开始任务调度,那么创建完任务后需要进行任务调度
}
} else {
OS_ENTER_CRITICAL();
OSTCBPrioTbl[prio] = (OS_TCB *)0;/* Make this priority available to others */
OS_EXIT_CRITICAL();
}
return (err);
}
OS_EXIT_CRITICAL();
return (OS_PRIO_EXIST);
}
下面是初始化堆栈的函数:
OS_STK * OSTaskStkInit (void (*task)(void *pd), void *pdata, OS_STK *ptos, INT16U opt)
{//函数需要4个参数,一个指向任务函数的指针,任务运行时需要的参数,堆栈指针,扩展参数
unsigned int * stk;
stk = (unsigned int *)ptos; /* Load stack pointer */
//USE_ARG(opt);
opt++;
/* build a stack for the new task */
*--stk = (unsigned int) task; /* pc */
*--stk = (unsigned int) task; /* lr */
*--stk = 12; /* r12 */
*--stk = 11; /* r11 */
*--stk = 10; /* r10 */
*--stk = 9; /* r9 */
*--stk = 8; /* r8 */
*--stk = 7; /* r7 */
*--stk = 6; /* r6 */
*--stk = 5; /* r5 */
*--stk = 4; /* r4 */
*--stk = 3; /* r3 */
*--stk = 2; /* r2 */
*--stk = 1; /* r1 */
*--stk = (unsigned int) pdata; /* r0 */
*--stk = (SUPMODE); /* cpsr */
*--stk = (SUPMODE); /* spsr */
return ((OS_STK *)stk);
}
关于堆栈,我们前面已经讲过,这里用的即递减的满堆栈。
对于任务,其实就是一个无限循环的函数,那怎么控制它的运行那,这就是操作系统要干的活,操作系统根据调度算法实现对任务的调度以及任务的切换。实现了多个任务共享cpu。
我们已经知道,堆栈对任务的重要性,一:c语言执行需要堆栈空间。二:当发生任务切换时需要把程序运行的现场保存到任务的堆栈中。
也就是说,任务堆栈中应该保存的是任务运行时函数调用的情况以及被打断时的状态信息,可是问题来了,我们刚创建一个任务时,这个任务并没有运行过呀。这个好办,我们就模拟这个任务被打断过的迹象,任务没执行过,那么这个函数调用栈帧就不复存在。我们只模拟函数运行环境的保存。看上面代码,我们首先保存的是PC和LR,因为任务函数还没有执行过,因此这个PC和LR就应该是函数的首地址,也就是函数的名称指针。比如说你定义了一个任务函数
void Task1(void *Id)
{
for(;;){
printf("run task1\n");
OSTimeDly(1000);
}
}
那就把Task1这个函数指针赋给PC和LR。接着是R1~R12这些通用寄存器,由于函数还没有执行过,这些通用寄存器的值是什么就不太重要,可以随便赋值,你看,这里就是给R1赋值1,给R2赋值2 ………… 给R12赋值12。当然你也可以给这些寄存器赋其他值,这些无关紧要,但当任务运行过后,那再保存程序执行现场时就要按章程来了,即这些寄存器被切换的时候里面的值是什么就应该保存什么。下面就要初始化CPSR和SPSR了,这两个值要根据你的操作系统要运行在处理器的那种模式下,任务运行时应该开中断的。你像我们这个把CPSR的值赋成SUPMODE(这个宏的值是0x13),就是说这个任务运行时在SVC模式下并且开中断。咱们原来说过ucos初始化过程CPSR的中断一直是关着的,CPSR的中断位就是当最高优先级任务运行时就已经开中断了。
一句话说那,现在初始化任务堆栈就是未雨绸缪。
所以,“初始化堆栈”实际上是做了一个“未雨绸缪”的工作。这个过程中有两点是必须慎重考虑的,一是PC该如何定位,二是CPU的其它寄存器(除PC之外)该怎么处理。先说第一点,因为任务是第一次运行,而任务从实质上将就是一段代码,所以PC指针应该定位到这段代码的第一行处,即所谓的入口地址(Entry Point)处,这个地址由任务指针保存着,所以把该指针值赋给PC即可。第二,这段代码还未被履行过,所以代码中的变量与CPU的其它寄存器一点关系也没有,因此R0-R12,R14可随便给值,或者不赋值也可,让这些寄存器保持原来的值,显然后者更为简单。最后再给CPSR赋值,用户可以根据实际需要使系统运行于系统模式或管理模式。
关于初始化任务控制块的代码,我想我们有了前面的有关TCB的相关知识应该很容易就会理解。我们说我们创建了一个任务,其唯一标识就是新创建了一个任务控制块。对这个任务控制块的初始化操作就是对里面的各个选项赋予相应的值而已。至于每一个选项的具体意义,源码里都有详细的说明,要是不懂可以参考邵贝贝翻译的书。
任务创建完成后,要把这个任务置为就绪态。关于怎么置位就绪表是怎么实现的,我也不想多说,前面介绍的就绪表的实现原理要仔细看。你懂了原理,实现就是用c语言的位操作相关的知识。包括或操作、与操作等,自己看源码哦!
现在我们已经知道了创建任务的方法。
我们假设我们共创建了4个任务,一个系统级任务,两个用户任务,还有一个空闲任务。
下面我们就说明一下,这个ucos是怎么开始多任务调度的。
这个系统级任务,就是任务优先级最高的任务,ucos一开始调度会找到优先级最高的任务并开始运行,这个系统级任务有它自己的特殊任务,就是一,把OSRunning置成TRUE,标志ucos开始调度运行。二:开启定时器,并开启定时器中断响应。
这是最系统级任务的运行代码:
static void SYS_Task(void *Id)
{
OSRunning=TRUE; //begin OS
uHALr_InstallSystemTimer();
printk("start system task.\n");
for (;;)
{
OSTimeDly(1000);
}
}
不过,创建完这个任务并不会执行,而是等到ucos操作系统调度到这个任务时,它才开始真正运行。
我们创建任务完毕后,会调用OSStart,多任务调度开始函数
void OSStart (void)
{
INT8U y;
INT8U x;
if (OSRunning == FALSE) {
y = OSUnMapTbl[OSRdyGrp]; /* Find highest priority's task priority number */
x = OSUnMapTbl[OSRdyTbl[y]];
OSPrioHighRdy = (INT8U)((y << 3) + x);
OSPrioCur = OSPrioHighRdy;
OSTCBHighRdy = OSTCBPrioTbl[OSPrioHighRdy]; /* Point to highest priority task ready to run */
OSTCBCur = OSTCBHighRdy;
OSStartHighRdy(); /* Execute target specific code to start task */
}
}
这个函数的主要任务就是找到最高优先级的任务的控制块,然后根据创建任务时的堆栈内容,把堆栈里的内容恢复到各个寄存器中,那这最高优先级的任务就开始真正运行了。这时ucos操作系统就掌握了整体的运行了。
关于怎么找到的最高优先级,是根据OSRdyGrp和OSRdyTbl[]这两个变量,至于方法,我们在就绪表的相关内容一节已经讲解。知道了最高优先级就知道了最高优先级任务的TCB,知道了TCB就得到了任务堆栈。把此任务的任务堆栈恢复到各个寄存器,这个任务就开始真正运行了。
OSStartHighRdy(); 这个函数是用汇编写的,很简单就是恢复寄存器现场。
OSStartHighRdy
LDR r4, addr_OSTCBCur ; Get current task TCB address
LDR r5, addr_OSTCBHighRdy ; Get highest priority task TCB address
LDR r5, [r5] ; get stack pointer
LDR sp, [r5] ; switch to the new stack
STR r5, [r4] ; set new current task TCB address
LDMFD sp!, {r4} ; YYY
MSR SPSR_cxsf, r4
LDMFD sp!, {r4} ; get new state from top of the stack
MSR CPSR_cxsf, r4 ; CPSR should be SVC32Mode
LDMFD sp!, {r0-r12, lr, pc } ; start the new task
代码要自己好好分析,不要调皮哦。
好了,现在已经是优先级最高的任务开始运行了,它要干什么那?前面已经把代码贴出来了。
下面就说说这个定时器,哈。
前面说了,这个ucos操作系统的核心就是定时器,定时器就是ucos的心脏。
进程切换的原因很多,如一个进程要等待一个资源但资源还没有出现,那这个进程要被挂起,让另一个进程来执行。我们说一个最普遍的引起任务切换的原因。时间,在操作系统里有很多控制都是基于时间的。比如,你一个任务执行时什么资源都不缺少,那我可不能让你一直运行下去,你一直运行下去的话,就违背了的操作系统设计的初衷。所以任何任务都有时间限制。时间一到就进行切换。那时间谁记那?当然是定时器了,定时器隔固定的时间产生相应的中断来提示操作系统运行了多长时间。嘻嘻,时钟中断就好像操作系统的心脏跳动一样。
在我们现在的状态下,已经创建的任务除了处理器之外不用等待其他资源,任务主动放弃cpu的使用权是产生调度的一个原因,这时候发生的是任务级任务切换。比如在ucos中有一个任务延时函数,这个函数会让当前任务挂起并延时指定的时间。那谁记录这些时间那,当然是定时器,当时间到了会产生任务切换,当然这时候是中断级的任务切换。
既然定时器如此重要,我们看看ucos是怎么利用定时器的中断服务程序来实现这些功能的。
首先,定时器中断的入口是arm的外部中断入口。当定时器定时时间到时,就产生定时器中断,通过判断中断定时器的内容来跳转到指定的中断服务程序里去执行。至于很多中断源都通过一个入口,怎么区分到底是哪个中断来了那,这是arm中断控制器的相关章节内容。Datasheet第十四章 interrupt controllor 。
我们前面提到过,ucos这个工程中为了统一,把所有的外部中断服务程序都写到不同的中断结构体里了。这些中断结构体在ucos初始化的时候有一些简单的初始化。
在系统级任务里调用了这样一个函数:
SetISR_Interrupt(IRQ_TIMER4, TimerTickHandle, NULL);
这个函数就实现了对定时器有关的中断服务程序的初始化操作,并且打开了定时器中断的掩码寄存器标志位。我们知道,一开始我们的arm的cpsr的中断是禁止的,这个是在系统级任务运行时就打开了的(还记得未雨绸缪时的堆栈cpsr的内容不)。现在又打开了定时器中断的掩码寄存器标志位,好了,原来的双层保护都打开了,从现在开始ucos就开始响应定时器中断了。那中断服务程序都干了些什么那?
看下面代码 ,
unsigned int irq=GetISROffsetClr(); //得到中断向量的偏移地址
irq=fixup_irq(irq);
if(irq>=NR_IRQS)
return;
if(InterruptFunc[irq].InterruptHandlers==NULL){
InterruptFunc[irq].ack_irq(irq); //clear pending
return;
}
OSIntEnter();
// Call interrupt service routine
InterruptFunc[irq].InterruptHandlers(irq, InterruptFunc[irq].data);
InterruptFunc[irq].ack_irq(irq); //clear pending
OSIntExit();
}
得到中断服务程序的偏移量,即确定是什么外部中断。然后执行响应的中断服务程序,然后进入ucos的中断OSIntEnter();,在这里没干什么,只是对OSIntNesting这个变量加一。然后OSIntExit();,在这里面干的就多了,如果有更高的优先级的任务就绪,那直接切换到高优先级任务去执行,即在这发生中断级任务切换。
最后推出中断。
那定时器中断服务程序(就是那个OSTimeTick()函数)具体都干什么那?
下面是部分代码:
ptcb = OSTCBList; /* Point at first TCB in TCB list */
while (ptcb->OSTCBPrio != OS_IDLE_PRIO) { /* Go through all TCBs in TCB list */
OS_ENTER_CRITICAL();
if (ptcb->OSTCBDly != 0) { /* No, Delayed or waiting for event with TO */
if (--ptcb->OSTCBDly == 0) { /* Decrement nbr of ticks to end of delay */
/* Check for timeout */
if ((ptcb->OSTCBStat & OS_STAT_PEND_ANY) != OS_STAT_RDY) {
ptcb->OSTCBStat &= ~OS_STAT_PEND_ANY; /* Yes, Clear status flag */
ptcb->OSTCBPendTO = TRUE; /* Indicate PEND timeout */
} else {
ptcb->OSTCBPendTO = FALSE;
}
if ((ptcb->OSTCBStat & OS_STAT_SUSPEND) == OS_STAT_RDY) { /* Is task suspended? */
OSRdyGrp |= ptcb->OSTCBBitY; /* No, Make ready */
OSRdyTbl[ptcb->OSTCBY] |= ptcb->OSTCBBitX;
}
}
}
ptcb = ptcb->OSTCBNext; /* Point at next TCB in TCB list */
OS_EXIT_CRITICAL();
通过阅读,简单分析你就可以知道这个定时器中断服务程序,就是对所有的通过调用延时函数的任务的TCB进行对OSTCBDly进行减一操作。延时函数OSTimeDly干的活就是把当前任务挂起,并给这个任务的TCB中的OSTCBDly赋值。当这个值减到一就说明这个任务的延时时间到了,把这个任务再置成就绪态,等待调度。
综上所述,我们知道了。我们现在创建的任务都是自己调用OSTimeDly();函数自动放弃cpu的使用权,然后通过定时器中断函数计时,当延时时间到时把任务置为就绪态,等待调度和切换。
在这个例子中,就有两种产生调度的原因,一:任务自愿调用OSTimeDly()产生了一次任务调度和切换,定时器中断服务程序负责对延时计时,当时间到时,置位就绪表,并产生任务调度和切换。
关于这四个任务的运行情况,可以参考我利用程序打印的代码来说明分析:
可以发现,其实程序一直都被定时器打断,还有计时cpu大部分时间都被空闲任务占有。因为我们的那三个任务都是执行一段代码后就延时。而空闲任务不延时。它都是在定时器中断服务程序里被迫的让出cpu使用权。请仔细分析这个多任务调度实例,在头脑中形成一个宏观现象,有利于对ucos的整体掌握。
下章,我们仔细分析一下这个任务级任务切换和中断级任务切换时怎么在arm9上实现的,因为这两个函数都是用汇编书写的,又和硬件有一些联系,可能比较难理解一些。我们做详细讲解。下章见。
版权声明:本文为博主原创文章,未经博主允许不得转载。