之前在ucos多任务切换中漏掉了一个变量,
OSCtxSwCtr标识系统任务切换次数
主要应该还是用在调试功能中
Ucos系统初始化函数为OSInit(),主要完成以下功能
全局变量初始化
就绪任务表初始化
空任务控制块初始化
事件控制块链表初始化
信号量集初始化
存储器管理初始化
Qs队列控制初始化
系统空闲任务初始化
系统统计任务初始化
部分功能需要依靠宏定义打开另外要注意一个变量OSTaskCtr标识系统全部任务数,在初始化完成之后就可以创建任务了,创建任务完成之后启动系统使用OSStart函数,代码如下
void OSStart (void)
{
if (OSRunning == OS_FALSE) {
OS_SchedNew();
OSPrioCur = OSPrioHighRdy;
OSTCBHighRdy = OSTCBPrioTbl[OSPrioHighRdy];
OSTCBCur = OSTCBHighRdy;
OSStartHighRdy();
}
}
开始阶段没有直接调用OS_SchedNew,因为此时系统是没有任务在执行的,也就没有中断锁啦,所以这一点需要注意,在系统启动之前,最好只留下系统中断,启动之后再打开中断,否则可能会造成问题
然后依旧是获取优先级,获取tcb,最后根据当前最高优先级任务调用OSStartHighRdy函数,该函数是一个需要汇编语言移植的函数,处于os_cpu_a.asm中,代码如下
OSStartHighRdy
LDR R4, =NVIC_SYSPRI2 ; set the PendSV exception priority
LDR R5, =NVIC_PENDSV_PRI
STR R5, [R4]
MOV R4, #0 ; set the PSP to 0 for initial context switch call
MSR PSP, R4
LDR R4, =OSRunning ; OSRunning = TRUE
MOV R5, #1
STRB R5, [R4]
;切换到最高优先级的任务
LDR R4, =NVIC_INT_CTRL ;rigger the PendSV exception (causes context switch)
LDR R5, =NVIC_PENDSVSET
STR R5, [R4]
CPSIE I ;enable interrupts at processor level
OSStartHang
B OSStartHang ;should never get here死循环
先将 堆栈清零,然后将OSRunning设置为true,最后触发中断,因为之前的OS_SchedNew已经得到了最高优先级的任务,所以触发中断之后,在中断中就能直接切换到我们想要切换的最高优先级任务中,实现系统启动
另外需要注意一点,如果使用了统计任务,那么系统开始的时候必须要启动统计任务,否则打开这个宏却没有初始化统计任务会在成一定的异常
关于系统临界段
在系统运行过程中,有时候某段代码是不能被打断的,而中断的时机经常会比较随机,为了解决这个问题,提出了临界段的概念,ucos一般采用三种方式来处理临界段
- 开关中断的方式
- 通过保存程序状态字并关中断的方式
- 保存程序状态字到cpu_sr变量中
第一种方法简单粗暴,第二种方法复杂一点,但是可以让处理器中断允许标志在中断前和中断之后发生变化,但是状态时压到堆栈中的第三种方式使用局部变量来保存中断状态字,不用使用堆栈,更加灵活
移植的时候可以任选一种方式,一般选择第三种方式,示例如下
OS_CPU_SR_Save
MRS R0, PRIMASK ;读取PRIMASK到R0,R0为返回值
CPSID I ;PRIMASK=1,关中断(NMI和硬件FAULT可以响应)
BX LR ;返回
OS_CPU_SR_Restore
MSR PRIMASK, R0 ;读取R0到PRIMASK中,R0为参数
BX LR ;返回
#if OS_CRITICAL_METHOD == 3
#define OS_ENTER_CRITICAL() {cpu_sr = OS_CPU_SR_Save();}
#define OS_EXIT_CRITICAL() {OS_CPU_SR_Restore(cpu_sr);}
#endif
Ucos时钟
Ucos为了处理等待,延时等与时间有关的时间,引入了一个周期性的信号,也就是ucos时钟,该时钟依托于硬件环境,需要我们根据硬件处理器的定时来去确定,与之相关的宏有一个,如下
OS_TICKS_PER_SEC,该宏定义了系统1s类中断的次数,例如我们将该宏定义为1000,那么我们就要保证系统每1ms中断一次,并且在中断处理程序中调用如下代码
OSIntEnter(); //进入中断
OSTimeTick(); //调用ucos的时钟服务程序
OSIntExit(); //触发任务切换软中断
OSIntEnter和OSIntExit之前已经说过了,来看看OSTimeTick的构成,如下
while (ptcb->OSTCBPrio != OS_TASK_IDLE_PRIO) {
OS_ENTER_CRITICAL();
if (ptcb->OSTCBDly != 0u) {
ptcb->OSTCBDly--;
if (ptcb->OSTCBDly == 0u) {
if ((ptcb->OSTCBStat & OS_STAT_PEND_ANY) != OS_STAT_RDY) {
ptcb->OSTCBStat &= (INT8U)~(INT8U)OS_STAT_PEND_ANY;
ptcb->OSTCBStatPend = OS_STAT_PEND_TO;
} else {
ptcb->OSTCBStatPend = OS_STAT_PEND_OK;
}
if ((ptcb->OSTCBStat & OS_STAT_SUSPEND) == OS_STAT_RDY) {
OSRdyGrp |= ptcb->OSTCBBitY;
OSRdyTbl[ptcb->OSTCBY] |= ptcb->OSTCBBitX;
}
}
}
ptcb = ptcb->OSTCBNext;
OS_EXIT_CRITICAL();
}
可以看到,在这个函数中,系统遍历了整个tcb控制结构,对每一个元素中的OSTCBDly元素进行递减,当某个任务控制块的延时时间OSTCBDly为0的时候,检测任务的状态是否为挂起状态,如果不是挂起状态,就修改系统就绪表将当前任务设置为reday,等待系统进行任务调度,这个任务调度就是在上一个三句代码中的最后一句OSIntExit()完成的,这也是为什么设计一个中断中切换任务功能的原因,这样可以保证机时某个任务不释放处理器,在出现更高优先级的任务的时候也能立即切换到该任务中.
为了不让系统中优先级最高的任务独占处理器,ucos设计了一个延时函数,用于高优先级任务主动释放掉cpu所有权,在实际的嵌入式系统中,这种释放也是很常见的,比如等待器件反应,人眼视觉残留等都需要,与之相关的重要函数是
OSTimeDly 参数是延时节拍数
OSTimeDlyHMSM 长时间延时,参数分别为延时小时 分 秒 毫秒
OSTimeDlyResume 取消特定优先级的任务的延时
OSTimeGet 获取当前系统节拍
OSTimeSet 设置当前系统节拍
基本上我们只要关注OSTimeDly和OSTimeDlyResume便好,先看OSTimeDly
if (OSIntNesting > 0u) {
return;
}
if (OSLockNesting > 0u) {
return;
}
if (ticks > 0u) {
OS_ENTER_CRITICAL();
y = OSTCBCur->OSTCBY;
OSRdyTbl[y] &= (OS_PRIO)~OSTCBCur->OSTCBBitX;
if (OSRdyTbl[y] == 0u) {
OSRdyGrp &= (OS_PRIO)~OSTCBCur->OSTCBBitY;
}
OSTCBCur->OSTCBDly = ticks;
OS_EXIT_CRITICAL();
OS_Sched();
当设置的延时节拍大于0的时候,首先取消当前任务的在系统就绪表中的就绪标志,然后将系统控制块的OSTCBDly参数设置为设置的节拍数,最后调用系统任务调度函数,完成系统任务调度,并且OSTCBDly设置之后和之前的中断处理OSTimeTick函数就关联起来了,从而实现系统延时
取消任务的延时函数为OSTimeDlyResume,有用的代码为
ptcb = OSTCBPrioTbl[prio];
*******
ptcb->OSTCBDly = 0u;
if ((ptcb->OSTCBStat & OS_STAT_PEND_ANY) != OS_STAT_RDY) {
ptcb->OSTCBStat &= ~OS_STAT_PEND_ANY;
ptcb->OSTCBStatPend = OS_STAT_PEND_TO;
} else {
ptcb->OSTCBStatPend = OS_STAT_PEND_OK;
}
if ((ptcb->OSTCBStat & OS_STAT_SUSPEND) == OS_STAT_RDY) {
OSRdyGrp |= ptcb->OSTCBBitY;
OSRdyTbl[ptcb->OSTCBY] |= ptcb->OSTCBBitX;
OS_EXIT_CRITICAL();
OS_Sched();
} else {
OS_EXIT_CRITICAL();
}
首先获取想要取消延时的任务tcb(根据优先级获取),然后查看任务是否被挂起,如果没被挂起而且任务就绪,就设置任务就绪表相关位置为raday,并调用OS_Sched进行任务切换,但是并不是说取消了一定会运行,还是要看任务优先级的.
到这里我们可以说任务的调度时机在于系统定时中断的时候和系统调用延时函数的时候.(后面还有别的延时时机).