关闭

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

285人阅读 评论(0) 收藏 举报
分类:

一个多任务的操作系统可以同时运行多个任务,在只有一个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



0
0

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:8463次
    • 积分:432
    • 等级:
    • 排名:千里之外
    • 原创:36篇
    • 转载:0篇
    • 译文:0篇
    • 评论:1条
    文章存档
    最新评论