掃描關(guān)注一起學(xué)嵌入式,一起學(xué)習(xí),一起成長(zhǎng)

RT-Thread 調(diào)度第一個(gè)線程的主要流程分如下:

rtthread_startup:RTT的啟動(dòng)函數(shù),主要負(fù)責(zé)板級(jí)驅(qū)動(dòng),調(diào)度器,系統(tǒng)線程初始化,啟動(dòng)調(diào)度的工作
rt_system_scheduler_start:調(diào)度系統(tǒng)第一個(gè)線程
rt_hw_context_switch_to:初始化上下文切換環(huán)境,觸發(fā) PendSV 異常
first_thread:系統(tǒng)中優(yōu)先級(jí)最高的線程的入口函數(shù)
???
rt_hw_context_switch_to
rt_hw_context_switch_to((rt_uintptr_t)&to_thread->sp):
加載目標(biāo)線程的堆棧狀態(tài),使CPU開(kāi)始執(zhí)行目標(biāo)線程的代碼
(rt_uintptr_t)&to_thread->sp 表示目標(biāo)線程的堆棧指針地址;to_thread->sp是目標(biāo)線程的堆棧指針,此時(shí) SP=0x2000198C

函數(shù)原型:
rt_hw_context_switch_to PROCEXPORT rt_hw_context_switch_to; set to threadLDR r1, =rt_interrupt_to_threadSTR r0, [r1]
這段匯編可以類(lèi)比為C語(yǔ)言寫(xiě)法:
rt_uintptr_t r0 = (rt_uintptr_t)&to_thread->sp; // r0 傳入的棧指針地址rt_uintptr_t *r1 = &rt_interrupt_to_thread; // r1 加載 rt_interrupt_to_thread 的地址*r1 = r0; // 將 r0 的值賦給 r1 指向的地址
此時(shí) rt_interrupt_to_thread 地址存儲(chǔ)的是目標(biāo)線程SP的地址,通過(guò)內(nèi)存窗口查看 0x2000113c 地址中內(nèi)容,內(nèi)容即為 0x2000198C:


接下來(lái)設(shè)置 rt_interrupt_from_thread 的值為0,表示啟動(dòng)第一次線程切換:

設(shè)置中斷標(biāo)志位 rt_thread_switch_interrupt_flag 的值為1,因?yàn)?Cortex-M 是通過(guò) PendSV 異常進(jìn)行線程切換的,所以這里置位表示要進(jìn)行切換,稍后會(huì)用到。

