fans-rt 任务调度-堆栈切换篇(1)任务切换的基本原理

原创 2015年07月08日 01:39:59

一个多任务的操作系统可以同时运行多个任务,在只有一个CPU的情况下,怎么同时运行多个任务呢?其实就是操作系统控制CPU在多个任务之间来回切换,这种切换对于任务来说,可以是主动发起的(休眠、阻塞),也可以是不可感知的(优先级抢占、时间片流失)。由于切换的频率极高,速度极快,给用户的感觉就好像是同时在运行多个任务一样。那么,CPU是怎么在任务之间切换的呢?我们知道CPU运行程序的所有局部变量、需要重复使用的寄存器信息以及返回地址等都保存在堆栈中。特别是发生中断时,通用寄存器信息入栈、返回地址入栈,当中断处理完成后,再从堆栈中装入这些寄存器信息,并获得返回地址,返回断点后继续执行,这样就完成了中断的处理,并且不会影响到程序的原有执行流程。但是,如果我们在中断处理完成后,从堆栈中装入另一个任务的寄存器信息和返回地址,这样是不是就可以达到任务切换的效果呢?对!!原理基本上就是这样。

比如,任务主动发起切换:

任务A请求任务切换 -> 任务A寄存器入栈(保存断点) -> 保存堆栈指针到任务上下文-> 找到一个最高优先级的就绪任务 -> 从新的任务上下文获得堆栈指针 -> 装入新任务的寄存器信息 -> 返回新任务的断点继续执行。

优先级抢占切换(时间片轮转切换):

任务A正在执行时发生硬件中断  -> 任务A寄存器入栈(保存断点) -> 保存堆栈指针到任务上下文-> 中断服务程序唤醒了高优先级的任务B-> 从任务B的上下文获得堆栈指针 ->装入任务B的寄存器信息 -> 返回任务B断点继续执行

通过上面的分析,我们已经了解了操作系统任务切换的基本原理,接下来我们可以开始实现我们的任务切换代码了。

SysTick_Handler PROC
    CPSID   I                           ;  Why to disable IRQ ? Guess !
    PUSH    {R0, LR}                    ;  Why to push R0?
    BL      CORE_EnterIRQ               ;  Set current interrupt nest layer
    BL      CORE_TickHandler            ;  Inc the system tick
    CPSIE   I                           ;
    BL      CORE_TaskScheduling         ;  Find the new task will be scheduling
    CPSID   I                           ;
    POP     {R0}                        ;  Stack must be aligned to 64 bit, so push R0
    BL      CORE_LeaveIRQ               ;  Set and get current interrupt nest layer
    CBNZ    R0,     ST_LE               ;  Nest layer != 0 then leave this interrupt
    BL      CORE_CheckMustbeSchedule    ;  Check need schedule
    CBZ     R0,     ST_LE               ;  Must schedule = FALSE then leave this interrupt
    PUSH    {R4-R12}                    ;  nest layer = 0 and Must schedule = TRUE then scheduling
    MRS     R0,     MSP                 ;  R0 = Core stack for old task
    MRS     R1,     PSP                 ;  R1 = User stack for old task
    BL      CORE_SwitchTask             ;  CORE_SwitchTask(CoreStack, UserStack);
                                        ;  No need check the task permssion
    BL      CORE_GetTaskStackPosition   ;
    MOV     R1,     R0                  ;  R1 = User stack for new task
    BL      CORE_GetCoreStackPosition   ;  R0 = Core stack for new task
    MSR     PSP,    R1                  ;  Update user stack
    MSR     MSP,    R0                  ;  Update core stack
    POP     {R4-R12}                    ;  Restore new task registers
ST_LE                                   ;
    CPSIE   I                           ;
    POP     {PC}                        ;  Return to task break point
    ENDP

上面是 fans-rt 基于 cortex-m3 在 tiny 模式下的时钟中断处理程序,什么是 tiny 模式?tiny模式的配置说明:

