RT-Thread内核实现 --线程调度的实现(二)

前情提要

Cotex - M内核的三个中断屏蔽器
PRIMASK 只有一个单一比特的寄存器,被置一则关闭所有可屏蔽异常。只剩下MMI和硬FAULT可以响应。缺省为0,表示没有中断
FAULTMASK 只有一个单一比特的寄存器,被置一时,只有NMI才能响应。缺省为0,表示没有关异常
BASEPRI 最多有9位(由表达优先级的位数决定),它定义了被屏蔽优先级的阈值。 当它被设置为某个值后,所有优先级大于等于此值得中断都被关闭(优先级号越大,优先级越低)。缺省为0,表示不关闭任何中断。
Cortex-M内核的专门的CPS指令
CPSID I PRIMASK=1               ;关中断
CPSIE I PRIMASK=0               ;开中断
CPSID F FAULTMASK=1          ;关异常
CPSIE F FAULTMASK=0          ;开异常

 当汇编函数在C文件中被调用的时候,如果有一个形参,则执行的时候会将这个形参传入到CPU寄存器R0,如果有两个形参,第二个形参则传入到R1。

全局变量和常量

;/**
; * @addtogroup cortex-m4
; */
;/*@{*/    //为Cortex-m4写得启动文件

SCB_VTOR        EQU     0xE000ED08               ; Vector Table Offset Register ;向量表偏移寄存器
NVIC_INT_CTRL   EQU     0xE000ED04               ; interrupt control state register
NVIC_SYSPRI2    EQU     0xE000ED20               ; system priority register (2)
NVIC_PENDSV_PRI EQU     0x00FF0000               ; PendSV priority value (lowest)
NVIC_PENDSVSET  EQU     0x10000000               ; value to trigger PendSV exception


    AREA |.text|, CODE, READONLY, ALIGN=2
    THUMB
    REQUIRE8
    PRESERVE8

    ;全局变量的声明,在.c文件里定义过的
    IMPORT rt_thread_switch_interrupt_flag    
    IMPORT rt_interrupt_from_thread
    IMPORT rt_interrupt_to_thread

    ;--------省略
    ;--------省略        ;省略得部分,就是rt_hw_context_switch_to这些函数
    ;--------省略
    ;--------省略

    ALIGN   4

    END

第一次调度函数 rt_hw_context_switch_to()函数