NVIC_SYSPRI2 EQU 0xE000ED20 ; system priority register (2)NVIC_PENDSV_PRI EQU 0xFFFF0000 ; PendSV and SysTick priority value (lowest); set the PendSV and SysTick exception priorityLDR r0, =NVIC_SYSPRI2 ; 將 NVIC_SYSPRI2 的地址加載到 r0LDR r1, =NVIC_PENDSV_PRI ; 將 NVIC_PENDSV_PRI 的值加載到 r1LDR.W r2, [r0,ORR r1, r1, r2 ; 將 r1 和 r2 的值進(jìn)行按位或操作,保持其他位不變STR r1, [r0] ; 將修改后的值寫(xiě)回 NVIC_SYSPRI2,更新 PendSV 和 SysTick 的優(yōu)先級(jí)
接下來(lái)分別將 PendSV 和 SysTick 異常的優(yōu)先級(jí)設(shè)置為最低優(yōu)先級(jí)(0xFFFF0000)
最低優(yōu)先級(jí)的意義:
延遲上下文切換:通過(guò)將 PendSV 的優(yōu)先級(jí)設(shè)置為最低(0xFF),確保它在所有其他中斷(例如硬件中斷或更高優(yōu)先級(jí)的異常)處理完成后才會(huì)執(zhí)行。這避免了上下文切換被高優(yōu)先級(jí)中斷打斷,保證了中斷處理程序的實(shí)時(shí)性。
避免搶占:在 RTOS 中,上下文切換是一個(gè)相對(duì)耗時(shí)的操作(涉及保存/恢復(fù)寄存器、棧操作等)。將 PendSV 設(shè)置為最低優(yōu)先級(jí),確保它不會(huì)搶占其他關(guān)鍵中斷(如定時(shí)器、外部設(shè)備中斷),從而提高系統(tǒng)的實(shí)時(shí)性和穩(wěn)定性。
; trigger the PendSV exception (causes context switch)LDR r0, =NVIC_INT_CTRL ; 將 NVIC_INT_CTRL 的地址加載到 r0LDR r1, =NVIC_PENDSVSET ; 將 NVIC_PENDSVSET 的值加載到 r1STR r1, [r0] ; 將 r1 的值寫(xiě)入 NVIC_INT_CTRL 寄存器
這段代碼通過(guò)向 NVIC 的 ICSR 寄存器寫(xiě)入 NVIC_PENDSVSET 值,手動(dòng)的觸發(fā) PendSV 異常。觸發(fā)后,處理器會(huì)在當(dāng)前中斷或異常處理完成后調(diào)用 PendSV_Handler,執(zhí)行線程上下文切換。
SCB_VTOR EQU 0xE000ED08 ; Vector Table Offset RegisterLDR r0, =SCB_VTOR ; 將 SCB_VTOR 的地址加載到 r0LDR r0, [r0] ; 從 SCB_VTOR 地址讀取向量表地址到 r0LDR r0, [r0] ; 從向量表起始地址讀取初始 MSP 值到 r0MSR msp, r0 ; 將 r0 的值寫(xiě)入 MSP 寄存器
這段代碼從向量表中提取初始主堆棧指針(MSP)的值,并將其恢復(fù)到 MSP 寄存器。
作用是重置主堆棧指針(MSP)到系統(tǒng)啟動(dòng)時(shí)的初始狀態(tài),通常用于初始化或上下文切換的場(chǎng)景,確保處理器在特權(quán)模式下使用正確的堆棧。
芯片啟動(dòng)時(shí)(Reset_Handler),MSP的數(shù)值為:0x200010E0

我們可以發(fā)現(xiàn)當(dāng)執(zhí)行完206行后
MSP 的數(shù)值已經(jīng)恢復(fù)成初值了(0x200010E0)

CPSIE F ; 啟用故障異常(Fault)CPSIE I ; 啟用中斷(Interrupt)
CPSIE I:
指令含義:CPSIE I 用于啟用中斷。
作用:該指令清除 PRIMASK 寄存器的值(將其置為 0),從而啟用所有配置為可屏蔽的中斷(包括外部中斷和系統(tǒng)異常)。
背景:
PRIMASK 是一個(gè)特殊寄存器,用于控制中斷的屏蔽狀態(tài)。當(dāng) PRIMASK = 1 時(shí),除 NMI 和 HardFault 外的所有中斷被禁用。
因此到了這里代表即將要觸發(fā) PendSV 中斷來(lái)進(jìn)行線程切換
; clear the BASEPRI register to disable masking priorityMOV r0, #0x00 ; 將 r0 設(shè)置為 0MSR BASEPRI, r0 ; 將 r0 的值(0)寫(xiě)入 BASEPRI 寄存器
作用:
在觸發(fā) PendSV 異常后,清除 BASEPRI 確保不會(huì)因?yàn)橹暗膬?yōu)先級(jí)屏蔽設(shè)置而阻止任何中斷的觸發(fā)。
這是初始化線程環(huán)境的一部分,確保目標(biāo)線程運(yùn)行時(shí),處理器能夠響應(yīng)所有可屏蔽中斷。
???
PendSV_Handler
PendSV_Handler PROCEXPORT PendSV_Handler; disable interrupt to protect context switchMRS r2, PRIMASK ; 將 PRIMASK 寄存器的值讀取到 r2CPSID I ; 禁用中斷(設(shè)置 PRIMASK = 1); get rt_thread_switch_interrupt_flagLDR r0, =rt_thread_switch_interrupt_flag ; 將 rt_thread_switch_interrupt_flag 的地址加載到 r0LDR r1, [r0] ; 從該地址讀取值到 r1CBZ r1, pendsv_exit ; 如果 r1 為 0,跳轉(zhuǎn)到 pendsv_exit

