从0到1写RT-Thread内核——线程定义及切换的实现
從0寫RT-Thread內(nèi)核之線程定義及切換的實現(xiàn)具體可以分為以下六步來實現(xiàn)
一:分別定義線程棧、線程函數(shù)、線程控制塊;
ALIGN(RT_ALIGN_SIZE)//設(shè)置4字節(jié)對齊 /* 定義線程棧 */ rt_uint8_t rt_flag1_thread_stack[512]; rt_uint8_t rt_flag2_thread_stack[512]; /* 線程1 */ void flag1_thread_entry( void *p_arg ) {for( ;; ){flag1 = 1;delay( 100 ); flag1 = 0;delay( 100 );/* 線程切換,這里是手動切換 */ rt_schedule();//此函數(shù)在下面介紹} }/* 線程2 */ void flag2_thread_entry( void *p_arg ) {for( ;; ){flag2 = 1;delay( 100 ); flag2 = 0;delay( 100 );/* 線程切換,這里是手動切換 */rt_schedule();//此函數(shù)在下面介紹} } /* 此結(jié)構(gòu)體在rtdef.h里定義 */ struct rt_thread {void *sp; /* 線程棧指針 */void *entry; /* 線程入口地址 */void *parameter; /* 線程形參 */ void *stack_addr; /* 線程棧起始地址 */rt_uint32_t stack_size; /* 線程棧大小,單位為字節(jié) */rt_list_t tlist; /* 線程鏈表節(jié)點(diǎn) */ }; typedef struct rt_thread *rt_thread_t;/* ************************************************************************* * 雙向鏈表結(jié)構(gòu)體 ************************************************************************* */ struct rt_list_node {struct rt_list_node *next; /* 指向后一個節(jié)點(diǎn) */struct rt_list_node *prev; /* 指向前一個節(jié)點(diǎn) */ }; typedef struct rt_list_node rt_list_t; /* 在main.c定義線程控制塊 */ struct rt_thread rt_flag1_thread; struct rt_thread rt_flag2_thread;二:用線程初始化函數(shù)rt_thread_init(函數(shù)體如下圖)調(diào)用rt_list_init函數(shù)初始化rt_list_t類型的節(jié)點(diǎn),其實就是將節(jié)點(diǎn)里面的next和prev這兩個節(jié)點(diǎn)指針指向節(jié)點(diǎn)本身,然后把上面第一步定義的線程棧、線程函數(shù)、線程控制塊這三部分聯(lián)系起來,實際上就是初始化線程控制塊,由該函數(shù)的函數(shù)體我們知道它還調(diào)用了rt_hw_stack_init初始化了線程的棧。
/* 初始化鏈表節(jié)點(diǎn)(在rtserver.h中定義) */ rt_inline void rt_list_init(rt_list_t *l) {l->next = l->prev = l; }?
三:定義就緒列表并把線程插入到就緒列表中(線程控制塊里有一個tlist成員,數(shù)據(jù)類型為rt_list_t,我們將線程插入就緒列表里面,就是通過將線程控制塊的tlist這個節(jié)點(diǎn)插入到就緒列表中來實現(xiàn)的。如果把就緒列表比作晾衣桿,線程是衣服,那tlist就是晾衣架,每個線程都自帶晾衣架,就是為了把自己掛在各種不同的鏈表中。)
/* 定義就緒列表 */ extern rt_list_t rt_thread_priority_table[RT_THREAD_PRIORITY_MAX];就緒列表的下標(biāo)對應(yīng)的是線程的優(yōu)先級,但是目前我們的線程還不支持優(yōu)先級,有關(guān)支持多優(yōu)先級的知識點(diǎn)我們后面會講到,所以flag1和flag2線程在插入到就緒列表的時候,可以隨便選擇插入的位置。我們這里將flag1線程插入到就緒列表下表為0的鏈表中,flag2線程插入到就緒列表下標(biāo)為1的鏈表中。/* 將線程1插入到就緒列表 */ rt_list_insert_before( &(rt_thread_priority_table[0]),&(rt_flag1_thread.tlist) );/* 將線程2插入到就緒列表 */ rt_list_insert_before( &(rt_thread_priority_table[1]),&(rt_flag2_thread.tlist) );四:調(diào)度器的初始化(初始化就緒列表里面各個不同優(yōu)先級的鏈表,其實就是將節(jié)點(diǎn)里面的next和prev這兩個節(jié)點(diǎn)指針指向節(jié)點(diǎn)本身)
/* 初始化系統(tǒng)調(diào)度器 */ void rt_system_scheduler_init(void) { register rt_base_t offset; /* 線程就緒列表初始化 */for (offset = 0; offset < RT_THREAD_PRIORITY_MAX; offset ++){rt_list_init(&rt_thread_priority_table[offset]);}/* 初始化當(dāng)前線程控制塊指針 */rt_current_thread = RT_NULL;/* 初始化線程休眠列表,當(dāng)線程創(chuàng)建好沒有啟動之前會被放入到這個列表 */rt_list_init(&rt_thread_defunct); }五:使用rt_system_scheduler_start()啟動調(diào)度器,手動指定第一個運(yùn)行的線程并切換到該線程中去(利用rt_hw_context_switch_to函數(shù)切換線程,該函數(shù)用于第一次線程的切換),該函數(shù)只是開啟中斷并設(shè)置中斷標(biāo)志位,真正實現(xiàn)線程切換是在中斷服務(wù)函數(shù)里。
/* 啟動系統(tǒng)調(diào)度器 */ void rt_system_scheduler_start(void) {register struct rt_thread *to_thread;/* 手動指定第一個運(yùn)行的線程 *//* rt_list_entry是一個已知一個結(jié)構(gòu)體里面成員的地址,反推出該結(jié)構(gòu)體的首地址的宏 *//* 此處是通過struct rt_thread里面tlist成員獲取該結(jié)構(gòu)體首地址,即獲取該線程控制塊的首地址 */to_thread = rt_list_entry(rt_thread_priority_table[0].next,struct rt_thread,tlist);rt_current_thread = to_thread;/* 切換到第一個線程,該函數(shù)在context_rvds.S中實現(xiàn),在rthw.h聲明,用于實現(xiàn)第一次任務(wù)切換。當(dāng)一個匯編函數(shù)在C文件中調(diào)用的時候,如果有形參,則執(zhí)行的時候會將形參傳人到CPU寄存器r0。*/rt_hw_context_switch_to((rt_uint32_t)&to_thread->sp); }六:定義一個void rt_schedule(void)函數(shù),并在其中調(diào)用rt_hw_context_switch()函數(shù)(該函數(shù)實現(xiàn)新老線程的切換,和第五點(diǎn)中的rt_hw_context_switch_to()一樣,都只是開啟中斷并設(shè)置中斷標(biāo)志位,真正的線程切換在中斷服務(wù)函數(shù)中實現(xiàn))
/* 系統(tǒng)調(diào)度 */ void rt_schedule(void) {struct rt_thread *to_thread;struct rt_thread *from_thread;/* 兩個線程輪流切換 */if( rt_current_thread == rt_list_entry( rt_thread_priority_table[0].next,struct rt_thread,tlist) ){from_thread = rt_current_thread;to_thread = rt_list_entry( rt_thread_priority_table[1].next,struct rt_thread,tlist);rt_current_thread = to_thread;}else{from_thread = rt_current_thread;to_thread = rt_list_entry( rt_thread_priority_table[0].next,struct rt_thread,tlist);rt_current_thread = to_thread; }/* 產(chǎn)生上下文切換 */rt_hw_context_switch((rt_uint32_t)&from_thread->sp,(rt_uint32_t)&to_thread->sp); }中斷服務(wù)函數(shù)PendSV_Handler的實現(xiàn)如下,該函數(shù)真正的實現(xiàn)了線程的切換
;/* ; *----------------------------------------------------------------------- ; * void PendSV_Handler(void); ; * 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 PROCEXPORT PendSV_Handler; 失能中斷,為了保護(hù)上下文切換不被中斷MRS r2, PRIMASKCPSID I; 獲取中斷標(biāo)志位,看看是否為0LDR r0, =rt_thread_switch_interrupt_flag ; 加載rt_thread_switch_interrupt_flag的地址到r0LDR r1, [r0] ; 加載rt_thread_switch_interrupt_flag的值到r1CBZ r1, pendsv_exit ; 判斷r1是否為0,為0則跳轉(zhuǎn)到pendsv_exit; r1不為0則清0MOV r1, #0x00STR r1, [r0] ; 將r1的值存儲到rt_thread_switch_interrupt_flag,即清0; 判斷rt_interrupt_from_thread的值是否為0LDR r0, =rt_interrupt_from_thread ; 加載rt_interrupt_from_thread的地址到r0LDR r1, [r0] ; 加載rt_interrupt_from_thread的值到r1CBZ r1, switch_to_thread ; 判斷r1是否為0,為0則跳轉(zhuǎn)到switch_to_thread; 第一次線程切換時rt_interrupt_from_thread肯定為0,則跳轉(zhuǎn)到switch_to_thread; ========================== 上文保存 ==============================; 當(dāng)進(jìn)入PendSVC Handler時,上一個線程運(yùn)行的環(huán)境即:; xPSR,PC(線程入口地址),R14,R12,R3,R2,R1,R0(線程的形參); 這些CPU寄存器的值會自動保存到線程的棧中,剩下的r4~r11需要手動保存MRS r1, psp ; 獲取線程棧指針到r1STMFD r1!, {r4 - r11} ;將CPU寄存器r4~r11的值存儲到r1指向的地址(每操作一次地址將遞減一次)LDR r0, [r0] ; 加載r0指向值到r0,即r0=rt_interrupt_from_threadSTR r1, [r0] ; 將r1的值存儲到r0,即更新線程棧sp; ========================== 下文切換 ============================== switch_to_threadLDR r1, =rt_interrupt_to_thread ; 加載rt_interrupt_to_thread的地址到r1; rt_interrupt_to_thread是一個全局變量,里面存的是線程棧指針SP的指針LDR r1, [r1] ; 加載rt_interrupt_to_thread的值到r1,即sp指針的指針LDR r1, [r1] ; 加載rt_interrupt_to_thread的值到r1,即spLDMFD r1!, {r4 - r11} ;將線程棧指針r1(操作之前先遞減)指向的內(nèi)容加載到CPU寄存器r4~r11MSR psp, r1 ;將線程棧指針更新到PSPpendsv_exit; 恢復(fù)中斷MSR PRIMASK, r2ORR lr, lr, #0x04 ; 確保異常返回使用的堆棧指針是PSP,即LR寄存器的位2要為1BX lr ; 異常返回,這個時候任務(wù)堆棧中的剩下內(nèi)容將會自動加載到xPSR,PC(任務(wù)入口地址),R14,R12,R3,R2,R1,R0(任務(wù)的形參); 同時PSP的值也將更新,即指向任務(wù)堆棧的棧頂。在ARMC3中,堆是由高地址向低地址生長的。; PendSV_Handler 子程序結(jié)束ENDP ALIGN 4END? ? ?通過上面六個步驟,我們已經(jīng)完成了線程切換的各個函數(shù),接下來的是我們main()函數(shù),先初始化調(diào)度器,然后分別初始化線程1和2并把兩者分別插入到就緒列表中的0號和1號鏈表,然后啟動系統(tǒng)調(diào)度器,啟動系統(tǒng)調(diào)度器之后就會進(jìn)行線程的第一次切換,切換到線程flag1的函數(shù)中去,在該函數(shù)的最后又會切換到線程flag2中去,然后線程flag2又會切換會線程flag1,一直這樣來回切換。
/************************************************************************* @brief main函數(shù)* @param 無* @retval 無** @attention*********************************************************************** */ int main(void) { /* 硬件初始化 *//* 將硬件相關(guān)的初始化放在這里,如果是軟件仿真則沒有相關(guān)初始化代碼 */ /* 調(diào)度器初始化 */rt_system_scheduler_init();/* 初始化線程 */rt_thread_init( &rt_flag1_thread, /* 線程控制塊 */flag1_thread_entry, /* 線程入口地址 */RT_NULL, /* 線程形參 */&rt_flag1_thread_stack[0], /* 線程棧起始地址 */sizeof(rt_flag1_thread_stack) ); /* 線程棧大小,單位為字節(jié) *//* 將線程插入到就緒列表 */rt_list_insert_before( &(rt_thread_priority_table[0]),&(rt_flag1_thread.tlist) );/* 初始化線程 */rt_thread_init( &rt_flag2_thread, /* 線程控制塊 */flag2_thread_entry, /* 線程入口地址 */RT_NULL, /* 線程形參 */&rt_flag2_thread_stack[0], /* 線程棧起始地址 */sizeof(rt_flag2_thread_stack) ); /* 線程棧大小,單位為字節(jié) *//* 將線程插入到就緒列表 */rt_list_insert_before( &(rt_thread_priority_table[1]),&(rt_flag2_thread.tlist) );/* 啟動系統(tǒng)調(diào)度器 */rt_system_scheduler_start(); }? ? ? 最后我們通過軟件仿真的效果如下圖,可以看到flag1和flag2兩個變量波形的占空比為1/4,剛好符合我們兩個線程函數(shù)來回切換的效果。
最后聲明一下,我這里只是對學(xué)習(xí)的知識點(diǎn)進(jìn)行總結(jié),本文章的大多數(shù)知識來自于野火公司出版的《RT-Thread 內(nèi)核實現(xiàn)與應(yīng)用開發(fā)實戰(zhàn)—基于STM32》,這本書非常不錯,有志學(xué)習(xí)RT-Thread物聯(lián)網(wǎng)操作系統(tǒng)的人可以考慮一下。
總結(jié)
以上是生活随笔為你收集整理的从0到1写RT-Thread内核——线程定义及切换的实现的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: arm-linux-gcc静态编译和动态
- 下一篇: crontab 每3个小时运行一次