<pre name="code" class="cpp">;     System global core stack                  NO
;     The local core stack of general task      YES
;     The loacl core stack of kernel task       YES
;     The local user stack of general task      NO
;     Hardware supported task switch IRQ        NO
;     Hardware supported double stack           NO


每个任务一个独立的内核栈,普通任务没有用户栈,禁用硬件任务切换中断(PendSV),禁用双堆栈(所有任务运行于内核模式)。

第一行,关中断,为什么直接关中断而不保存当前标志寄存器呢?能够进入时钟中断说明中断没有被Disable,所以直接关中断。

第二行,PUSH {R0, LR}, 从代码看来,R0似乎没有保存的必要,因为我们需要保持堆栈为64位对齐,当我们需要处理一个堆栈中的64位数据时,64位对齐很重要。

再看 BL CORE_TaskScheduling 进入调度函数,检查系统中是否有就绪的更高优先级任务,如果有调度需求,置标志,等到后面  CORE_CheckMustbeSchedule 时进行判断,为什么不直接在 CORE_TaskScheduling 中返回结果呢?恩,这个问题在分析了 Hardware supported task switch IRQ = YES 的堆栈模型后就明白了。接下来再看

PUSH    {R4-R12}                    ;  nest layer = 0 and Must schedule = TRUE then scheduling
这个地方,保存R4-R12,就是我们切换流程上讲到的第二步 任务A寄存器入栈(保存断点),再看:

    MRS     R0,     MSP                 ;  R0 = Core stack for old task
    MRS     R1,     PSP                 ;  R1 = User stack for old task
    BL      CORE_SwitchTask             ;  CORE_SwitchTask(CoreStack, UserStack);
这里就是我们切换流程的第三步,保存堆栈指针到任务上下文,当然,这里还做了切换任务上下文的动作,在任务上下文切换之后,我们需要从新的任务上下文获得新任务的堆栈指针:

    BL      CORE_GetTaskStackPosition   ;
    MOV     R1,     R0                  ;  R1 = User stack for new task
    BL      CORE_GetCoreStackPosition   ;  R0 = Core stack for new task
    MSR     PSP,    R1                  ;  Update user stack
    MSR     MSP,    R0                  ;  Update core stack
然后恢复CPU寄存器并返回新任务的断点继续执行:

ST_LE                                   ;
    CPSIE   I                           ;
    POP     {PC}                        ;  Return to task break point
在POP {PC}之后,CPU就开始执行新的任务了,至此大功告成。


注:上面的流程中提到了任务上下文,在单片机上CPU并不存在对任务的定义,任务上下文对象和任务堆栈指针保存的位置由操作系统定义,但在某些高端CPU上可能提供了用于描述任务状态的标准结构体,比如X86上的任务状态段TSS,在任务状态段中有对各特权层堆栈指针的保存位置。


完整的Tiny模式中断处理代码:

;
;    Copyright(C) 2013-2015, Fans-rt development team.
;
;    All rights reserved.
;
;    This is open source software.
;    Learning and research can be unrestricted to  modification, use and dissemination.
;    If you need for commercial purposes, you should get the author's permission.
;
;    Configuration:
;     System global core stack                  NO
;     The local core stack of general task      YES
;     The loacl core stack of kernel task       YES
;     The local user stack of general task      NO
;     Hardware supported task switch IRQ        NO
;     Hardware supported double stack           NO
;
;    date           author          notes
;    2015-06-25     JiangYong       new file
;    2015-07-07     JiangYong       rename to kboard_interrupt.s
;
        INCLUDE kirq_define_enum.inc

    EXPORT  UsageFault_Handler
    EXPORT  BusFault_Handler
    EXPORT  MemManage_Handler
    EXPORT  HardFault_Handler
    EXPORT  SysTick_Handler
    EXPORT  PendSV_Handler
    EXPORT  SVC_Handler
    EXPORT  CORE_Switch2UserMode

    IMPORT  CORE_EnterIRQ
    IMPORT  CORE_LeaveIRQ
    IMPORT  CORE_TickHandler
    IMPORT  CORE_TaskScheduling
    IMPORT  CORE_HandlerLPC
    IMPORT  CORE_SwitchTask
    IMPORT  CORE_SetTaskStackPosition
    IMPORT  CORE_GetTaskStackPosition
    IMPORT  CORE_GetCoreStackPosition
    IMPORT  CORE_CheckMustbeSchedule

    PRESERVE8

    AREA    |.text|, CODE, READONLY
    ALIGN 4
    THUMB

