OSTaskStkInt()任务堆栈初始化函数,在编写此函数之前,必须先确定任务的堆栈结构。而任务的堆栈结构是与CPU的体系结构、编译器有密切的关联。本移植的堆栈结构见图2.1所示。
图2.1 任务堆栈结构图
1. 函数OSTaskStkInt()代码
OS_STK *OSTaskStkInit (void (*task)(void *pd), void *pdata, OS_STK *ptos, INT16U opt)
{
OS_STK *stk;
opt = opt; /* 'opt' 没有使用。作用是避免编译器警告 */
stk = ptos; /* 获取堆栈指针 */
/* 建立任务环境,ADS1.2使用满递减堆栈 */
*stk = (OS_STK) task; /* pc */
*--stk = (OS_STK) task; /* lr */
*--stk = 0; /* r12 */
*--stk = 0; /* r11 */
*--stk = 0; /* r10 */
*--stk = 0; /* r9 */
*--stk = 0; /* r8 */
*--stk = 0; /* r7 */
*--stk = 0; /* r6 */
*--stk = 0; /* r5 */
*--stk = 0; /* r4 */
*--stk = 0; /* r3 */
*--stk = 0; /* r2 */
*--stk = 0; /* r1 */
*--stk = (unsigned int) pdata; /* r0,第一个参数使用R0传递 */
*--stk = (USER_USING_MODE|0x00); /* spsr,允许 IRQ, FIQ 中断 */
*--stk = 0; /* 关中断计数器OsEnterSum; */
return (stk);
}
OsEnterSum是作者定义的一个全局变量,主要是用它来保存关中断的次数,这样关中断和开中断就可以嵌套了。在调用OS_ENTER_CRITICAL()时,它的值加1,同时关中断。在调用OS_EXIT_CRITICAL()时,它的值减1,同时开中断。每个任务都有独立的OsEnterSum,在任务切换时保存和恢复各自的OsEnterSum。这样,各个任务开关中断的状态可以不同,任务不必过分考虑关中断对别的任务的影响。
说明:用户创建任务时,OSTasKCreat()会调用OSTaskStkInt()函数初始化该任务的堆栈,并把返回的堆栈指针保存到该任务的TCB结构中的最前面的参数OSTCBStkPtr中,当该任务要被恢复时,任务切换函数从其TCB块中取得其任务堆栈指针,依次将堆栈内容弹到处理器对应的CPSR、r0、r1,…,r12,lr,pc的寄存器中,完成现场的恢复和程序指针PC的返回。
2. 软件中断异常SWI服务程序C语言部分
参数SWI_Num为功能号,而Regs为指向堆栈中保存寄存器的值的位置。软中断的0、1号功能并没有在这里实现,而是在OS_CUP_A.S中实现。
软中断的C语言部分代码
void SWI_Exception(int SWI_Num, int *Regs)
{
OS_TCB *ptcb;
switch(SWI_Num)
{
//case 0x00: /* 任务切换函数OS_TASK_SW,参考os_cpu_s.s文件 */
// break;
//case 0x01: /* 启动任务函数OSStartHighRdy,参考os_cpu_s.s文件 */
// break;
case 0x02: /* 关中断函数OS_ENTER_CRITICAL(),参考os_cpu.h文件 */
__asm /*C语言内嵌汇编指令关键字,通过__asm关键字可以在C程序中内嵌汇编程序*/
{
MRS R0, SPSR /*读取SPSR寄存器的值,保存到r0中
ORR R0, R0, #NoInt /*寄存器r0与立即数NoInt相或,并保存到r0中,
MSR SPSR_c, R0 /*将r0的值写入SPSR[7:0],关中断
}
OsEnterSum++;
break;
case 0x03: /* 开中断函数OS_EXIT_CRITICAL(),参考os_cpu.h文件 */
if (--OsEnterSum == 0)
{
__asm
{
MRS R0, SPSR
BIC R0, R0, #NoInt
MSR SPSR_c, R0
}
}
break;
case 0x80: /* 任务切换到系统模式 */
__asm
{
MRS R0, SPSR
BIC R0, R0, #0x1f /* 将R0的低5位清零,其它位不变 */
ORR R0, R0, #SYS32Mode
MSR SPSR_c, R0
}
break;
case 0x81: /* 任务切换到用户模式 */
__asm
{
MRS R0, SPSR
BIC R0, R0, #0x1f
ORR R0, R0, #USR32Mode
MSR SPSR_c, R0
}
break;
case 0x82: /* 任务是ARM代码 */
if (Regs[0] <= OS_LOWEST_PRIO)
{
ptcb = OSTCBPrioTbl[Regs[0]];/*ptcb指向当前任务堆栈的首
地址,也就是当前任务堆栈结构的OsEnterSum
if (ptcb != NULL)
{
ptcb -> OSTCBStkPtr[1] &= ~(1 << 5) /*OSTCNStkPtr指向任务堆栈的栈顶,所以OSTCBStkPtr[1]指向任务堆栈的SPSR寄存器,通过改变SPSR相应位切换处理器模式
}
}
break;
case 0x83: /* 任务是THUMB代码 */
if (Regs[0] <= OS_LOWEST_PRIO)
{
ptcb = OSTCBPrioTbl[Regs[0]];
if (ptcb != NULL)
{
ptcb -> OSTCBStkPtr[1] |= (1 << 5);
}
}
break;
default:
break;
}
3. 宏OS_ENTER_CRITICAL()和OS_EXIT_CRITICAL()
这里需要对关中断函数和开中断函数作些解释。与所有的实时内核一样,μC/OS-Ⅱ需要先禁止中断再访问代码的临界段,并且在访问完毕后重新允许中断。这就使得μC/OS-Ⅱ能够保护临界段代码免受多任务或中断服务破坏。实现OS_ENTER_CRITICAL()和OS_EXIT_CRITICAL()这2个宏的定义有3种方法,本移植采用的是第三种方法,即在OS_CPU.H文件中使OS_CRITICAL_MEHTOD等于3,这种方法是利用某些编译器提供的扩展功能,用户可以得到当前处理器状态字的值,并将其保存在C函数的局部变量之中,这个变量可以用于恢复PSW,而本ARM内核关中断和开中断时,是通过改变程序状态寄存器CPSR中的相应控制位实现。由于使用了软件中断,程序状态寄存器 CPSR保存到程序状态保存寄存器 SPSR中,软件中断退出时会将SPSR恢复到CPSR中。所以程序只要改变程序状态保存寄存器SPSR中相应的控制位就可以了。
4. OSStartHighRdy
uC/OS-II启动多任务环境的函数是OSStart()。用户在调用OSStart()之前,必须已经建立了一个或更多任务。OSStart()最终调用函数OSStartHighRdy()运行多任务启动前优先级最高的任务,OSStartHighRdy()的代码如下。
void OSStartHighRdy(void)
{
_OSStartHighRdy();
}
通过函数_OSStartHighRdy()进入软中断SoftwareInterrupt,最终调用__OSStartHighRdy。__OSStartHighRdy代码如下所示
__OSStartHighRdy
MSR CPSR_c, #(NoInt | SYS32Mode);/*关中断,切换到系统模式
/*告诉uC/OS-II自身已经运行,将1保存到OSRunning*/
LDR R4, =OSRunning /*将OSRunning的地址加载到R4,
R4里存放的是OSRunning的地址
MOV R5, #1
STRB R5, [R4] /*将R5的值保存到R4指定的地址中,
只存储1字节数据
BL OSTaskSwHook ;调用钩子函数
LDR R6, =OSTCBHighRdy /*将OSTCBHighRdy的地址加载到R6
LDR R6, [R6] /*加载R6指定地址上的数据(字),放入R6中
B OSIntCtxSw_1
5. 移植增加的特定函数
根据ARM核心的特点和移植目标,为此增加了两个处理器模式转换函数(ChangTo-SYSMode()、ChangeToUSRMode())和两个任务初始指令集设置函数(TaskIsARM()、TaskIsTHUMBle())。他们都是通过中断指令SWI转换到系统模式,通过软件中断服务程序实现的。
ChangeToSYSMode()把当前任务转换到系统模式,ChangeToUSRMode()把当前任务转换到用户模式。她们改变程序状态保存寄存器SPSR的相应位段,而程序状态保存寄存器会在软件中断退出时复制到程序状态寄存器CPSR,任务的处理器模式就改变了。
本移植增加了两个函数TaskIsARM()和TaskIsTHUMB()用于改变任务建立时默认的指令集。函数TaskIsARM()用于声明指定优先级的任务的第一条指令是ARM指令集中的指令,而函数TaskIsTHUMB()用于声明指定优先级的任务的第一条指令是THUMB指令集中的指令,她们都有唯一的参数,即需要改变的任务的优先级。值得注意的是,这两个函数必须在相应的任务建立后但还没运行时调用。这样,如果在低优先级的任务中创建高优先级的任务就十分危险了。此时,解决的方法有三种:
(1) 高优先级任务使用默认的指令集;
(2) 改变函数OSTaskCreateHook()使任务默认不是出于就绪状态,建立任务后调用函数OSTaskResume()来使任务进入就绪状态。
(3) 建立任务时禁止任务切换,调用函数TaskIsARM()或TaskIsTHUMB()后再允许任务切换。
6. …Hook()函数
uC/OS-II有很多由用户编写的…Hook()函数,它在移植中全为空函数,用户就可以按照uC/OS-II的要求修改它。