RT-Thread core

RT-Thread 內核介紹

在這裏插入圖片描述

RT-Thread 啓動流程

在這裏插入圖片描述

RT-Thread 程序內存分佈

程序運行之前,需要有文件實體被燒錄到 STM32 的 Flash 中,一般是 bin 或者 hex 文件,該被燒錄文件稱爲可執行映像文件。如圖 3-3 中左圖所示,是可執行映像文件燒錄到 STM32 後的內存分佈,它包含 RO 段和 RW 段兩個部分:其中 RO 段中保存了 Code、RO-data 的數據,RW 段保存了 RW-data 的數據,由於 ZI-data 都是 0,所以未包含在映像文件中。

STM32 在上電啓動之後默認從 Flash 啓動,啓動之後會將 RW 段中的 RW-data(初始化的全局變量)搬運到 RAM 中,但不會搬運 RO 段,即 CPU 的執行代碼從 Flash 中讀取,另外根據編譯器給出的 ZI 地址和大小分配出 ZI 段,並將這塊 RAM 區域清零。在這裏插入圖片描述
Keil 工程在編譯完之後,會有相應的程序所佔用的空間提示信息,如下所示:

linking…
Program Size: Code=48008 RO-data=5660 RW-data=604 ZI-data=2124
After Build - User command #1: fromelf --bin.\build\rtthread-stm32.axf–output rtthread.bin
“.\build\rtthread-stm32.axf” - 0 Error(s), 0 Warning(s).
Build Time Elapsed: 00:00:07
上面提到的 Program Size 包含以下幾個部分:

1)Code:代碼段,存放程序的代碼部分;

2)RO-data:只讀數據段,存放程序中定義的常量;

3)RW-data:讀寫數據段,存放初始化爲非 0 值的全局變量;

4)ZI-data:0 數據段,存放未初始化的全局變量及初始化爲 0 的變量;

編譯完工程會生成一個. map 的文件,該文件說明了各個函數佔用的尺寸和地址,在文件的最後幾行也說明了上面幾個字段的關係:

Total RO Size (Code + RO Data) 53668 ( 52.41kB)
Total RW Size (RW Data + ZI Data) 2728 ( 2.66kB)
Total ROM Size (Code + RO Data + RW Data) 53780 ( 52.52kB)
1)RO Size 包含了 Code 及 RO-data,表示程序佔用 Flash 空間的大小;

2)RW Size 包含了 RW-data 及 ZI-data,表示運行時佔用的 RAM 的大小;

3)ROM Size 包含了 Code、RO Data 以及 RW Data,表示燒寫程序所佔用的 Flash 空間的大小;

RT-Thread 內核對象模型

RT-Thread 採用內核對象管理系統來訪問 / 管理所有內核對象,內核對象包含了內核中絕大部分設施,這些內核對象可以是靜態分配的靜態對象,也可以是從系統內存堆中分配的動態對象。

通過這種內核對象的設計方式,RT-Thread 做到了不依賴於具體的內存分配方式,系統的靈活性得到極大的提高。

RT-Thread 內核對象包括:線程,信號量,互斥量,事件,郵箱,消息隊列和定時器,內存池,設備驅動等。對象容器中包含了每類內核對象的信息,包括對象類型,大小等。對象容器給每類內核對象分配了一個鏈表,所有的內核對象都被鏈接到該鏈表上,如圖 RT-Thread 的內核對象容器及鏈表如下圖所示:在這裏插入圖片描述

下圖則顯示了 RT-Thread 中各類內核對象的派生和繼承關係。

由對象控制塊 rt_object 派生出來的有:線程對象、內存池對象、定時器對象、設備對象和 IPC 對象(IPC:Inter-Process Communication,進程間通信。在 RT-Thread 實時操作系統中,IPC 對象的作用是進行線程間同步與通信);由 IPC 對象派生出信號量、互斥量、事件、郵箱與消息隊列、信號等對象。在這裏插入圖片描述