CORE_Switch2UserMode   PROC
    MOV     R0,     #0
    MSR     PRIMASK, R0
    BX      LR
    ENDP


PendSV_Handler  PROC
    BX      LR
    ENDP

SVC_Handler     PROC
    CPSID   I                           ;  Why to disable IRQ ? Guess !
    PUSH    {LR}                        ;
    PUSH    {R4, R0-R3}                 ;  Why to push R4? Guess !
    BL      CORE_EnterIRQ               ;  Set current interrupt nest layer
    POP     {R0-R3}                     ;  Where is R4?
    CPSIE   I                           ;
    BL      CORE_HandlerLPC             ;  Call system service
    CPSID   I                           ;
    POP     {R4}                        ;  Stack must be aligned to 64 bit, so push R4
    BL      CORE_LeaveIRQ               ;  Set and get current interrupt nest layer
    CBNZ    R0,     SV_LE               ;  Nest layer != 0 then leave this interrupt
    BL      CORE_CheckMustbeSchedule    ;  Check need schedule
    CBZ     R0,     SV_LE               ;  Must schedule = FALSE then leave this interrupt
    PUSH    {R4-R12}                    ;  nest layer = 0 and Must schedule = TRUE then scheduling
    MRS     R0,     MSP                 ;  R0 = Core stack for old task
    MRS     R1,     PSP                 ;  R1 = User stack for old task
    BL      CORE_SwitchTask             ;  CORE_SwitchTask(CoreStack, UserStack);
                                        ;  No need check the task permssion
    BL      CORE_GetTaskStackPosition   ;
    MOV     R1,     R0                  ;  R1 = User stack for new task
    BL      CORE_GetCoreStackPosition   ;  R0 = Core stack for new task
    MSR     PSP,    R1                  ;  Update user stack
    MSR     MSP,    R0                  ;  Update core stack
    POP     {R4-R12}                    ;  Restore new task registers
SV_LE                                   ;
    CPSIE   I                           ;
    POP     {PC}                        ;  Return to task break point
    ENDP

SysTick_Handler PROC
    CPSID   I                           ;  Why to disable IRQ ? Guess !
    PUSH    {R0, LR}                    ;  Why to push R0?
    BL      CORE_EnterIRQ               ;  Set current interrupt nest layer
    BL      CORE_TickHandler            ;  Inc the system tick
    CPSIE   I                           ;
    BL      CORE_TaskScheduling         ;  Find the new task will be scheduling
    CPSID   I                           ;
    POP     {R0}                        ;  Stack must be aligned to 64 bit, so push R0
    BL      CORE_LeaveIRQ               ;  Set and get current interrupt nest layer
    CBNZ    R0,     ST_LE               ;  Nest layer != 0 then leave this interrupt
    BL      CORE_CheckMustbeSchedule    ;  Check need schedule
    CBZ     R0,     ST_LE               ;  Must schedule = FALSE then leave this interrupt
    PUSH    {R4-R12}                    ;  nest layer = 0 and Must schedule = TRUE then scheduling
    MRS     R0,     MSP                 ;  R0 = Core stack for old task
    MRS     R1,     PSP                 ;  R1 = User stack for old task
    BL      CORE_SwitchTask             ;  CORE_SwitchTask(CoreStack, UserStack);
                                        ;  No need check the task permssion
    BL      CORE_GetTaskStackPosition   ;
    MOV     R1,     R0                  ;  R1 = User stack for new task
    BL      CORE_GetCoreStackPosition   ;  R0 = Core stack for new task
    MSR     PSP,    R1                  ;  Update user stack
    MSR     MSP,    R0                  ;  Update core stack
    POP     {R4-R12}                    ;  Restore new task registers
