当系统响应时间很重要时,要使用可剥夺型内核,uc/OS_II是可剥夺型的实时内核,抢占式的多任务实时内核
任务的三个重要部分
- 程序代码
- **私有堆栈:**保存的是任务上下文的信息
- **任务控制块:**保存了任务堆栈指针,任务当前状态标志,任务的优先级别
uC/OS_II的任务调度
任务调度的思想是:“近似地每时每刻总是让优先级最高的就绪任务处于运行状态”
即任务调度采用的就是最高优先级调度算法:
- 找到具有最高优先级的任务
- 进行任务切换
**最高优先级判断:**系统将64个优先级分为8组,每组8个,即OSRdyGrp表示组,OSRdytbl[]表示每一组
优先级prio来设置OSRdyGrp和OSRdytbl[],其中的核心代码为:
任务prio置为就绪状态
OSRdyGrp |= OSMapTbl[prio>>3];
OSRdytbl[prio>>3] |= OSMapTbl[prio&0x07];
任务prio脱离就绪状态
if((OSRdytbl[prio>>3] &= ~OSMapTbl[prio&0x07]) == 0)
{
OSRdyGrp &= ~OSMapTbl[prio>>3];
}
任务调度中判断最高优先级:因为优先级存放在RSRdyGrp, OSRdytbl中,低位代表的是高优先级,故先检查组,由低位到高位检查,检查到后,再在该组中由低位到高位检查,第一个为1的就是最高优先级
获取最高的就绪任务算法:
y = OSUnMapTal[OSRdyGrp]; //D5,D4,D3位
x = USUnMapTal[OSRdyTbl[y]]; //D2,D1,D0位
prio = (y<<3)+x; //优先级别
或
y = USUnMapTbl[OSRdyGrp];
prio = (INT8U)((y<<3)+OSUnMapTbl[OSRdyTbl[y]]);
在OS_SchedNew()函数中完成:
static void OS_SchedNew (void)
{
#if OS_LOWEST_PRIO <= 63u /* See if we support up to 64 tasks */
INT8U y;
y = OSUnMapTbl[OSRdyGrp];
OSPrioHighRdy = (INT8U)((y << 3u) + OSUnMapTbl[OSRdyTbl[y]]);
#else /* We support up to 256 tasks */
INT8U y;
OS_PRIO *ptbl;
if ((OSRdyGrp & 0xFFu) != 0u) {
y = OSUnMapTbl[OSRdyGrp & 0xFFu];
} else {
y = OSUnMapTbl[(OS_PRIO)(OSRdyGrp >> 8u) & 0xFFu] + 8u;
}
ptbl = &OSRdyTbl[y];
if ((*ptbl & 0xFFu) != 0u) {
OSPrioHighRdy = (INT8U)((y << 4u) + OSUnMapTbl[(*ptbl & 0xFFu)]);
} else {
OSPrioHighRdy = (INT8U)((y << 4u) + OSUnMapTbl[(OS_PRIO)(*ptbl >> 8u) & 0xFFu] + 8u);
}
#endif
}
其中OSUnMaptbl[256]数组的功能主要就是寻找OSRdyGrp里为1的最低位,然后在OSRdyTbl[]数组里面对应的为1的最低位就是最高任务优先级
其中生成OSUnMapTbl[]数组的算法参考:
int main()
{
uint8 redgrp;
uint8 OSUnMapTbl[256] = {0};
int i;
for(redgrp=1;regdrp<255;regdrp++)
{
uint8 temp = regdrp;
for(i=0;i<8;i++)
{
if((temp&0x01) != 0)
{
OSUnMapTbl[regdrp] = i;
break;
}
else
{
temp >>= 1;
}
}
}
}
任务切换
调度有两种方式:任务级的调度是由OSSched()函数完成,中断级的调度是由OSIntExt()函数完成的
函数OSSched():
void OS_Sched (void)
{
#if OS_CRITICAL_METHOD == 3u /* Allocate storage for CPU status register */
OS_CPU_SR cpu_sr = 0u;
#endif
OS_ENTER_CRITICAL();
if (OSIntNesting == 0u) { /* Schedule only if all ISRs done and ... */
if (OSLockNesting == 0u) { /* ... scheduler is not locked */
OS_SchedNew(); /*Find Highest priority task ready to run------global variable 'OSPrioHighRdy'*/
OSTCBHighRdy = OSTCBPrioTbl[OSPrioHighRdy];
if (OSPrioHighRdy != OSPrioCur) { /* No Ctx Sw if current task is highest rdy */
#if OS_TASK_PROFILE_EN > 0u
OSTCBHighRdy->OSTCBCtxSwCtr++; /* Inc. # of context switches to this task */
#endif
OSCtxSwCtr++; /* Increment context switch counter */
OS_TASK_SW(); /* Perform a context switch */
}
}
}
OS_EXIT_CRITICAL();
}
- 若调度是禁止的(调度上锁),或当前处于中断服务子程序中,任务的调度是不允许的,任务调度函数OSSched()将退出。
- OSLockNesting–调度锁定嵌套计数器用于跟踪调度上锁次数,当OSLockNesting>0时,表明调度是禁止的;只有OSLockNesting=0时调度才是允许的,也称为调度开锁
- OSIntNesting–中断嵌套计数器用于跟踪中断嵌套层数,只要一进入中断服务,就有OSIntNesting>0;只有所有中断都退出时,才有OSIntNesting=0
- 若调度是开放的且程序不处于中断服务之中,则调用OS_SchedNew()函数找出准备就绪且优先级最高的任务
- 任务切换通过一个宏调用OS_TASK_SW()来完成的,实质上是_OSCtxSW的堆栈操作
- 在调用_OSCtxSw中断服务程序前,CPU将PSW和任务断点指针CS,IP压入当前任务堆栈
- 将CPU寄存器AX、CX、DX、BX、SP、BP、SI、及DI、压入当前任务堆栈
- 将当前CPU的堆栈指针SS和SP存入当前任务控制块的任务堆栈指针变量(至此,当前任务的断点数据已经全部保存,所以每一个新任务运行之前,uc/os都会将旧任务的断点数据全部按照顺序压入私有堆栈中,同时每一个新任务的任务堆栈都已经保存初始化好的或以前任务中止时,CPU保存的断点数据)
- OSTCBCur = OSTCBHighRdy、OSPrioCur = OSPrioHighRdy
- 使CPU的SS和SP堆栈指针指向新的任务堆栈
- 将新的任务堆栈的内容弹出到CPU寄存器,顺序为DS、ES、DI、SI、BP、SP、BX、DX、CX、AX。
- 运行IRET,将新任务断点指针弹出到CPU的CS、IP指针寄存器,PSW弹出到CPU的PSW寄存器
- uc/os开始运行新任务
任务切换实际上就是分两步:首先将被挂起的任务的CPU寄存器推入任务堆栈,然后将准备就绪的最高优先级任务的寄存器值从任务栈中恢复到CPU寄存器中
CPU寄存器中的CS,IP寄存器内有出栈和入栈指令,所以只能引发一次中断,自动将CS,IP寄存器压入堆栈,再利用中断返回,将新任务的任务断点指针弹出到CPU的CS,IP寄存器中,实现任务切换
中断为INT 0x80;即128号中断
程序计数器PC是系统进行程序切换动作的关键
任务的切换就是任务运行环境的切换
保存在任务堆栈中的内容有:---------也叫任务上下文
- 程序的断点地址(PC)
- 任务的堆栈指针(SP)
- 程序状态字寄存器(PSW)
- 通用寄存器内容
- 函数调用信息(以存在于堆栈)
uC/OS_II的任务状态
- **运行状态:**处于就绪状态的任务如果经调度器判断获得了CPU的使用权,则任务就进入运行状态
- **就绪状态:**系统为任务配备了任务控制块且在任务就绪表中进行了就绪登记,这时任务的状态叫做就绪状态
- **等待状态:**正在运行的任务,需要等待一段时间或需要等待一个事件发生再运行时,该任务就会把CPU的使用权让给别的任务从而使任务进入等待状态
- **睡眠状态:**任务在没有被配备任务控制块或剥夺了任务控制块时的状态叫做任务的睡眠状态
- **中断服务状态:**一个正在运行的任务一旦相应中断申请就会中止运行而去执行中断服务程序,这时任务的状态叫做中断服务状态
uC/OS_II的通信机制
- 信号量
- 互斥型信号量
- 消息邮箱
- 消息队列
uC/OS_II的任务优先级反转现象
互斥型信号量是一个二值信号,它可以使任务以独占方式使用共享资源,但在可剥夺型内核中,当任务以独占方式使用共享资源时,会出现低优先级任务先于高优先级任务而被运行的现象
即一个优先级别较低的任务在获得了信号量使用共享资源期间被具有较高优先级任务所打断而不能释放信号量,从而使正在等待这个信号量的更高级别的任务因得不到信号量而被迫处于等待状态,在这个等待期间,就让优先级别低于她而高于占据信号量的任务的任务先运行了。
**解决方法:**使获得信号量任务的优先级别在使用共享资源期间暂时提升到所有任务最高优先级别的高一级别,以使该任务不被其他任务所打断,从而能尽快地完成共享资源释源放并释放信号量,然后再释放了信号量之后再恢复该任务原来的优先级别—低8位存放信号量值,高8位存放需要提升的优先级别prio
uC/OS_II的移植
OS_CPU_H
该文件中修改定义与编译器无关的数据类型
设置定义栈的增长方向-------OS_STK_GROWTH 1 //栈是由高地址向低地址增长
堆栈的数据类型 OS_STK
修改定义进入临界段的方法
#define OS_CRITICAL_METHOD 3
#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
定义任务切换宏
#define OS_TASK_SW() OSCtxSw()
OS_CPU_C.c
主要是初始化任务堆栈函数—OSTaskStkInit()
OS_STK *OSTaskStkInit (void (*task)(void *p_arg), void *p_arg, OS_STK *ptos, INT16U opt)
{
OS_STK *stk;
(void)opt; /* 'opt' is not used, prevent warning */
stk = ptos; /* Load stack pointer */
/* Registers stacked as if auto-saved on exception */
*(stk) = (INT32U)0x01000000L; /* xPSR---T位(24位)置1,否则第一次执行任务为Fault */
*(--stk) = (INT32U)task; /* Entry Point */
*(--stk) = (INT32U)0xFFFFFFFEL; /* R14 (LR) (init value will cause fault if ever used)*/
*(--stk) = (INT32U)0x12121212L; /* R12 */
*(--stk) = (INT32U)0x03030303L; /* R3 */
*(--stk) = (INT32U)0x02020202L; /* R2 */
*(--stk) = (INT32U)0x01010101L; /* R1 */
*(--stk) = (INT32U)p_arg; /* R0 : argument用于传递任务函数的参数 */
/* Remaining registers saved on process stack */
*(--stk) = (INT32U)0x11111111L; /* R11 */
*(--stk) = (INT32U)0x10101010L; /* R10 */
*(--stk) = (INT32U)0x09090909L; /* R9 */
*(--stk) = (INT32U)0x08080808L; /* R8 */
*(--stk) = (INT32U)0x07070707L; /* R7 */
*(--stk) = (INT32U)0x06060606L; /* R6 */
*(--stk) = (INT32U)0x05050505L; /* R5 */
*(--stk) = (INT32U)0x04040404L; /* R4 */
return (stk);
}
OS_CPU_A.asm
主要是四个与处理器相关的函数和两个开中断和关中断的函数
- OSStartHighRdy()
- OSCtxSw()
- OSIntCtxSw()
- OSTickISR()
- OS_CPU_SR_Save()
- OS_CPU_SR_Restore()
(1) OS_SR_Save函数
OS_CPU_SR_Save
MRS R0, PRIMASK ;读取PRIMASK到R0中,R0为返回值
CPSID 1 ;PRIMASK=1,关中断(NMI 和硬FAULT可以响应)
BX LR ;返回
(2) OS_CPU_SR_Restore函数
OS_CPU_SR_Restore
MSR PRIMASK, R0 ;读取R0到PRIMASK中,R0为参数
BX LR ;返回
PRIMASK是个只有1位的寄存器,当它置1时,就关闭所有可屏蔽的异常和中断,只剩下NMI和硬fault可以响应,通过MRS和MSR方式进行访问
MRS , //将程序状态寄存器的内容传送到通用寄存器中
MSR , //将Rm中的内容传送到程序状态寄存器的特定域中
(3) OSCtxSw函数
OSCtxSw
;触发PendSV中断
LDR R0, =NVIC_INT_CTRL ;R0=NVIC_INT_CTRL
LDR R1, =NVIC_PENDSVSET ;R1=NVIC_PENDSVSET
STR R1, [R0] ;*(uint32_t*)NVIC_INT_CTRL = NVIC_PENDSVSET
BX LR ;返回
(4) OSIntCtxSw函数
OSIntCtxSw
;触发PendSV中断
LDR R0, =NVIC_INT_CTRL
LDR R1, =NVIC_PENDSVSET
STR R1, [R0]
BX LR
OSCtxSw()做的是任务之间的切换,如任务 A 因为等待某个资源或是做延时切换到任务 B,而 OSIntCtxSw()则是中断退出时,由中断状态切换到另一个任务。由中断切换到任务时,CPU 寄存器入栈的工作已经做完了,所以无需做第二次了
uC/OS_II的启动过程
(1)SystemInit()函数:进行时钟配置,选择外部时钟,进行分频,倍频配置
(2)进入main()函数:进行局部变量初始化,完成硬件初始化,定时中断
- OSInit()函数:作业系统的初始化,初始化任务控制块,事件控制表,创建最低优先级的空闲任务
- OSTaskCreate()函数:任务的创建,分配任务的工作堆栈,为函数提供存放和访问空间,任务优先级,以及对应简历的工作函数载体,任务创建成功之后,就启动任务调度程序,执行就绪任务优先级最高的任务。