常見宏定義說明

RT-Thread 中經常使用一些宏定義,舉例 Keil 編譯環境下一些常見的宏定義:

1)rt_inline,定義如下,static 關鍵字的作用是令函數只能在當前的文件中使用;inline 表示內聯,用 static 修飾後在調用函數時會建議編譯器進行內聯展開。

#define rt_inline static __inline
2)RT_USED,定義如下,該宏的作用是向編譯器說明這段代碼有用,即使函數中沒有調用也要保留編譯。例如 RT-Thread 自動初始化功能使用了自定義的段,使用 RT_USED 會將自定義的代碼段保留。

#define RT_USED attribute((used))
3)RT_UNUSED,定義如下,表示函數或變量可能不使用,這個屬性可以避免編譯器產生警告信息。

#define RT_UNUSED attribute((unused))
4)RT_WEAK,定義如下,常用於定義函數,編譯器在鏈接函數時會優先鏈接沒有該關鍵字前綴的函數,如果找不到則再鏈接由 weak 修飾的函數。

#define RT_WEAK __weak
5)ALIGN(n),定義如下,作用是在給某對象分配地址空間時,將其存放的地址按照 n 字節對齊,這裏 n 可取 2 的冪次方。字節對齊的作用不僅是便於 CPU 快速訪問,同時合理的利用字節對齊可以有效地節省存儲空間。

#define ALIGN(n) attribute((aligned(n)))
6)RT_ALIGN(size,align),定義如下,作用是將 size 提升爲 align 定義的整數的倍數,例如,RT_ALIGN(13,4) 將返回 16。

#define RT_ALIGN(size, align) (((size) + (align) - 1) & ~((align) - 1))

線程管理

每個線程都有重要的屬性,如線程控制塊、線程棧、入口函數等在這裏插入圖片描述
RT-Thread 的線程調度器是搶佔式的,主要的工作就是從就緒線程列表中查找最高優先級線程,保證最高優先級的線程能夠被運行,最高優先級的任務一旦就緒,總能得到 CPU 的使用權。

線程棧的增長方向是芯片構架密切相關的,RT-Thread 3.1.0 以前的版本,均只支持棧由高地址向低地址增長的方式,對於 ARM Cortex-M 架構,線程棧可構造如下圖所示。
在這裏插入圖片描述

線程狀態
在這裏插入圖片描述
時間片
每個線程都有時間片這個參數,但時間片僅對優先級相同的就緒態線程有效。系統對優先級相同的就緒態線程採用時間片輪轉的調度方式進行調度時,時間片起到約束線程單次運行時長的作用,其單位是一個系統節拍(OS Tick)在這裏插入圖片描述
線程狀態切換
線程通過調用函數 rt_thread_create/init() 進入到初始狀態(RT_THREAD_INIT);初始狀態的線程通過調用函數 rt_thread_startup() 進入到就緒狀態(RT_THREAD_READY);就緒狀態的線程被調度器調度後進入運行狀態(RT_THREAD_RUNNING);當處於運行狀態的線程調用 rt_thread_delay(),rt_sem_take(),rt_mutex_take(),rt_mb_recv() 等函數或者獲取不到資源時,將進入到掛起狀態(RT_THREAD_SUSPEND);處於掛起狀態的線程,如果等待超時依然未能獲得資源或由於其他線程釋放了資源,那麼它將返回到就緒狀態。掛起狀態的線程,如果調用 rt_thread_delete/detach() 函數,將更改爲關閉狀態(RT_THREAD_CLOSE);而運行狀態的線程,如果運行結束,就會在線程的最後部分執行 rt_thread_exit() 函數,將狀態更改爲關閉狀態。
在這裏插入圖片描述

系統線程
在 RT-Thread 內核中的系統線程有空閒線程和主線程
空閒線程是系統創建的最低優先級的線程,線程狀態永遠爲就緒態。

空閒線程在 RT-Thread 也有着它的特殊用途:

