rt-thread中的壓棧與出棧分析
掃描二維碼
隨時隨地手機看文章
rt-thread中的壓棧與出棧
1.說明
本文主要想分析一下rt-thread中線程的壓棧與入棧的相關(guān)操作。從而更好的掌握線程切換與線程恢復(fù)的相關(guān)知識。
2.使用場景
首先需要明白的是什么情況下需要進(jìn)行壓棧與出棧的操作?對于這個問題可以做這樣的設(shè)想,當(dāng)程序一直做一件事的時候,是順序執(zhí)行的,不會有任何干擾。但是此時來了一個中斷,那么程序邏輯肯定會優(yōu)先去處理中斷。那么這時需要做哪些事情?
也許這個例子有點脫離實際,講的通俗明白一些,就是一個人在專注的完成一件事,此時應(yīng)該是很順利的進(jìn)行。但是當(dāng)事情還沒有做完,但是又有一個更加緊急的事情需要你去處理,這時應(yīng)該怎么做?
對于一個人來講:
(1)將手里沒有做完的事情保留起來,保留當(dāng)前的進(jìn)度
(2)將大腦清空,全力完成更加重要的事情
(3)恢復(fù)到?jīng)]有做完的事情的現(xiàn)場,去接著完成沒有做完的事情
人的大腦是這樣工作的,其實芯片的邏輯也需要這樣執(zhí)行,我們知道芯片如何知道當(dāng)前程序的狀態(tài),無外乎幾個重要的寄存器,sp(程序指針寄存器),通用寄存器Rx,以及LR鏈接寄存器等等。有了這些信息,就可以知道程序當(dāng)前運行的狀態(tài)了,這個就是程序的現(xiàn)場。
對于armv7來說,寄存器可以分為以下幾種:
在rt-thread操作系統(tǒng)中,涉及到壓棧與出棧操作的有兩個地方,第一個是中斷的進(jìn)入與中斷處理完成后的退出,第二個是線程的切換。
3.簡單分析一下rt-thread線程棧的初始化
對于/bsp/qemu-vexpress-a9來說,系統(tǒng)上電后執(zhí)行rtt的第一行代碼在/libcpu/arm/cortex-a/start_gcc.S文件。
然后執(zhí)行_reset函數(shù),這個函數(shù)是匯編函數(shù)寫的,因為前期沒有棧空間,所以代碼需要采用匯編指令完成。
然后分配??臻g等等。執(zhí)行到rtt的其他部分邏輯。這里就不贅述了。這里主要分析的是線程的初始化。
每一個線程在初始化的時候,需要分配棧空間
rt_thread_create/rt_thread_init --> _rt_thread_init --> rt_hw_stack_init
最后調(diào)用到了/libcpu/arm/cortex-a/stack.c文件。
rt_uint8_t *rt_hw_stack_init(void *tentry, void *parameter, rt_uint8_t *stack_addr, void *texit) { rt_uint32_t *stk; stack_addr += sizeof(rt_uint32_t); stack_addr = (rt_uint8_t *)RT_ALIGN_DOWN((rt_uint32_t)stack_addr, 8); stk = (rt_uint32_t *)stack_addr; *(--stk) = (rt_uint32_t)tentry; /* entry point */ *(--stk) = (rt_uint32_t)texit; /* lr */ *(--stk) = 0xdeadbeef; /* r12 */ *(--stk) = 0xdeadbeef; /* r11 */ *(--stk) = 0xdeadbeef; /* r10 */ *(--stk) = 0xdeadbeef; /* r9 */ *(--stk) = 0xdeadbeef; /* r8 */ *(--stk) = 0xdeadbeef; /* r7 */ *(--stk) = 0xdeadbeef; /* r6 */ *(--stk) = 0xdeadbeef; /* r5 */ *(--stk) = 0xdeadbeef; /* r4 */ *(--stk) = 0xdeadbeef; /* r3 */ *(--stk) = 0xdeadbeef; /* r2 */ *(--stk) = 0xdeadbeef; /* r1 */ *(--stk) = (rt_uint32_t)parameter; /* r0 : argument */ /* cpsr */ if ((rt_uint32_t)tentry & 0x01) *(--stk) = SVCMODE | 0x20; /* thumb mode */ else *(--stk) = SVCMODE; /* arm mode */ #ifdef RT_USING_LWP *(--stk) = 0; /* user lr */ *(--stk) = 0; /* user sp*/ #endif #ifdef RT_USING_FPU *(--stk) = 0; /* not use fpu*/ #endif /* return task's current stack address */ return (rt_uint8_t *)stk; }
初始化線程的時候,每個線程都是有一個棧空間的,這個??臻g不僅僅保存一下參數(shù)變量,還在棧地址的首地址處保存了線程執(zhí)行需要的現(xiàn)場。而且每個線程都有一個獨立的棧內(nèi)存,這個內(nèi)存就是在棧的入口處。
當(dāng)線程發(fā)生切換的時候,需要取出這些寄存器
.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: #ifdef RT_USING_SMP /* r0 :svc_mod context * r1 :addr of from_thread's sp * r2 :addr of to_thread's sp * r3 :to_thread's tcb */ str r0, [r1] ldr sp, [r2] mov r0, r3 bl rt_cpus_lock_status_restore b rt_hw_context_switch_exit
執(zhí)行到rt_hw_context_switch_exit函數(shù)
.global rt_hw_context_switch_exit rt_hw_context_switch_exit: #ifdef RT_USING_SMP #ifdef RT_USING_SIGNALS mov r0, sp cps #Mode_IRQ bl rt_signal_check cps #Mode_SVC mov sp, r0 #endif #endif #ifdef RT_USING_FPU /* fpu context */ ldmfd sp!, {r6} vmsr fpexc, r6 tst r6, #(1<<30) beq 1f ldmfd sp!, {r5} vmsr fpscr, r5 vldmia sp!, {d16-d31} vldmia sp!, {d0-d15} 1: #endif #ifdef RT_USING_LWP ldmfd sp, {r13, r14}^ /* usr_sp, usr_lr */ add sp, #8 #endif ldmfd sp!, {r1} msr spsr_cxsf, r1 /* original mode */ ldmfd sp!, {r0-r12,lr,pc}^ /* irq return */
該函數(shù)可能看起來有些費勁,我來解釋一下大概的內(nèi)容:
當(dāng)線程間要從上一個線程切換到下一個線程的時候,首先會將切換之前現(xiàn)場保存起來,也就是將這些寄存器的知保存到內(nèi)存中,然后將sp指向下線程的地址。此時需要恢復(fù)下一個需要切換的線程的寄存器。
4.總結(jié)
如果需要理清楚rt-thread的??臻g的壓棧與入棧,其實最根本的問題就是如何去處理現(xiàn)場狀態(tài)的問題。也就是每個線程都需要有一個獨立的??臻g,然后這些??臻g除了保存數(shù)據(jù),還需要保存寄存器。當(dāng)進(jìn)行任務(wù)切換的時候,當(dāng)前線程的寄存器需要保存該線程的棧內(nèi)存中,而下個線程的??臻g則會從自己的??臻g的起始地址處恢復(fù)。這個就是rt-thread棧運作的實現(xiàn)邏輯。