;/*
; * void rt_hw_context_switch_to(rt_uint32 to);
; * r0 --> to
; * this fucntion is used to perform the first thread switch
; */
rt_hw_context_switch_to    PROC        ;子程序rt_hw_context_switch_to的定义格式
    EXPORT rt_hw_context_switch_to     ;全局变量导出,C文件里也可用
    ; set to thread
    LDR     r1, =rt_interrupt_to_thread    ;ba rt_interrupt_to_thread的地址加载入到r1
    STR     r0, [r1]                       ;把r0里的值加载到r1中数据代表的地址,即rt_interrupt_to_thread

    IF      {FPU} != "SoftVFP"
    ; CLEAR CONTROL.FPCA
    MRS     r2, CONTROL             ; read
    BIC     r2, #0x04               ; modify
    MSR     CONTROL, r2             ; write-back
    ENDIF

    ; set from thread to 0
    LDR     r1, =rt_interrupt_from_thread    ;把r1中数据加载为rt_interrupt_from_thread的地址
    MOV     r0, #0x0                         ;把r0中的值设置为0
    STR     r0, [r1]                         ;把0写入到rt_interrupt_frome_thread中

    ; set interrupt flag to 1                ;设置中断标志位rt_thread_switch_interrupt_flag为1
    LDR     r1, =rt_thread_switch_interrupt_flag
    MOV     r0, #1
    STR     r0, [r1]

    ; set the PendSV exception priority    ;设置PendSV的中断优先级
    LDR     r0, =NVIC_SYSPRI2              ;r0中加载中断优先级寄存器2的地址
    LDR     r1, =NVIC_PENDSV_PRI           ;r1中加载设置PendSV中断为低优先级的数据地址
    LDR.W   r2, [r0,#0x00]       ; read    ;R2中存入中断优先级地址
    ORR     r1,r1,r2             ; modify  ;r2与r1相与的结果存入r1中
    STR     r1, [r0]             ; write-back    ;设置PendSV中断优先级为低优先级

    ; trigger the PendSV exception (causes context switch)
    LDR     r0, =NVIC_INT_CTRL            
    LDR     r1, =NVIC_PENDSVSET
    STR     r1, [r0]                        ;触发中断

    ; restore MSP
    LDR     r0, =SCB_VTOR
    LDR     r0, [r0]
    LDR     r0, [r0]
    MSR     msp, r0

    ; enable interrupts at processor level        ;打开中断
    CPSIE   F
    CPSIE   I

    ; never reach here!                ;永远不会到达这里,?难道是触发PendSV中断后,回不来了?
    ENDP

运行中上下文的调度切换函数rt_hw_context_switch()函数

;/*
; * void rt_hw_context_switch(rt_uint32 from, rt_uint32 to);
; * r0 --> from
; * r1 --> to
; */
rt_hw_context_switch_interrupt
    EXPORT rt_hw_context_switch_interrupt
rt_hw_context_switch    PROC
    EXPORT rt_hw_context_switch

    ; set rt_thread_switch_interrupt_flag to 1
    LDR     r2, =rt_thread_switch_interrupt_flag
    LDR     r3, [r2]
    CMP     r3, #1
    BEQ     _reswitch
    MOV     r3, #1
    STR     r3, [r2]

    LDR     r2, =rt_interrupt_from_thread   ; set rt_interrupt_from_thread
    STR     r0, [r2]

_reswitch
    LDR     r2, =rt_interrupt_to_thread     ; set rt_interrupt_to_thread
    STR     r1, [r2]

    LDR     r0, =NVIC_INT_CTRL              ; trigger the PendSV exception (causes context switch)
    LDR     r1, =NVIC_PENDSVSET
    STR     r1, [r0]
    BX      LR
    ENDP

真正执行的切换函数PendSV_Handler函数

; r0 --> switch from thread stack
; r1 --> switch to thread stack
; psr, pc, lr, r12, r3, r2, r1, r0 are pushed into [from] stack
PendSV_Handler   PROC        ;PendSV中断服务子程序
    EXPORT PendSV_Handler    ;全局标号导出,C文件可用

    ; disable interrupt to protect context switch
    MRS     r2, PRIMASK            ;关中断,防止打扰上下文切换
    CPSID   I                ;rt_hw_context_switch_to函数触发了中断,就来到这个函数

    ; get rt_thread_switch_interrupt_flag
    LDR     r0, =rt_thread_switch_interrupt_flag
    LDR     r1, [r0]
    CBZ     r1, pendsv_exit         ; pendsv already handled    ;判断是否真的rt_thread_switch_interrupt_flag = 0,若为0,则执行pendsv_exit函数

    ; clear rt_thread_switch_interrupt_flag to 0
    MOV     r1, #0x00        ;当然,通常应该是不为0的,因为中断之前设置过其为1,所以这一步将其清0
    STR     r1, [r0]    ;很明显,清0是这一步的

    LDR     r0, =rt_interrupt_from_thread    ;把将要切换的线程的地址加载到r0中
    LDR     r1, [r0]
    CBZ     r1, switch_to_thread    ; skip register save at the first time ;第一次的话,rt_interrupt_from_thread为0,我们在上一个函数里设置过的

;-------------------上文保存---------------------

    MRS     r1, psp                 ; get from thread stack pointer  ;加载xpsp寄存器数据到r1寄存器中

    IF      {FPU} != "SoftVFP"
    TST     lr, #0x10               ; if(!EXC_RETURN[4])
    VSTMFDEQ  r1!, {d8 - d15}       ; push FPU register s16~s31
    ENDIF

    STMFD   r1!, {r4 - r11}         ; push r4 - r11 register ;很明显这里是手动加载的

    IF      {FPU} != "SoftVFP"
    MOV     r4, #0x00               ; flag = 0

    TST     lr, #0x10               ; if(!EXC_RETURN[4])
    MOVEQ   r4, #0x01               ; flag = 1

    STMFD   r1!, {r4}               ; push flag
    ENDIF

    LDR     r0, [r0]                ;加载r0指向值到r0,即r0=rt_interrupt_from_thread,这里在上文保存前就已经能存入到r0寄存器了了
    STR     r1, [r0]                ; update from thread stack pointer ;将r1的值存储到r0,即更新线程栈sp

;-------------------下文切换---------------------

switch_to_thread
    LDR     r1, =rt_interrupt_to_thread        //加载将要执行的线程栈指针sp地址到r1中
    LDR     r1, [r1]                ;很明显,把sp指针指向的地址写入r1
    LDR     r1, [r1]                ; load thread stack pointer ;很好,这一步已经到了sp

    IF      {FPU} != "SoftVFP"
    LDMFD   r1!, {r3}               ; pop flag
    ENDIF

    LDMFD   r1!, {r4 - r11}         ; pop r4 - r11 register

    IF      {FPU} != "SoftVFP"
    CMP     r3,  #0                 ; if(flag_r3 != 0)
    VLDMFDNE  r1!, {d8 - d15}       ; pop FPU register s16~s31
    ENDIF

    MSR     psp, r1                 ; update stack pointer

    IF      {FPU} != "SoftVFP"
    ORR     lr, lr, #0x10           ; lr |=  (1 << 4), clean FPCA.
    CMP     r3,  #0                 ; if(flag_r3 != 0)
    BICNE   lr, lr, #0x10           ; lr &= ~(1 << 4), set FPCA.
    ENDIF

pendsv_exit
    ; restore interrupt
    MSR     PRIMASK, r2

    ORR     lr, lr, #0x04
    BX      lr
    ENDP

嗯,情况大概就是这样,F4支持FPU浮点运算。当然,浮点运算这个部分,哈哈,我自然是不太清楚的。慢慢来嘛,等我对这里再有更深印象的时候,再回来发出来。

猜你喜欢

转载自blog.csdn.net/xiangxistu/article/details/83475873