OS_Sched()分析
在uc/os中总是运行优先级最高的就绪任务,确定哪个任务优先级最高,该由哪个优先级人物运行了,这一工作是由任务调度器完成的,(而具体的任务切换,是任务调度器在调用其他函数来完成)。其中任务级的调度由函数OS_Sched()来完成,中断级的调度由OSIntExt()来完成的。
OS_Sched()函数分析
void OSSched (void)
{
INT8U y;
OS_ENTER_CRITICAL();
if ((OSLockNesting | OSIntNesting) == 0)
{ //判断是否满足调度条件,在uc/os中任务级调度的调用不允许来自中断服务子程序(OSIntNesting) == 0),此外当调度器上锁时,任务调度函数将直接退出,不做任务调度
y = OSUnMapTbl[OSRdyGrp];
OSPrioHighRdy = (INT8U)((y << 3) + OSUnMapTbl[OSRdyTbl[y]]);//这两行代码是获得进入就绪态且优先级最高的任务
if (OSPrioHighRdy != OSPrioCur)
{ //检验优先级最高的任务是否是当前正在运行的任务。以避免不必要的的任务调度,毕竟任务调度是需要时间滴。
OSTCBHighRdy = OSTCBPrioTbl[OSPrioHighRdy];//通过当前最高优先级OSPrioHighRdy,从任务控制块优先级表中OSTCBPrioTbl[]获得当前最高优优先级任务控制块
OSCtxSwCtr++;//该全局变量(32位)用于记录任务切换的次数,
OS_TASK_SW();最后调用OS_TASK_SW宏来完成实际上的任务切换,该宏是一个软中断
}
}
OS_EXIT_CRITICAL();
}
OS_TASK_SW()函数分析
任务切换的内容其实是很简单的:将被挂起任务的寄存器压入堆栈,然后将高优先级的寄存器从栈中恢复到CPU的寄存器中。该过程使用软中断来实现。
SoftwareInterrupt
LDR SP, StackSvc ; 重新设置堆栈指针,在LPC2200开发板中软中断触发后,开发板处于管理模式
STMFD SP!, {R0-R3, R12, LR} //压栈,在下面过程中使用到了以下以下的寄存器,故需要将这些寄存器压入管理模式堆栈,来实现原始寄存器的保存。
MOV R1, SP //R1指向参数存储位置
MRS R3, SPSR /在ARM处理器中,只有MRS指令可以读取状态寄存器
TST R3, #T_bit //中断前是否是Thumb状态
LDRNEH R0, [LR,#-2] //是: 取得Thumb状态SWI号
BICNE R0, R0, #0xff00
LDREQ R0, [LR,#-4] //否: 取得arm状态SWI号
BICEQ R0, R0, #0xFF000000
// r0 = SWI号,R1指向参数存储位置
CMP R0, #1//比较软中断号,确立软中断的服务程序
LDRLO PC, =OSIntCtxSw//0号软中断用于任务级切换
LDREQ PC, =__OSStartHighRdy ; SWI 0x01为第一次任务切换
BL SWI_Exception//跳转到软中断服务子程序
LDMFD SP!, {R0-R3, R12, PC}^//SWI异常中断返回
StackSvc DCD (SvcStackSpace + SVC_STACK_LEGTH * 4 - 4)
对于SWI中断来说,其格式是SWI{cond} immed_24;后面的24位数字就是软中断号,故在SWI异常中断出路程序中,取出SWI中断号的方法是首先确定软中断SWI指令是ARM指令还是THUMB指令(通过访问SPSR获得),然后取得该指令的地址,可通过LR获得,接着读出指令,分解出立即数,该立即数就是软中断号。
在软中断触发后,处理器应进入管理模式,故需要从新设置堆栈指针。
在软中断触发后,SoftwareInterrupt相当于是一个判断程序,只是简单的取出中断号,具体的实现是调用其他函数完成的。
在SoftwareInterrupt中,进行了一次压栈,是因为在获得软中断号的过程中使用了R0,R1,R2,R3故需要将这些寄存器压栈,而使用的SP是管理模式下的寄存器,其于用户模式下的SP寄存器不是一个寄存器,故不需要进行压栈。
该函数调用所需要的时间是常量,于实际的任务数无关
任务级调度 OSIntCtxsw
OSIntCtxSw
//下面为保存任务环境
LDR R2, [SP, #20] //此时处理器仍处于管理模式,从管理模式堆栈中获得PC保存到R2中
LDR R12, [SP, #16] //从堆栈中获取R12,更新CPU寄存器
MRS R0, CPSR //保存当前状态寄存器,用于模式之间的切换
MSR CPSR_c, #(NoInt | SYS32Mode)//进入系统模式,且关中断
MOV R1, LR //获得系统模式下的LR保存到R1中,该LR就是原始任务的LR
STMFD SP!, {R1-R2} //此时处理器处于系统模式,故SP是原始任务的堆栈指针,故原始任务的LR,PC压入原始任务堆栈
STMFD SP!, {R4-R12} //由于R4-R11寄存器未改变,故其内容是原始任务的内容,直接压入原始任务堆栈即可。
MSR CPSR_c, R0 //此时切换回管理模式
LDMFD SP!, {R4-R7} //从管理模式堆栈中获得前面保存的原是任务的RO-R3,保存到CPU的R4-R7
ADD SP, SP, #8 //忽略掉R12,PC,使堆栈指针回到初始位置,使得下次继续使用
MSR CPSR_c, #(NoInt | SYS32Mode)//进入系统模式
STMFD SP!, {R4-R7} //原始任务的R0-R3入栈保存
LDR R1, =OsEnterSum //获取OsEnterSum
LDR R2, [R1]
STMFD SP!, {R2, R3} //从上面知道R3的内容为原始任务的CPSR,故该指令保存CPSR,OsEnterSum
LDR R1, =OSTCBCur //获得原始任务的TCB地址
LDR R1, [R1]//该地址的内容是原始任务的SP值
STR SP, [R1] //保存原始任务堆栈指针到原始任务的TCB
BL OSTaskSwHook //调用钩子函数
;OSPrioCur <= OSPrioHighRdy将当前优先级切换为最高优先级(优先级的切换)
LDR R4, =OSPrioCur//当前TCB结构体首地址
LDR R5, =OSPrioHighRdy//最高优先级TCB结构体首地址
LDRB R6, [R5]
STRB R6, [R4]//更新当前TCB指向最高优先级TCB
;OSTCBCur <= OSTCBHighRdy将当前TCB切换为最高优先级的TCB(TCB的切换)
LDR R6, =OSTCBHighRdy
LDR R6, [R6]
LDR R4, =OSTCBCur
STR R6, [R4]
OSIntCtxSw_1
;获取新任务堆栈指针
LDR R4, [R6]//R6为新任务的TCB地址,其内容是新任务的堆栈指针
ADD SP, R4, #68 //;17寄存器CPSR,OsEnterSum,R0-R12,LR,SP,移动新任务堆栈的SP,指向栈底
LDR LR, [SP, #-8]//新任务LR进入CPU寄存器
MSR CPSR_c, #(NoInt | SVC32Mode) //进入管理模式
MOV SP, R4 //设置堆栈指针
LDMFD SP!, {R4, R5} //CPSR,OsEnterSum
//恢复新任务的OsEnterSum
LDR R3, =OsEnterSum
STR R4, [R3]
MSR SPSR_cxsf, R5 //恢复CPSR
LDMFD SP!, {R0-R12, LR, PC }^ ;新任务RO-R12,LR,PC出栈,运行新任务
该函数执行前的数据结构:OSTCBCur指向原始任务(低优先级任务)、CPU的SP指向原始任务的栈顶、OSTCBHighRdy指向新任务的TCB
【转】msr cpsr_cxsf,r1 ;这里的cxsf表示从低到高分别占用的4个8bit的数据域
指令中有时还有出现cpsr_cf, cpsr_all, cpsr_c等,这里:
c 指 CPSR中的control field ( PSR[7:0])
f 指 flag field (PSR[31:24])
x 指 extend field (PSR[15:8])
s 指 status field ( PSR[23:16])
其中cpsr的位表示为:
31 30 29 28 --- 7 6 - 4 3 2 1 0
N Z C V I F M4 M3 M2 M1 M0
0 0 0 0 0 User26 模式
0 0 0 0 1 FIQ26 模式
0 0 0 1 0 IRQ26 模式
0 0 0 1 1 SVC26 模式
1 0 0 0 0 User 模式
1 0 0 0 1 FIQ 模式
1 0 0 1 0 IRQ 模式
1 0 0 1 1 SVC 模式
1 0 1 1 1 ABT 模式
1 1 0 1 1 UND 模式
深入分析:
对于MSR(寄存器到状态寄存器)的指令,
MSR CPSR, r0
MSR CPSR_all, r0
MSR CPSR_flg, r0
都是已经过时的表示方法。
对于MRS(状态寄存器到寄存器)的指令,
MRS R0, CPSR 等同于MRS R0, CPSR_cxsf
MRS R0, CPSR_all 会有waring
MRS R0, CPSR_flg 会有错误
在ADS中使用c,f,x,s表示cpsr的各个部分是推荐的。从指令来说:
MSR CPSR_f, r0机器码为0xe128f000
MSR CPSR_c, r0机器码为0xe121f000
MSR CPSR_x, r0机器码为0xe122f000
MSR CPSR_s, r0机器码为0xe124f000
可见机器码中用bit[29:16]4bit表示是f,c,x,s的。所以能够在机器执行的时候,
给予不同的执行结果。为了代码向后兼容性,建议使用f,c,x,s尾缀