RT-Thread核心之執行緒排程(六)
阿新 • • 發佈:2019-01-27
5.執行緒切換的本質?
到現在我們知道,每個執行緒的執行需要一定的“物質”基礎。首先,需要獲得CPU的使用權,這就包括CPU內部各暫存器的使用,然後有自己獨立的棧空間,這部分的空間每個執行緒應該各自獨立。然後,每個執行緒都有一段獨特的指令以完成特定的功能。由這些就組成了“執行緒上下文”,執行緒的切換就是執行緒上下文的切換。在RT-Thread中有兩個架構相關的函式來完成這項工作:rt_hw_context_switch,rt_hw_context_switch_interrupt。
那麼這兩個函式有什麼區別呢?顯然,rt_hw_context_switch是在非中斷中進行上下文切換,而rt_hw_context_switch_interrupt則是在中斷上下文中完成執行緒切換的。這裡以S3C2440處理為例:
/*******************************************************************************************
** 函式名稱: rt_hw_context_switch
** 函式功能: 非中斷中進行上下文切換
** 入口引數: from 被切出的執行緒的棧頂指標
** to 被切入的執行緒的棧頂指標
** 返 回 值: 無
** 調 用:
*******************************************************************************************/
.globl rt_hw_context_switch
rt_hw_context_switch:
stmfd sp!, {lr} /** 將被中斷的執行緒的下一條要執行的指令的地址壓入棧中
* (LR存放下一條將要執行的指令地址)
*/
stmfd sp!, {r0-r12, lr}/** 將LR,R12-R0暫存器依次入棧
*/
mrs r4, cpsr /** 讀取CPSR暫存器的值到R4中
*/
stmfd sp!, {r4} /** 將R4暫存器的值(CPSR)壓入棧中
*/
mrs r4, spsr /** 讀取SPSR暫存器的值到R4暫存器中
*/
stmfd sp!, {r4} /** 將R4暫存器的值(SPSR)壓入棧中
*/
str sp, [r0] /** 將執行緒的棧頂指標儲存到執行緒結構的sp中
*/
ldr sp, [r1] /** 從新執行緒的執行緒結構的sp中取出該執行緒的棧頂指標
*/
ldmfd sp!, {r4} /** 從執行緒的棧中彈出SPSR暫存器值到R4暫存器中 */
msr spsr_cxsf, r4 /** 將值寫入SPSR暫存器中 */
ldmfd sp!, {r4} /** 從執行緒的棧中彈出CPSR暫存器值到R4暫存器中 */
msr spsr_cxsf, r4 /** 將值寫入CPSR暫存器中 */
ldmfd sp!, {r0-r12, lr, pc}^ /** 恢復該執行緒其他暫存器的值PC,LR,R12 - R0 */
/*******************************************************************************************
** 函式名稱: rt_hw_context_switch_interrupt
** 函式功能: 中斷中進行上下文切換
** 入口引數: from 被切出的執行緒的棧頂指標
** to 被切入的執行緒的棧頂指標
** 返 回 值: 無
** 調 用:
******************************************************************************************/
.globl rt_thread_switch_interrupt_flag
.globl rt_interrupt_from_thread
.globl rt_interrupt_to_thread
.globl rt_hw_context_switch_interrupt
rt_hw_context_switch_interrupt:
/** 1.判斷rt_thread_switch_interrupt_flag變數的值是否為1 */
ldr r2, =rt_thread_switch_interrupt_flag
/** 載入變數rt_thread_switch_interrupt_flag
* 的地址到r2暫存器中
*/
ldr r3, [r2]
/** 讀取rt_thread_switch_interrupt_flag暫存器的值到R3暫存器中 */
cmp r3, #1/**
判斷rt_thread_switch_interrupt_flag的值是否為1 */
/** 如果rt_thread_switch_interrupt_flag值為1 */
beq
_reswitch /** 如果rt_thread_switch_interrupt_flag值為1,跳轉到標號_reswitch執行 */
mov r3, #1/**
如果rt_thread_switch_interrupt_flag值為0,將其值設定為1 */
str r3, [r2]
ldr r2, =rt_interrupt_from_thread
/** 載入rt_interrupt_from_thread變數的地址到R2寄存
* 器中
*/
str r0, [r2]
/** 將被切換出的執行緒的棧頂地址儲存到變數rt_interrupt_from_thread中 */
_reswitch:
ldr r2, =rt_interrupt_to_thread
/** 載入rt_interrupt_to_thread變數的地址到R2暫存器
* 中
*/
str r1, [r2]/**
將被切入的執行緒的棧頂地址儲存到變數rt_interrupt_to_thread中 */
mov pc, lr
/******************************************************************************************* ** 函式名稱: rt_hw_context_switch_interrupt ** 函式功能: 中斷中進行上下文切換 ** 入口引數: from 被切出的執行緒的棧頂指標 ** to 被切入的執行緒的棧頂指標 ** 返 回 值: 無 ** 調 用: *******************************************************************************************/ .globl rt_thread_switch_interrupt_flag .globl rt_interrupt_from_thread .globl rt_interrupt_to_thread .globl rt_hw_context_switch_interrupt rt_hw_context_switch_interrupt: /** 1.判斷rt_thread_switch_interrupt_flag變數的值是否為1 */ ldr r2, =rt_thread_switch_interrupt_flag/** 載入變數rt_thread_switch_interrupt_flag的地址到r2暫存器中 */ ldr r3, [r2] /** 讀取rt_thread_switch_interrupt_flag暫存器的值到R3暫存器中 */ cmp r3, #1 /** 判斷rt_thread_switch_interrupt_flag的值是否為1 */ /** 如果rt_thread_switch_interrupt_flag值為1 */ beq _reswitch /** 如果rt_thread_switch_interrupt_flag值為1,跳轉到標號_reswitch執行 */ mov r3, #1 /** 如果rt_thread_switch_interrupt_flag值為0,將其值設定為1 */ str r3, [r2] ldr r2, =rt_interrupt_from_thread /** 載入rt_interrupt_from_thread變數的地址到R2暫存器中 */ str r0, [r2] /** 將被切換出的執行緒的棧頂地址儲存到變數rt_interrupt_from_thread中 */ _reswitch: ldr r2, =rt_interrupt_to_thread /** 載入rt_interrupt_to_thread變數的地址到R2暫存器中 */ str r1, [r2] /** 將被切入的執行緒的棧頂地址儲存到變數rt_interrupt_to_thread中 */ mov pc, lr 我們發現rt_hw_context_switch_interrupt並沒有完成執行緒的切換,只是用全域性變rt_interrupt_from_thread 和rt_interrupt_to_thread儲存了被換出和換入的執行緒的棧頂指標,而真正的切換過程在中斷處理中完成。 .globl rt_interrupt_enter .globl rt_interrupt_leave .globl rt_thread_switch_interrupt_flag .globl rt_interrupt_from_thread .globl rt_interrupt_to_thread vector_irq: stmfd sp!, {r0-r12,lr} /** 使用中斷模式的棧空間來儲存SVC模式下的PC, R12 - R0 */ bl rt_interrupt_enter /** 呼叫rt_interrupt_enter函式: 中斷巢狀的層數加1 */ bl rt_hw_trap_irq /** 根據中斷號去呼叫中斷處理程式:由於中斷處理程式是在IRQ模式執行, * 因此係統是不支援中斷巢狀的 */ bl rt_interrupt_leave /** 呼叫rt_interrupt_leave函式: 中斷巢狀的層數減1 */ /** 在中斷退出之前,判斷rt_thread_switch_interrupt_flag變數的值是否為1 */ ldr r0, =rt_thread_switch_interrupt_flag /** 讀取變數rt_thread_switch_interrupt_flag * 的地址到r0暫存器中 */ ldr r1, [r0] /** 讀取變數rt_thread_switch_interrupt_flag的值 */ cmp r1, #1 /** 判斷變數rt_thread_switch_interrupt_flag的值是否為1 */ beq _interrupt_thread_switch /** 如果為1說明在退出中斷模式之前還需要進行任務切換工作; * 如果為0則可以安全的退出中斷模式了 */ ldmfd sp!, {r0-r12,lr} /** 恢復SVC模式下的各個暫存器值 */ subs pc, lr, #4 /** 繼續從被中斷點執行 */ _interrupt_thread_switch: /** 1.將變數rt_thread_switch_interrupt_flag的值清0 */ mov r1, #0 /** 設定R1暫存器的值為0 */ str r1, [r0] /** 將變數rt_thread_switch_interrupt_flag的值設定為0 */ ldmfd sp!, {r0-r12,lr} /** 恢復儲存在IRQ模式中的各暫存器值 */ stmfd sp!, {r0-r3} /** 將R0-R3暫存器入棧 */ mov r1, sp /** 將此時的棧指標儲存在R1中 */ add sp, sp, #16 /** 將SP的值加16,SP重新指向R0-R3入棧時的位置 */ sub r2, lr, #4 /** 計算出被中斷的執行緒的PC值儲存到R2中 */ mrs r3, spsr /** 載入被中斷的執行緒的CPSR暫存器值到R3暫存器中 */ orr r0, r3, #NOINT /** 遮蔽中斷位 */ msr spsr_c, r0 /** 將設定後的值寫回IRQ模式的SPSR暫存器中 */ ldr r0, =.+8 /** 通過反彙編檢視: 是將下面第二條指令的地址存到R0中 */ movs pc, r0 /** movs指令會影響到CPSR,包括N,Z,C標誌位,CPSR會被SPSR覆蓋 * 因此執行此條指令相當於完成處理器從IRQ到SVC模式的切換 * 下面指令的sp將為SVC下的sp暫存器,而非IRQ模式的sp */ stmfd sp!, {r2} /** 將被中斷的執行緒的PC值入棧 */ stmfd sp!, {r4-r12,lr} /** 將被中斷的執行緒的LR,R12-R4暫存器入棧 */ mov r4, r1 /** 將R1的值儲存到R4 */ mov r5, r3 /** 將R3的值儲存到R5(IRQ_SPSR) */ ldmfd r4!, {r0-r3} /** 將棧中儲存的R0-R3暫存器值恢復 */ stmfd sp!, {r0-r3} /** 將R3-R0暫存器值入棧 */ stmfd sp!, {r5} /** 將舊任務的CPSR值入棧 */ mrs r4, spsr stmfd sp!, {r4} /** 將舊任務的SPSR值入棧 */ /** 讀取儲存在變數rt_interrupt_from_thread的舊執行緒的sp值 */ ldr r4, =rt_interrupt_from_thread ldr r5, [r4] str sp, [r5] /** 儲存換出任務的棧頂指標 */ /** 獲取新執行緒的棧頂指標 */ ldr r6, =rt_interrupt_to_thread /** 載入變數rt_interrupt_to_thread的地址到R6暫存器 * 中 */ ldr r6, [r6] /** 載入變數rt_interrupt_to_thread的值到R6中 */ ldr sp, [r6] /** 載入變數rt_interrupt_to_thread的值到SP暫存器中 */ ldmfd sp!, {r4} /** 彈出新執行緒的SPSR暫存器值 */ msr SPSR_cxsf, r4 ldmfd sp!, {r4} /** 彈出新執行緒的CPSR暫存器值 */ msr CPSR_cxsf, r4 ldmfd sp!, {r0-r12,lr,pc} /** 彈出新執行緒的其他各暫存器,執行緒恢復 */
/******************************************************************************************* ** 函式名稱: rt_hw_context_switch_interrupt ** 函式功能: 中斷中進行上下文切換 ** 入口引數: from 被切出的執行緒的棧頂指標 ** to 被切入的執行緒的棧頂指標 ** 返 回 值: 無 ** 調 用: *******************************************************************************************/ .globl rt_thread_switch_interrupt_flag .globl rt_interrupt_from_thread .globl rt_interrupt_to_thread .globl rt_hw_context_switch_interrupt rt_hw_context_switch_interrupt: /** 1.判斷rt_thread_switch_interrupt_flag變數的值是否為1 */ ldr r2, =rt_thread_switch_interrupt_flag/** 載入變數rt_thread_switch_interrupt_flag的地址到r2暫存器中 */ ldr r3, [r2] /** 讀取rt_thread_switch_interrupt_flag暫存器的值到R3暫存器中 */ cmp r3, #1 /** 判斷rt_thread_switch_interrupt_flag的值是否為1 */ /** 如果rt_thread_switch_interrupt_flag值為1 */ beq _reswitch /** 如果rt_thread_switch_interrupt_flag值為1,跳轉到標號_reswitch執行 */ mov r3, #1 /** 如果rt_thread_switch_interrupt_flag值為0,將其值設定為1 */ str r3, [r2] ldr r2, =rt_interrupt_from_thread /** 載入rt_interrupt_from_thread變數的地址到R2暫存器中 */ str r0, [r2] /** 將被切換出的執行緒的棧頂地址儲存到變數rt_interrupt_from_thread中 */ _reswitch: ldr r2, =rt_interrupt_to_thread /** 載入rt_interrupt_to_thread變數的地址到R2暫存器中 */ str r1, [r2] /** 將被切入的執行緒的棧頂地址儲存到變數rt_interrupt_to_thread中 */ mov pc, lr 我們發現rt_hw_context_switch_interrupt並沒有完成執行緒的切換,只是用全域性變rt_interrupt_from_thread 和rt_interrupt_to_thread儲存了被換出和換入的執行緒的棧頂指標,而真正的切換過程在中斷處理中完成。 .globl rt_interrupt_enter .globl rt_interrupt_leave .globl rt_thread_switch_interrupt_flag .globl rt_interrupt_from_thread .globl rt_interrupt_to_thread vector_irq: stmfd sp!, {r0-r12,lr} /** 使用中斷模式的棧空間來儲存SVC模式下的PC, R12 - R0 */ bl rt_interrupt_enter /** 呼叫rt_interrupt_enter函式: 中斷巢狀的層數加1 */ bl rt_hw_trap_irq /** 根據中斷號去呼叫中斷處理程式:由於中斷處理程式是在IRQ模式執行, * 因此係統是不支援中斷巢狀的 */ bl rt_interrupt_leave /** 呼叫rt_interrupt_leave函式: 中斷巢狀的層數減1 */ /** 在中斷退出之前,判斷rt_thread_switch_interrupt_flag變數的值是否為1 */ ldr r0, =rt_thread_switch_interrupt_flag /** 讀取變數rt_thread_switch_interrupt_flag * 的地址到r0暫存器中 */ ldr r1, [r0] /** 讀取變數rt_thread_switch_interrupt_flag的值 */ cmp r1, #1 /** 判斷變數rt_thread_switch_interrupt_flag的值是否為1 */ beq _interrupt_thread_switch /** 如果為1說明在退出中斷模式之前還需要進行任務切換工作; * 如果為0則可以安全的退出中斷模式了 */ ldmfd sp!, {r0-r12,lr} /** 恢復SVC模式下的各個暫存器值 */ subs pc, lr, #4 /** 繼續從被中斷點執行 */ _interrupt_thread_switch: /** 1.將變數rt_thread_switch_interrupt_flag的值清0 */ mov r1, #0 /** 設定R1暫存器的值為0 */ str r1, [r0] /** 將變數rt_thread_switch_interrupt_flag的值設定為0 */ ldmfd sp!, {r0-r12,lr} /** 恢復儲存在IRQ模式中的各暫存器值 */ stmfd sp!, {r0-r3} /** 將R0-R3暫存器入棧 */ mov r1, sp /** 將此時的棧指標儲存在R1中 */ add sp, sp, #16 /** 將SP的值加16,SP重新指向R0-R3入棧時的位置 */ sub r2, lr, #4 /** 計算出被中斷的執行緒的PC值儲存到R2中 */ mrs r3, spsr /** 載入被中斷的執行緒的CPSR暫存器值到R3暫存器中 */ orr r0, r3, #NOINT /** 遮蔽中斷位 */ msr spsr_c, r0 /** 將設定後的值寫回IRQ模式的SPSR暫存器中 */ ldr r0, =.+8 /** 通過反彙編檢視: 是將下面第二條指令的地址存到R0中 */ movs pc, r0 /** movs指令會影響到CPSR,包括N,Z,C標誌位,CPSR會被SPSR覆蓋 * 因此執行此條指令相當於完成處理器從IRQ到SVC模式的切換 * 下面指令的sp將為SVC下的sp暫存器,而非IRQ模式的sp */ stmfd sp!, {r2} /** 將被中斷的執行緒的PC值入棧 */ stmfd sp!, {r4-r12,lr} /** 將被中斷的執行緒的LR,R12-R4暫存器入棧 */ mov r4, r1 /** 將R1的值儲存到R4 */ mov r5, r3 /** 將R3的值儲存到R5(IRQ_SPSR) */ ldmfd r4!, {r0-r3} /** 將棧中儲存的R0-R3暫存器值恢復 */ stmfd sp!, {r0-r3} /** 將R3-R0暫存器值入棧 */ stmfd sp!, {r5} /** 將舊任務的CPSR值入棧 */ mrs r4, spsr stmfd sp!, {r4} /** 將舊任務的SPSR值入棧 */ /** 讀取儲存在變數rt_interrupt_from_thread的舊執行緒的sp值 */ ldr r4, =rt_interrupt_from_thread ldr r5, [r4] str sp, [r5] /** 儲存換出任務的棧頂指標 */ /** 獲取新執行緒的棧頂指標 */ ldr r6, =rt_interrupt_to_thread /** 載入變數rt_interrupt_to_thread的地址到R6暫存器 * 中 */ ldr r6, [r6] /** 載入變數rt_interrupt_to_thread的值到R6中 */ ldr sp, [r6] /** 載入變數rt_interrupt_to_thread的值到SP暫存器中 */ ldmfd sp!, {r4} /** 彈出新執行緒的SPSR暫存器值 */ msr SPSR_cxsf, r4 ldmfd sp!, {r4} /** 彈出新執行緒的CPSR暫存器值 */ msr CPSR_cxsf, r4 ldmfd sp!, {r0-r12,lr,pc} /** 彈出新執行緒的其他各暫存器,執行緒恢復 */