RT-Thread 調度第一個線程的主要流程分如下:
rtthread_startup:RTT的啟動函數,主要負責板級驅動,調度器,系統線程初始化,啟動調度的工作
rt_system_scheduler_start:調度系統第一個線程
rt_hw_context_switch_to:初始化上下文切換環境,觸發 PendSV 異常
???
rt_hw_context_switch_to
rt_hw_context_switch_to((rt_uintptr_t)&to_thread->sp):
加載目標線程的堆棧狀態,使CPU開始執行目標線程的代碼
(rt_uintptr_t)&to_thread->sp表示目標線程的堆棧指針地址;to_thread->sp是目標線程的堆棧指針,此時SP=0x2000198C
函數原型:
rt_hw_context_switch_to PROC EXPORT rt_hw_context_switch_to ; set to thread LDR r1, =rt_interrupt_to_thread STR r0, [r1]
rt_uintptr_tr0 = (rt_uintptr_t)&to_thread->sp;// r0 傳入的棧指針地址rt_uintptr_t*r1 = &rt_interrupt_to_thread;// r1 加載 rt_interrupt_to_thread 的地址*r1 = r0;// 將 r0 的值賦給 r1 指向的地址
此時rt_interrupt_to_thread地址存儲的是目標線程SP的地址,通過內存窗口查看0x2000113c地址中內容,內容即為0x2000198C:
接下來設置rt_interrupt_from_thread的值為0,表示啟動第一次線程切換:
設置中斷標志位rt_thread_switch_interrupt_flag的值為1,因為 Cortex-M 是通過 PendSV 異常進行線程切換的,所以這里置位表示要進行切換,稍后會用到。
NVIC_SYSPRI2 EQU0xE000ED20;system priorityregister(2)NVIC_PENDSV_PRI EQU 0xFFFF0000 ;PendSVandSysTick priorityvalue(lowest);setthe PendSVandSysTick exception priorityLDR r0, =NVIC_SYSPRI2 ; 將 NVIC_SYSPRI2 的地址加載到 r0LDR r1, =NVIC_PENDSV_PRI ; 將 NVIC_PENDSV_PRI 的值加載到 r1LDR.W r2, [r0,#0x00] ; 讀取 NVIC_SYSPRI2 的當前值到 r2ORR r1, r1, r2 ; 將 r1 和 r2 的值進行按位或操作,保持其他位不變STR r1, [r0] ; 將修改后的值寫回 NVIC_SYSPRI2,更新 PendSV 和 SysTick 的優先級
接下來分別將PendSV和SysTick異常的優先級設置為最低優先級(0xFFFF0000)
最低優先級的意義:
延遲上下文切換:通過將 PendSV 的優先級設置為最低(0xFF),確保它在所有其他中斷(例如硬件中斷或更高優先級的異常)處理完成后才會執行。這避免了上下文切換被高優先級中斷打斷,保證了中斷處理程序的實時性。
避免搶占:在 RTOS 中,上下文切換是一個相對耗時的操作(涉及保存/恢復寄存器、棧操作等)。將 PendSV 設置為最低優先級,確保它不會搶占其他關鍵中斷(如定時器、外部設備中斷),從而提高系統的實時性和穩定性。
;trigger the PendSVexception(causes contextswitch)LDR r0, =NVIC_INT_CTRL ; 將 NVIC_INT_CTRL 的地址加載到 r0LDR r1, =NVIC_PENDSVSET ; 將 NVIC_PENDSVSET 的值加載到 r1STR r1, [r0] ; 將 r1 的值寫入 NVIC_INT_CTRL 寄存器
這段代碼通過向 NVIC 的 ICSR 寄存器寫入NVIC_PENDSVSET值,手動的觸發 PendSV 異常。觸發后,處理器會在當前中斷或異常處理完成后調用PendSV_Handler,執行線程上下文切換。
SCB_VTOR EQU0xE000ED08; VectorTableOffsetRegisterLDR r0,=SCB_VTOR ; 將 SCB_VTOR 的地址加載到 r0LDR r0, [r0] ; 從 SCB_VTOR 地址讀取向量表地址到 r0LDR r0, [r0] ; 從向量表起始地址讀取初始 MSP 值到 r0MSR msp, r0 ; 將 r0 的值寫入 MSP 寄存器
這段代碼從向量表中提取初始主堆棧指針(MSP)的值,并將其恢復到 MSP 寄存器。
作用是重置主堆棧指針(MSP)到系統啟動時的初始狀態,通常用于初始化或上下文切換的場景,確保處理器在特權模式下使用正確的堆棧。
芯片啟動時(Reset_Handler),MSP的數值為:0x200010E0
我們可以發現當執行完206行后
MSP 的數值已經恢復成初值了(0x200010E0)
CPSIE F ; 啟用故障異常(Fault)CPSIEI; 啟用中斷(Interrupt)
CPSIE I:
指令含義:CPSIE I 用于啟用中斷。
作用:該指令清除PRIMASK寄存器的值(將其置為 0),從而啟用所有配置為可屏蔽的中斷(包括外部中斷和系統異常)。
背景:
PRIMASK 是一個特殊寄存器,用于控制中斷的屏蔽狀態。當 PRIMASK = 1 時,除 NMI 和 HardFault 外的所有中斷被禁用。
因此到了這里代表即將要觸發 PendSV 中斷來進行線程切換
; clear the BASEPRI register todisablemasking priorityMOV r0,#0x00 ; 將 r0 設置為 0MSR BASEPRI, r0 ; 將 r0 的值(0)寫入 BASEPRI 寄存器
作用:
在觸發 PendSV 異常后,清除 BASEPRI 確保不會因為之前的優先級屏蔽設置而阻止任何中斷的觸發。
這是初始化線程環境的一部分,確保目標線程運行時,處理器能夠響應所有可屏蔽中斷。
???
PendSV_Handler
PendSV_Handler PROCEXPORT PendSV_Handler; disable interrupt to protect contextswitchMRS r2, PRIMASK ; 將 PRIMASK 寄存器的值讀取到 r2CPSID I ; 禁用中斷(設置 PRIMASK =1);getrt_thread_switch_interrupt_flagLDR r0, =rt_thread_switch_interrupt_flag ; 將 rt_thread_switch_interrupt_flag 的地址加載到 r0LDR r1, [r0] ; 從該地址讀取值到 r1CBZ r1, pendsv_exit ; 如果 r1 為0,跳轉到 pendsv_exit
保護上下文切換:
通過CPSID I禁用中斷,確保上下文切換過程不被其他中斷打斷。
保存PRIMASK(通過 MRS r2, PRIMASK)允許在切換完成后恢復原始中斷狀態,避免影響系統的中斷配置。
檢查切換請求:
通過讀取rt_thread_switch_interrupt_flag,檢查是否需要執行上下文切換。如果標志為 0,說明無需切換,直接退出,減少不必要的開銷。
LDR r0, =rt_interrupt_from_thread ; 將 rt_interrupt_from_thread 的地址加載到 r0LDR r1, [r0] ; 從該地址讀取值到 r1CBZ r1, switch_to_thread ; 如果 r1 為 0,跳轉到 switch_to_thread
這段代碼檢查rt_interrupt_from_thread是否為 0,以決定是否需要保存當前線程的上下文。
如果 r1 為 0,說明無需保存上下文,直接跳轉到switch_to_thread加載目標線程的上下文。
如果 r1 非零,說明有來源線程需要保存上下文,代碼會繼續執行后續的寄存器保存操作(如保存 r4-r11 和 FPU 寄存器)。
聯系上文,rt_interrupt_from_thread在系統啟動時在函數rt_hw_context_switch_to中已經置為0,所以此時應該切換到switch_to_thread函數
switch_to_threadLDR r1, =rt_interrupt_to_thread ; 將 rt_interrupt_to_thread 的地址加載到 r1LDR r1, [r1] ; 從該地址讀取值到 r1LDR r1, [r1] ; 從該值指向的地址讀取線程堆棧指針到 r1
作用:
rt_interrupt_to_thread存儲目標線程的堆棧指針地址(rt_hw_context_switch_to函數傳入的第一個參數)。
第一次 LDR r1, [r1] 從rt_interrupt_to_thread讀取目標線程的堆棧指針地址(例如,&to_thread->sp)。
第二次 LDR r1, [r1] 從該地址讀取實際的堆棧指針值(to_thread->sp),即目標線程的當前堆棧頂部。
通過上述步驟,獲取目標線程的堆棧指針,為后續從堆棧中恢復上下文做準備。
針對棧幀的處理:
IF{FPU} !="SoftVFP"LDMFDr1!, {r3} ; 彈出 FPU 使用標志到 r3ENDIFLDMFDr1!, {r4 - r11} ; 彈出 r4 - r11 寄存器IF{FPU} !="SoftVFP"CMPr3, #0; 如果 flag_r3 !=0VLDMFDNEr1!, {d8 - d15} ; 彈出 FPU 寄存器 s16~s31ENDIFMSRpsp, r1 ; 更新堆棧指針到 PSPIF{FPU} !="SoftVFP"ORRlr, lr, #0x10 ; lr |= (1<4),清除 FPCACMP?r3, #0?; 如果 flag_r3 !=?0BICNE?lr, lr, #0x10 ; lr &= ~(1?<4),設置 FPCAENDIF
作用:
如果處理器支持硬件浮點單元,從目標線程的堆棧中彈出 FPU 使用標志。
從目標線程的堆棧中彈出寄存器 r4 到 r11 的值,恢復目標線程的通用寄存器狀態。
r4-r11 是 ARM 架構中需要手動保存的寄存器,r0-r3 和 r12 等由硬件自動壓棧
可見壓棧的順序和cpuport.c中的線程棧幀是完全對應的,見下圖:
???
Pendsv_Exit
作用:
恢復中斷狀態:
通過MSR PRIMASK, r2,恢復進入 PendSV 異常前的中斷狀態(使能或禁用),確保系統中斷配置不受影響。
設置異常返回堆棧:
通過ORR lr, lr, #0x04,設置 EXC_RETURN[2] = 1,確保異常返回時使用PSP,切換到目標線程的堆棧。(線程模式使用 PSP,特權模式使用 MSP)。
完成異常退出:
通過BX lr,觸發異常返回,處理器切換到線程模式,恢復目標線程的執行上下文(包括 PC、寄存器和堆棧)。
通過調試我們可以查看要切換的第一個線程(main)的入口地址是:0x080050A1
當執行完成pendsv_exit函數后,進入main_thread_entry線程入口函數,可以看到PC指向的地址是移植的,至此已經完成了 RT-Thread 的第一個線程切換。
-
調度
+關注
關注
0文章
54瀏覽量
10926 -
線程
+關注
關注
0文章
508瀏覽量
20127 -
RT-Thread
+關注
關注
32文章
1384瀏覽量
41650
發布評論請先 登錄
什么是RT-Thread線程管理看完你就懂了
RT-Thread學習筆記 --(6)RT-Thread線程間通信學習過程總結

評論