ST_LE                                   ;
    CPSIE   I                           ;
    POP     {PC}                        ;  Return to task break point
    ENDP


HardFault_Handler   PROC
    B       .
    ENDP

MemManage_Handler   PROC
    B       .
    ENDP
BusFault_Handler    PROC
    B       .
    ENDP
UsageFault_Handler  PROC
    B       .
    ENDP
	
ALIGN
            END



版权声明:本文为博主原创文章,未经博主允许不得转载。

相关文章推荐

fans-rt 任务调度-堆栈切换篇(4)tiny模型详细分析

优化后的Tiny模型代码: ; ; Copyright(C) 2013-2015, Fans-rt development team. ; ; All rights reserved. ...
  • sevek
  • sevek
  • 2015-07-11 23:32
  • 241

任务调度与上下文切换时间测试

创建两个进程(实时进程)并在它们之间传送一个令牌,如此往返传送一定的次数。其中一个进程在读取令牌时就会引起阻塞。另一个进程发送令牌后等待其返回时也处于阻塞状态。发送令牌带来的开销与上下文切换带来的开销...

深入Spark内核:任务调度(1)-基本流程

SparkContext是Spark应用的入口并负责和整个集群的交互,创建RDD,累积量(accumulators variables)和广播量(broadcast variables)等, 理解s...

Python的任务调度模块APScheduler学习1(基本认识)

APScheduler基于Quartz的一个Python定时任务框架,实现了Quartz的所有功能,使用起来十分方便。提供了基于日期、固定时间间隔以及crontab类型的任务,并且可以持久化任务。基于...

uCOS-II中的任务切换-图解多种任务调度时机与问题

【@.1 任务调度时机】 之前的一篇文章分析了具体的uCOS-II中的任务切换机制,是从函数调用的角度上分析的。这次我具体从整个程序运行的时间上来看,分析多种任务调度发生的时机。以下所有图片均可...

FreeRTOS 任务调度 任务切换

@(嵌入式) 简述 启动调度器 移植层调度器 启动第一个任务 任务切换 参考 FreeRtos 简述前面文章 介绍了 FreeRTOS 中如何创建任务以及其具体实现。 一般来说, 我们会在程序...

μC/OS-II与RT-Thread对比——任务调度

在任务调度器的实现上,μC/OS-II和RT-Thread都采用了位图调度(bitmap scheduling),任务优先级的值越小则代表具有越高的优先级,主要区别在于实现形式,是采用多级队列的形式,...

Fans-rt 任务局部变量特性设计方案(需求分解)

FANS-RT 任务局部变量特性设计方案 一、特性流程简图 二、特性总体需求 1.创建局部变量对象 2.获得局部变量键 3.释放局部变量键 4.根据局部变量键设置变量值 5.根据局部变量键...
  • sevek
  • sevek
  • 2015-07-17 01:36
  • 157

ucosii的任务调度原理(文章来自百度)

ucosii的任务调度原理 1. 任務切换由操作系统自动完成,切换工作是由软件来完成主要功能,例如上下文的切换;还有部分工作由硬件来完成,例如通过中断返回指令切换时硬件来完成程序寄存器等的恢复,...

VxWorks任务的基本原理

  • 2009-04-27 16:05
  • 230KB
  • 下载
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:深度学习:神经网络中的前向传播和反向传播算法推导
举报原因:
原因补充:

(最多只允许输入30个字)