uC/OS 的进程调度(下)
2016-05-04 21:29
477 查看
上文提到uC/OS进程调度的前两个主题:何时进行调度、如何选择下一个活动进程。本文来分析最后一个主题,即如何实现进程切换。
从上文的分析可知,uC/OS在实现调度时,涉及的最核心的函数只有三个:
这些函数的实现基本都是体系结构相关的,需要在移植操作系统时实现。这里以CK-CPU为例来讲解它们的具体实现方法。
在实现进程切换时,最重要的是关注新旧进程栈的状态,以及栈中保存的PC指针的值。
![](http://img.blog.csdn.net/20160504211942996)
首先把OSRunning变量设为1,从而开启操作系统的各个功能,然后跳转到OSCtxSwReturn。
让当前任务指针指向最高优先级任务,等价于
获取当前任务栈指针,等价于
调用一个用户自定义的钩子函数。
SP指向当前任务栈的栈顶,偏移0将进程的入口地址写入到EPC影子寄存器中。
接下来用SP偏移4的值写入到影子状态寄存器EPSR中。
依次从栈中弹出15个值,分别写入R1-R15寄存器。注意这时R2中为p_args,R15中为OS_TaskReturn。
调用rte指令返回,这时硬件自动把从影子寄存器EPSR和EPC的值拷贝入PSR和PC,然后从PC处开始执行。由于PC当前指向的是进程的入口地址,所以新进程得以执行。
这里还要注意一个细节,uC/OS规定进程入口函数有一个指针类型的参数,而CK-CPU规定由R2保存C语言的第一个参数,所以p_args要保存在R2中才能正好完成函数调用。
代码的核心有两个,一是调用
它是一个移植需要实现的函数。在CK-CPU上使用TRAP指令进入异常处理中,其服务函数在中断向量表中指向了OSCtxSw。
保护现场,把R1-R15压入当前旧进程栈。
根据CK-CPU手册,在异常服务程序中,EPC影子寄存器指向trap指令,我们将其加2后压栈以指向下一条指令。这是因为我们希望下次切回该进程时,执行trap的下一条指令。
把EPSR影子状态寄存器压栈,它与PSR寄存器内容一致。
把当前任务的栈顶指针SP保存入控制块TCB结构体的第一个变量,等价于
调用OSIntCtxSw。
![](http://img.blog.csdn.net/20160504212841789)
由此可见,旧进程栈中保存着当前被切出时的各寄存器状态,当前栈顶部分的结构如下图所示。同样,新进程要么是新创建的,要么是之前曾经运行但被切出的,因此它的当前栈的状态也是与图中一样的。
![](http://img.blog.csdn.net/20160504212452068)
OSIntCtxSw与在中断上下文中实现切换是同一段代码,我们继续分析。
我们来看OSIntCtxSw的实现:
它先给代表当前任务优先级的OSPrioCur变量赋值为OSPrioHighRdy,然后就开始执行OSCtxSwReturn切换到新进程。这个过程与第一节描述的完全一致,可以返回去看到新进程是如何启动的。
从上文的分析可知,uC/OS在实现调度时,涉及的最核心的函数只有三个:
OSStartHighRdy()、
OS_Sched()及
OSIntCtxSw(),它们分别对应系统启动时、进程上下文时、中断上下文时的进程切换。
这些函数的实现基本都是体系结构相关的,需要在移植操作系统时实现。这里以CK-CPU为例来讲解它们的具体实现方法。
在实现进程切换时,最重要的是关注新旧进程栈的状态,以及栈中保存的PC指针的值。
系统启动时实现切换
在main()中调用
OSStart()启动系统时,标志系统运行状态的OSRunning变量为0,第一个进程的栈已经由
OSTaskStkInit()初始化为如下结构:
OSStart()会调用
OSStartHighRdy()启动第一个进程。
=============== os_cpu_a.S 87 92 ==================== OSStartHighRdy: lrw r1,OSRunning // Set OSRunning to (1) movi r2,1 st.b r2,(r1) br OSCtxSwReturn
首先把OSRunning变量设为1,从而开启操作系统的各个功能,然后跳转到OSCtxSwReturn。
=============== os_cpu_a.S 137 158 ==================== OSCtxSwReturn: lrw r1,OSTCBHighRdy // Get highest priority task and make ld.w r3,(r1) // it the current task lrw r4,OSTCBCur st.w r3,(r4) ld.w sp,(r3) // Get current task stack pointer jbsr OSTaskSwHook // Call task switch hook ld.w r1,(sp,0) // Get the PC for the task mtcr r1,EPC ld.w r1,(sp,4) // Get the PSR for the task mtcr r1,EPSR addi sp,8 // Increment SP past the PC and PSR ldm r1-r15,(sp) // Load R0-R13 from the stack addi sp,32 // Increment SP past the registers addi sp,28 // Increment SP past the registers rte // Return to new task
让当前任务指针指向最高优先级任务,等价于
OSTCBCur = OSTCBHighRdy;
获取当前任务栈指针,等价于
SP = *OSTCBHighRdy;任务控制块TCB结构体的第一个变量OSTCBStkPtr用于保存进程的栈顶指针,这样直接通过指向TCB的指针就能获取任务的栈顶。这与Linux寻址内核栈的设计有异曲同工的妙处。
调用一个用户自定义的钩子函数。
SP指向当前任务栈的栈顶,偏移0将进程的入口地址写入到EPC影子寄存器中。
接下来用SP偏移4的值写入到影子状态寄存器EPSR中。
依次从栈中弹出15个值,分别写入R1-R15寄存器。注意这时R2中为p_args,R15中为OS_TaskReturn。
调用rte指令返回,这时硬件自动把从影子寄存器EPSR和EPC的值拷贝入PSR和PC,然后从PC处开始执行。由于PC当前指向的是进程的入口地址,所以新进程得以执行。
这里还要注意一个细节,uC/OS规定进程入口函数有一个指针类型的参数,而CK-CPU规定由R2保存C语言的第一个参数,所以p_args要保存在R2中才能正好完成函数调用。
在进程上下文中实现切换
在某个进程正在运行过程中,要实现从旧进程切换到新进程时,通过直接或间接调用OS_Sched()实现切换。
=============== os_core.c 1630 1653 ==================== 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(); 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(); }
代码的核心有两个,一是调用
OS_SchedNew()选出一个要切换至的新进程存入OSPrioHighRdy变量,二是调用
OS_TASK_SW()实现切换。前者的原理在上文中已有描述,这里来看后一个函数。
=============== os_cpu.h 75 75 ==================== #define OS_TASK_SW() asm("TRAP 0")
它是一个移植需要实现的函数。在CK-CPU上使用TRAP指令进入异常处理中,其服务函数在中断向量表中指向了OSCtxSw。
=============== os_cpu_a.S 102 121 ==================== OSCtxSw: subi sp,32 // Decrement SP to save registers subi sp,28 // Decrement SP to save registers stm r1-r15,(sp) // Save all registers to the stack subi sp,8 // Decrement SP to save PC and PSR mfcr r1,EPC // Save the PC for the current task addi r1,2 // Add 2 to PC to get past TRAP // instruction when returning st.w r1,(sp,0) mfcr r1,EPSR // Save the PSR for the current task st.w r1,(sp,4) lrw r2,OSTCBCur // Save the current task SP in the TCB ld.w r3,(r2) st.w sp,(r3) br OSIntCtxSw
保护现场,把R1-R15压入当前旧进程栈。
根据CK-CPU手册,在异常服务程序中,EPC影子寄存器指向trap指令,我们将其加2后压栈以指向下一条指令。这是因为我们希望下次切回该进程时,执行trap的下一条指令。
把EPSR影子状态寄存器压栈,它与PSR寄存器内容一致。
把当前任务的栈顶指针SP保存入控制块TCB结构体的第一个变量,等价于
*OSTCBHighRdy = SP;这也利用了TCB结构体变量排列的特性。
调用OSIntCtxSw。
由此可见,旧进程栈中保存着当前被切出时的各寄存器状态,当前栈顶部分的结构如下图所示。同样,新进程要么是新创建的,要么是之前曾经运行但被切出的,因此它的当前栈的状态也是与图中一样的。
OSIntCtxSw与在中断上下文中实现切换是同一段代码,我们继续分析。
在中断上下文中实现切换
在中断的退出阶段,如有必要,会调用OSIntCtxSw实现从中断前的旧进程切换到新进程。与在进程上下文中实现切换唯一不同的是,旧进程的栈是在发生中断时由硬件自动保存的。由于我们在实现OSCtxSw时参考了CK-CPU硬件手册,使得这两种情况下旧进程的栈都是一模一样的,因此在切换时几乎没有不同,事实上它们共享了约85%的代码,即上面分析过的OSCtxSwReturn。我们来看OSIntCtxSw的实现:
=============== os_cpu_a.S 131 137 ==================== OSIntCtxSw: lrw r1,OSPrioHighRdy // Copy the highest priority to the lrw r2,OSPrioCur // current ld.b r3,(r1) st.b r3,(r2) OSCtxSwReturn:
它先给代表当前任务优先级的OSPrioCur变量赋值为OSPrioHighRdy,然后就开始执行OSCtxSwReturn切换到新进程。这个过程与第一节描述的完全一致,可以返回去看到新进程是如何启动的。
与Linux比较
Linux的进程切换主体是schedule()函数,核心是switch_to()函数,它也是体系相关的,因此一般也用汇编实现。后面有机会我们会单独对它进行仔细分析。
相关文章推荐
- 官网下载及安装mysql-5.7.12-winx64是需要解压版(ZIP)
- Java-常用方法汇总
- ios - kvo观察者示例
- HDU 2126 01背包(求方案数)
- Redis 事务
- 使用keychain保存用户名和密码等敏感信息 KeychainItemWrapper和SFHFKeychainUtils。
- IDEA错误:Cannot start compilation: the output path is not specified for module "Test". Specify the out
- SQL Server 查询数据库中所有的表名及行数
- SQL Server 查询数据库中所有的表名及行数
- List
- 关于十进制转化为十六进制
- [CareerCup] 18.4 Count Number of Two 统计数字2的个数
- HDU5677 manacher + 二维多重背包
- Mybatis输入输出映射
- Java Web 学习笔记 —— eclipse 环境搭建
- 分布式系统介绍及MogileFS安装、基本配置 推荐
- NIO边看边记 之 ServerSocketChannel(九)
- ssh--问题记录
- Android 主题与style概述
- 在CentOS下安装ruby 1.9.3