保護(hù)上下文切換:
通過(guò) CPSID I 禁用中斷,確保上下文切換過(guò)程不被其他中斷打斷。
保存 PRIMASK(通過(guò) MRS r2, PRIMASK)允許在切換完成后恢復(fù)原始中斷狀態(tài),避免影響系統(tǒng)的中斷配置。
檢查切換請(qǐng)求:
通過(guò)讀取 rt_thread_switch_interrupt_flag,檢查是否需要執(zhí)行上下文切換。如果標(biāo)志為 0,說(shuō)明無(wú)需切換,直接退出,減少不必要的開(kāi)銷(xiāo)。
LDR r0, =rt_interrupt_from_thread ; 將 rt_interrupt_from_thread 的地址加載到 r0LDR r1, [r0] ; 從該地址讀取值到 r1CBZ r1, switch_to_thread ; 如果 r1 為 0,跳轉(zhuǎn)到 switch_to_thread
這段代碼檢查 rt_interrupt_from_thread 是否為 0,以決定是否需要保存當(dāng)前線程的上下文。
如果 r1 為 0,說(shuō)明無(wú)需保存上下文,直接跳轉(zhuǎn)到 switch_to_thread 加載目標(biāo)線程的上下文。
如果 r1 非零,說(shuō)明有來(lái)源線程需要保存上下文,代碼會(huì)繼續(xù)執(zhí)行后續(xù)的寄存器保存操作(如保存 r4-r11 和 FPU 寄存器)。
聯(lián)系上文,rt_interrupt_from_thread在系統(tǒng)啟動(dòng)時(shí)在函數(shù)rt_hw_context_switch_to中已經(jīng)置為0,所以此時(shí)應(yīng)該切換到 switch_to_thread 函數(shù)
switch_to_threadLDR r1, =rt_interrupt_to_thread ; 將 rt_interrupt_to_thread 的地址加載到 r1LDR r1, [r1] ; 從該地址讀取值到 r1LDR r1, [r1] ; 從該值指向的地址讀取線程堆棧指針到 r1
作用:
rt_interrupt_to_thread 存儲(chǔ)目標(biāo)線程的堆棧指針地址(rt_hw_context_switch_to函數(shù)傳入的第一個(gè)參數(shù))。
第一次 LDR r1, [r1] 從 rt_interrupt_to_thread 讀取目標(biāo)線程的堆棧指針地址(例如,&to_thread->sp)。
第二次 LDR r1, [r1] 從該地址讀取實(shí)際的堆棧指針值(to_thread->sp),即目標(biāo)線程的當(dāng)前堆棧頂部。

通過(guò)上述步驟,獲取目標(biāo)線程的堆棧指針,為后續(xù)從堆棧中恢復(fù)上下文做準(zhǔn)備。
針對(duì)棧幀的處理:
IF {FPU} != "SoftVFP"LDMFD r1!, {r3} ; 彈出 FPU 使用標(biāo)志到 r3ENDIFLDMFD r1!, {r4 - r11} ; 彈出 r4 - r11 寄存器IF {FPU} != "SoftVFP"CMP r3, #0 ; 如果 flag_r3 != 0VLDMFDNE r1!, {d8 - d15} ; 彈出 FPU 寄存器 s16~s31ENDIFMSR psp, r1 ; 更新堆棧指針到 PSPIF {FPU} != "SoftVFP"ORR lr, lr, #0x10 ; lr |= (1 << 4),清除 FPCACMP r3, #0 ; 如果 flag_r3 != 0BICNE lr, lr, #0x10 ; lr &= ~(1 << 4),設(shè)置 FPCAENDIF
作用:
如果處理器支持硬件浮點(diǎn)單元,從目標(biāo)線程的堆棧中彈出 FPU 使用標(biāo)志。
從目標(biāo)線程的堆棧中彈出寄存器 r4 到 r11 的值,恢復(fù)目標(biāo)線程的通用寄存器狀態(tài)。
r4-r11 是 ARM 架構(gòu)中需要手動(dòng)保存的寄存器,r0-r3 和 r12 等由硬件自動(dòng)壓棧
可見(jiàn)壓棧的順序和 cpuport.c 中的線程棧幀是完全對(duì)應(yīng)的,見(jiàn)下圖:

???
Pendsv_Exit
作用:
恢復(fù)中斷狀態(tài):
通過(guò) MSR PRIMASK, r2,恢復(fù)進(jìn)入 PendSV 異常前的中斷狀態(tài)(使能或禁用),確保系統(tǒng)中斷配置不受影響。
設(shè)置異常返回堆棧:
通過(guò) ORR lr, lr, #0x04,設(shè)置 EXC_RETURN[2] = 1,確保異常返回時(shí)使用 PSP,切換到目標(biāo)線程的堆棧。(線程模式使用 PSP,特權(quán)模式使用 MSP)。
完成異常退出:
通過(guò) BX lr,觸發(fā)異常返回,處理器切換到線程模式,恢復(fù)目標(biāo)線程的執(zhí)行上下文(包括 PC、寄存器和堆棧)。
通過(guò)調(diào)試我們可以查看要切換的第一個(gè)線程(main)的入口地址是:0x080050A1

當(dāng)執(zhí)行完成 pendsv_exit 函數(shù)后,進(jìn)入 main_thread_entry 線程入口函數(shù),可以看到PC指向的地址是移植的,至此已經(jīng)完成了 RT-Thread 的第一個(gè)線程切換。

來(lái)源:RTThread物聯(lián)網(wǎng)操作系統(tǒng)
文章來(lái)源于網(wǎng)絡(luò),版權(quán)歸原作者所有,如有侵權(quán),請(qǐng)聯(lián)系刪除。

關(guān)注【一起學(xué)嵌入式】,一起變得更優(yōu)秀!
覺(jué)得文章不錯(cuò),點(diǎn)擊“分享”、“贊”、“推薦” 唄!