【RT-Thread】線程的基本知識


什么是線程?

人們在生活中處理復雜問題時,慣用的方法就是分而治之,即把一個大問題分解成多個相對簡單、比較容易解決的小問題,小問題逐個被解決了,大問題也就隨之解決了。同樣,在設計一個較為復雜的應用程序時,也通常把一個大型任務分解成多個小任務,然后通過運行這些小任務,最終達到完成大任務的目的。

在裸機系統中, 系統的主體就是 main 函數里面順序執行的無限循環,這個無限循環里面 CPU 按照順序完成各種事情。在多線程系統中,我們根據功能的不同,把整個系統分割成一個個獨立的且無法返回的函數,這個函數我們稱為線程

線程由哪些部分組成?

RT-Thread 中的線程由三部分組成:線程代碼(函數)、線程控制塊、線程堆棧。

線程棧

在一個裸機系統中, 如果有全局變量,有子函數調用,有中斷發生。那么系統在運行的時候,全局變量放在哪里,子函數調用時,局部變量放在哪里, 中斷發生時,函數返回地址發哪里。

如果只是單純的裸機編程,它們放哪里我們不用管,但是如果要寫一個 RTOS,這些種種環境參數,我們必須弄清楚他們是如何存儲的。

在裸機系統中,他們統統放在一個叫棧的地方,棧是單片機 RAM 里面一段連續的內存空間,棧的大小一般在啟動文件或者鏈接腳本里面指定, 最后由 C 庫函數_main 進行初始化。

但是, 在多線程系統中,每個線程都是獨立的,互不干擾的,所以要為每個線程都分配獨立的棧空間,這個棧空間通常是一個預先定義好的全局數組, 也可以是動態分配的一段內存空間,但它們都存在於 RAM 中。 如:

static rt_uint8_t led_stack[512];

線程棧其實就是一個預先定義好的全局數據,數據類型為rt_uint8_t,大小我們設置為 512。 在 RT-Thread 中,凡是涉及到數據類型的地方, RTThread 都會將標准的 C 數據類型用 typedef 重新取一個類型名, 以“rt”前綴開頭。這些經過重定義的數據類型放在 rtdef.h ,如:

線程控制塊

在 RT-Thread 中,線程控制塊由結構體 struct rt_thread 表示,線程控制塊是操作系統用於管理線程的一個數據結構,它會存放線程的一些信息,例如優先級、線程名稱、線程狀態等,也包含線程與線程之間連接用的鏈表結構,線程等待事件集合等,詳細定義如下(在rtdef.h中定義):

為led線程定義一個線程控制塊:

static struct rt_thread led_thread; 

線程函數

線程控制塊中的 entry 是線程的入口函數,它是線程實現預期功能的函數。線程的入口函數由用戶設
計實現,一般有以下兩種代碼形式:

無限循環模式:

在實時系統中,線程通常是被動式的:這個是由實時系統的特性所決定的,實時系統通常總是等待外界事件的發生,而后進行相應的服務:

順序執行或有限次循環模式:

如簡單的順序語句、 do whlie() 或 for() 循環等,此類線程不會循環或不會永久循環,可謂是 “一次性”線程,一定會被執行完畢。在執行完畢后,線程將被系統自動刪除。

動態線程與靜態線程

我們的用戶線程有兩種創建方式,一種是靜態線程,另一種是動態線程。

創建靜態線程的函數:

返回值為錯誤代碼。

創建動態線程的函數:

返回值為線程控制塊 。

線程創建實例

創建一個靜態線程

1、確定線程棧

2、定義線程控制塊

3、創建線程函數。

#include <rtthread.h>
#include <rtdevice.h>
#include <board.h>

/* 靜態線程相關宏定義 */
#define THREAD_PRIORITY          25						/* 優先級       */
#define STACK_SIZE 				 512					/* 棧大小       */
#define TIMESLICE   			 5						/* 時間片       */

/* 線程三要素 */
static rt_uint8_t static_thread_stack[STACK_SIZE];		/* 線程棧       */
static struct rt_thread static_thread;					/* 線程控制塊   */
static void static_thread_entry(void* parameter);		/* 線程入口函數 */

/* 靜態線程入口函數 */ 
static void static_thread_entry(void* parameter)
{
    rt_uint32_t i = 0;
	rt_kprintf("This is static thread!\n");
    /* 無限循環*/
    while (1)
    {
        rt_kprintf("static thread count:%d \r\n", ++i);
        /* 等待0.5s,讓出cpu權限,切換到其他線程 */
        rt_thread_delay(500);
    }
}