若某線程運行完畢,系統將自動刪除線程:自動執行 rt_thread_exit() 函數,先將該線程從系統就緒隊列中刪除,再將該線程的狀態更改爲關閉狀態,不再參與系統調度,然後掛入 rt_thread_defunct 殭屍隊列(資源未回收、處於關閉狀態的線程隊列)中,最後空閒線程會回收被刪除線程的資源。

空閒線程也提供了接口來運行用戶設置的鉤子函數,在空閒線程運行時會調用該鉤子函數,適合鉤入功耗管理、看門狗喂狗等工作。

主線程
在系統啓動時,系統會創建 main 線程,它的入口函數爲 main_thread_entry(),用戶的應用入口函數 main() 就是從這裏真正開始的
在這裏插入圖片描述

線程的管理
下圖描述了線程的相關操作,包含:創建 / 初始化線程、啓動線程、運行線程、刪除 / 脫離線程。可以使用 rt_thread_create() 創建一個動態線程,使用 rt_thread_init() 初始化一個靜態線程,動態線程與靜態線程的區別是:動態線程是系統自動從動態內存堆上分配棧空間與線程句柄(初始化 heap 之後才能使用 create 創建動態線程),靜態線程是由用戶分配棧空間與線程句柄。
在這裏插入圖片描述

使線程讓出處理器資源
rt_err_t rt_thread_yield(void);
調用該函數後,當前線程首先把自己從它所在的就緒優先級線程隊列中刪除,然後把自己掛到這個優先級隊列鏈表的尾部,然後激活調度器進行線程上下文切換(如果當前優先級只有這一個線程,則這個線程繼續執行,不進行上下文切換動作)。

線程掛起使用下面的函數接口:
rt_err_t rt_thread_suspend (rt_thread_t thread);
通常不應該使用這個函數來掛起線程本身,如果確實需要採用 rt_thread_suspend() 函數掛起當前任務,需要在調用 rt_thread_suspend() 函數後立刻調用 rt_schedule() 函數進行手動的線程上下文切換。用戶只需要瞭解該接口的作用,不推薦使用該接口。

設置和刪除空閒鉤子
空閒鉤子函數是空閒線程的鉤子函數,如果設置了空閒鉤子函數,就可以在系統執行空閒線程時,自動執行空閒鉤子函數來做一些其他事情,比如系統指示燈。設置 / 刪除空閒鉤子的接口如下:
rt_err_t rt_thread_idle_sethook(void (*hook)(void));
rt_err_t rt_thread_idle_delhook(void (*hook)(void));
空閒線程是一個線程狀態永遠爲就緒態的線程,因此設置的鉤子函數必須保證空閒線程在任何時刻都不會處於掛起狀態,例如 rt_thread_delay(),rt_sem_take() 等可能會導致線程掛起的函數都不能使用。

設置調度器鉤子
在整個系統的運行時,系統都處於線程運行、中斷觸發 - 響應中斷、切換到其他線程,甚至是線程間的切換過程中,或者說系統的上下文切換是系統中最普遍的事件。有時用戶可能會想知道在一個時刻發生了什麼樣的線程切換,可以通過調用下面的函數接口設置一個相應的鉤子函數。在系統線程切換時,這個鉤子函數將被調用:
void rt_scheduler_sethook(void (hook)(struct rt_thread from, struct rt_thread* to));
請仔細編寫你的鉤子函數,稍有不慎將很可能導致整個系統運行不正常(在這個鉤子函數中,基本上不允許調用系統 API,更不應該導致當前運行的上下文掛起)。

關於刪除線程:大多數線程是循環執行的,無需刪除;而能運行完畢的線程,RT-Thread 在線程運行完畢後,自動刪除線程,在 rt_thread_exit() 裏完成刪除動作。用戶只需要瞭解該接口的作用,不推薦使用該接口(可以由其他線程調用此接口或在定時器超時函數中調用此接口刪除一個線程,但是這種使用非常少)。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章