SylixOS 在进入中断退出,进入内核退出时会进行任务切换。分为两种情况讨论:
1.中断退出时任务切换
在SylixOS 中无论是同步异常还是异步异常都会首先将系统寄存器保存到系统异常栈中,然后获取当前任务的tcb控制块,最后将保存到异常堆栈中的系统上下文寄存器保存到任务控制tcb中。在SylixOS 中EXC_SAVE_VOLATILE是定义成了一个宏,所以不会进入函数,LR被改变的问题(SylixOS相关上下文保存和恢复的都定位为一个宏)
;/*
; * 使用异常临时栈, 并在异常临时栈开辟临时上下文保存区, 将 volatile 寄存器保存到临时上下文保存区
; * SP 指向异常临时栈
; */
EXC_SAVE_VOLATILE
BL API_InterEnter
;/*
; * 如果不是第一次进入中断, 跳转
; */
CMP X0 , #1
BNE 1f
;/*
; * 获得当前任务 TCB 的 ARCH_REG_CTX 地址
; */
BL API_ThreadTcbInter ;/* get current tcb */
;/*
; * 拷贝 volatile 寄存器到当前任务 TCB 的 ARCH_REG_CTX 里
; */
EXC_COPY_VOLATILE
;/*
; * 保存 non volatile 寄存器到当前任务 TCB 的 ARCH_REG_CTX 里
; */
EXC_SAVE_NON_VOLATILE
此时有个很重要地方就是当在系统中断处理函数中保存的系统寄存器中,LR寄存器也就是程序链接寄存器,保存函数的返回值。函数的返回值就是进入中断前的那一句代码,也就是系统当前运行的程序PC指针被中断打断的地方。在中断的处理函数最后调用下面函数
;/*
; * API_InterExit()
; * 如果没有发生中断嵌套, 则 API_InterExit 会调用 archIntCtxLoad 函数
; */
BL API_InterExit
API_InetExit函数会进行一系列判断,如果查找到需要切换的任务,会获得任务的tcb控制块。然后使用archIntCtxLoad函数
FUNC_DEF(archIntCtxLoad)
LDR X18 , [X0] ;/* 获取当前 TCB 的 REG_CTX 地址*/
LDR X9 , [X18, #CTX_TYPE_OFFSET] ;/* 获得上下文类型 */
CMP X9 , #0
B.NE _RestoreSmallCtx
RESTORE_BIG_REG_CTX ;/* 恢复大寄存器上下文 */
FUNC_END()
在SylixOS中有个大小上下文的区别,有的体系结构不需要保存全部系统寄存器,使用的小上文。这里会判断类型,选择相应的恢复。下面以小上下文恢复为例。
;/*********************************************************************************************************
; 恢复小上下文寄存器(参数 X18: ARCH_REG_CTX 地址)
;*********************************************************************************************************/
MACRO_DEF(RESTORE_SMALL_REG_CTX)
LDR X1 , [X18 , #XSP_OFFSET] ;/* 恢复 SP 指针 */
MOV SP , X1
RESTORE_PSTATE ;/* 恢复 PSTATE */
LDR X1 , [X18 , #XPC_OFFSET] ;/* 恢复 PC 到 ELR_EL1 */
MSR ELR_EL1 , X1
LDP X0 , X1 , [X18 , #XGREG_OFFSET(0) ]
LDP X19 , X20 , [X18 , #XGREG_OFFSET(19)]
LDP X21 , X22 , [X18 , #XGREG_OFFSET(21)]
LDP X23 , X24 , [X18 , #XGREG_OFFSET(23)]
LDP X25 , X26 , [X18 , #XGREG_OFFSET(25)]
LDP X27 , X28 , [X18 , #XGREG_OFFSET(27)]
LDP X29 , LR , [X18 , #XGREG_OFFSET(29)]
ERET
MACRO_END()
;
LDP指令也能猜出来是把对应偏移的值加载到系统寄存器中。有个比较特殊的地方是将PC寄存器放入到了ELR_EL1 寄存器中。由于操作系统是运行在EL1层。所以这里使用的是ELR_EL1寄存器。前面进入中断保存上下文的时候将LR寄存器保存到了PC寄存器对应的位置,ELR_EL1 寄存器保存的是异常返回地址,也就是进入中断后退出的地址,把它赋值为PC所对应的值(也就是刚进中断处理函数时LR的值)这样中断退出的时候又恢复到了进入中断前的PC地址。最后一句代码 ERET 就是返回到异常链接寄存器(ERET和RET 是有区别的)。这样就完成了一个任务的切换。
2.内核退出时任务切换
在内涵退出时最终会调用archTaskCtxSwitch函数进行任务切换。
;/*********************************************************************************************************
; 线程切换
; 参数为当前 CPU 控制块, 即 X0 为当前 CPU 控制块指针
;*********************************************************************************************************/
FUNC_DEF(archTaskCtxSwitch)
LDR X18, [X0] ;/* 获取当前 TCB 的 REG_CTX 地址*/
SAVE_SMALL_REG_CTX ;/* 保存小寄存器上下文 */
MOV X19 , X0 ;/* X19 暂存 X0 */
#if LW_CFG_SMP_EN > 0
BL _SchedSafeStack ;/* _SchedSafeStack(); */
MOV SP , X0 ;/* 设置 SP */
MOV X0 , X19 ;/* 恢复 X0 */
#endif
BL _SchedSwp ;/* _SchedSwp(); */
LDR X18, [X19] ;/* 获取当前 TCB 的 REG_CTX 地址*/
LDR X9 , [X18, #CTX_TYPE_OFFSET] ;/* 获得上下文类型 */
CMP X9 , #0
B.NE _RestoreSmallCtx
RESTORE_BIG_REG_CTX ;/* 恢复大寄存器上下文 */
LINE_LABEL(_RestoreSmallCtx)
RESTORE_SMALL_REG_CTX ;/* 恢复小寄存器上下文 */
FUNC_END()
首先是保存当前上下文,和进入中断时一样还是会调用SAVE_SMALL_REG_CTX(在SylixOS定义为一个宏),和前面一样除了正常的系统寄存器,sp等保存,还将LR寄存器保存到了PC。系统首先获得了一个调度程序使用的堆栈,然后调用_SchedSwp程序进行判断哪个程序需要调度,将返回需要调度程序的tcb控制块,然后进行上下文恢复。
;/*********************************************************************************************************
; 恢复小上下文寄存器(参数 X18: ARCH_REG_CTX 地址)
;*********************************************************************************************************/
MACRO_DEF(RESTORE_SMALL_REG_CTX)
LDR X1 , [X18 , #XSP_OFFSET] ;/* 恢复 SP 指针 */
MOV SP , X1
RESTORE_PSTATE ;/* 恢复 PSTATE */
LDR X1 , [X18 , #XPC_OFFSET] ;/* 恢复 PC 到 ELR_EL1 */
MSR ELR_EL1 , X1
LDP X0 , X1 , [X18 , #XGREG_OFFSET(0) ]
LDP X19 , X20 , [X18 , #XGREG_OFFSET(19)]
LDP X21 , X22 , [X18 , #XGREG_OFFSET(21)]
LDP X23 , X24 , [X18 , #XGREG_OFFSET(23)]
LDP X25 , X26 , [X18 , #XGREG_OFFSET(25)]
LDP X27 , X28 , [X18 , #XGREG_OFFSET(27)]
LDP X29 , LR , [X18 , #XGREG_OFFSET(29)]
ERET
MACRO_END()
;
这里和中断退出时恢复寄存器一样,通过ELR_EL1 和ERET 配合完成任务切换。
备注:
在进入中断的时候状态寄存器会发生变化,关闭中断。此时SPSR_EL1存放的是关闭中断之前的状态寄存器,最后调用ERET命令时除了返回ELR_EL1中的地址,也会将SPSR_EL1重新加载到PSTATE。此时重新开启中断