/* 主函數 */
int main(void)
{
	rt_err_t result;  
	
	/* 創建靜態線程 : 優先級 25 ,時間片 5個系統滴答,線程棧512字節 */
    result = rt_thread_init(&static_thread,
                            "static_thread",
                            static_thread_entry, 
							RT_NULL,
                            (rt_uint8_t*)&static_thread_stack[0], 
							STACK_SIZE, 
							THREAD_PRIORITY, 
							TIMESLICE);
	
	/* 創建成功則啟動靜態線程 */
	if (result == RT_EOK)
    {
        rt_thread_startup(&static_thread);
    }
}

運行結果為:

可見,在T-Thread中創建一個線程需要線程棧、線程控制塊與線程函數這三要素。除此之外,需要設置一個線程優先級,因為RT-Thread的調度器是基於優先級的搶占式調度算法。還需要設置一個時間片參數,這個用於多個線程具有同等優先級的情況下,采用時間片的輪轉調度算法進行調度,這個值與時間節拍有關,每一秒的節拍數可在rtconfig.h里進行設置:

在這里我們只創建一個線程,所以時間片我們沒有用到,但也需要傳遞一個時間片的值給rt_thread_init函數。最后,在主函數里調用相關接口創建一個靜態線程,創建成功則啟動該線程。

創建一個動態線程

創建動態線程與創建靜態線程類似:

#include <rtthread.h>
#include <rtdevice.h>
#include <board.h>

/* 動態線程相關宏定義 */
#define THREAD_PRIORITY   		25						/* 優先級       */
#define STACK_SIZE 				512						/* 棧大小       */
#define TIMESLICE  				5						/* 時間片       */

/* 線程三要素 */
static rt_uint8_t dynamic_thread_stack[STACK_SIZE];		/* 線程棧       */
static struct rt_thread dynamic_thread;					/* 線程控制塊   */
static void dynamic_thread_entry(void* parameter);		/* 線程入口函數 */

/* 動態線程入口函數 */
static void dynamic_thread_entry(void* parameter)
{
    rt_uint32_t i;
    /* 無限循環*/
    while (1)
    {
        for (i = 0; i < 5; i++)
        {
            rt_kprintf("dynamic thread count:%d \r\n", i);
            /* 等待1s,讓出cpu權限,切換到其他線程 */
            rt_thread_delay(500);
        }
    }
}

/* 主函數 */
int main(void)
{
	rt_thread_t tid;  // 動態線程句柄
	
	/* 創建動態線程 : 優先級 25 ,時間片 5個系統滴答,線程棧512字節 */
	tid = rt_thread_create("dynamic_thread",
							dynamic_thread_entry,
							RT_NULL,
							STACK_SIZE,
							THREAD_PRIORITY,
							TIMESLICE);
	/* 創建成功則啟動動態線程 */
	if (tid != RT_NULL)
	{
		rt_thread_startup(tid);
	} 
}

運行結果:

靜態線程VS動態線程

上例中,從運行結果上看,是沒有任何差別的!那么,我們在實際中如何抉擇?

使用靜態線程時,必須先定義靜態的線程控制塊,並且定義好棧空間,然后調用rt_thread_init()函數來完成線程的初始化工作。采用這種方式,線程控制塊和堆棧占用的內存會放在 RW/ZI 段,這段空間在編譯時就已經確定,它不是可以動態分配的,所以不能被釋放,而只能使用 rt_thread_detach()函數將該線程控制塊從對象管理器中脫離。
使用動態定義方式 rt_thread_create()時, RT-Thread 會動態申請線程控制塊和堆棧空間。在編譯時,編譯器是不會感知到這段空間的,只有在程序運行時, RT-Thread 才會從系統堆中申請分配這段內存空間,當不需要使用該線程時,調用 rt_thread_delete()函數就會將這段申請的內存空間重新釋放到內存堆中。

這兩種方式各有利弊,靜態定義方式會占用 RW/ZI 空間,但是不需要動態分配內存,運行時效率較高,實時性較好。動態方式不會占用額外的 RW/ZI 空間,占用空間小,但是運行時需要動態分配內存,效率沒有靜態方式高。

總的來說,這兩種方式就是空間和時間效率的平衡,可以根據實際環境需求選擇采用具體的分配方式。就像C編程中,何時使用動態空間,何時使用靜態空間,也需要根據實際情況平衡選擇。


我的個人博客:https://zhengnianli.github.io/

我的微信公眾號:嵌入式大雜燴


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM