加入星計劃,您可以享受以下權(quán)益:

  • 創(chuàng)作內(nèi)容快速變現(xiàn)
  • 行業(yè)影響力擴(kuò)散
  • 作品版權(quán)保護(hù)
  • 300W+ 專業(yè)用戶
  • 1.5W+ 優(yōu)質(zhì)創(chuàng)作者
  • 5000+ 長期合作伙伴
立即加入
  • 正文
    • 一、前言
    • 二、線程介紹
    • 三、編程講解
    • 四、項目實戰(zhàn)
  • 推薦器件
  • 相關(guān)推薦
  • 電子產(chǎn)業(yè)圖譜
申請入駐 產(chǎn)業(yè)圖譜

RT-Thread零基礎(chǔ)快速入門第6講——線程管理

03/26 07:57
3196
閱讀需 24 分鐘
加入交流群
掃碼加入
獲取工程師必備禮包
參與熱點資訊討論

一、前言

從這一講開始,我們進(jìn)入RT-thread內(nèi)核的學(xué)習(xí),這是操作系統(tǒng)和裸機(jī)的區(qū)別,也是操作系統(tǒng)的核心所在,關(guān)于內(nèi)核的基礎(chǔ)我就不介紹了,大家可以先去官網(wǎng)上了解一下什么是內(nèi)核。我這一講重點講解內(nèi)核的線程管理,關(guān)于內(nèi)核的其他內(nèi)容我后續(xù)會接著講。

事先說明,本人也是剛接觸RT-thread不久,有些理解可能還比較顯淺,如果大家發(fā)現(xiàn)有什么錯誤,請一定要指正,謝謝!??!

然后就是文中有一些概念的定義和描述我是直接在官網(wǎng)上面抄過來的,因為我覺得官網(wǎng)上有些有些概念寫的非常清晰明了,我自己去描述的話可能還會帶大家繞彎。

內(nèi)核基礎(chǔ)知識:https://www.rt-thread.org/document/site/programming-manual/basic/basic/#

源碼鏈接

我發(fā)布的所有關(guān)于RT-thread的教程源代碼都在下面這個鏈接里面,隨著我教程的更新,新的代碼也會加入進(jìn)去。

教程源碼下載鏈接:https://pan.baidu.com/s/1N2D8dM31deKIqNqaIQfPiA
提取碼:7nsx

二、線程介紹

什么是線程

玩過單片機(jī)的同學(xué)應(yīng)該都知道,裸機(jī)程序在運(yùn)行完啟動程序之后總是會從main函數(shù)開始執(zhí)行用戶的程序。

我們這里假設(shè)有這么一份代碼,main函數(shù)里面有一個while循環(huán),里面調(diào)用了兩個函數(shù),函數(shù)A和函數(shù)B,main函數(shù)以外有一個中斷服務(wù)函數(shù)C。函數(shù)A是一個溫濕度采集函數(shù),函數(shù)B是LCD顯示函數(shù),顯示溫濕度的值,函數(shù)C是按鍵的中斷服務(wù)函數(shù),按鍵按下的時候停止溫濕度的采集。其實這三個函數(shù)我們就可以把它看作是三個線程,函數(shù)A和函數(shù)B是優(yōu)先級等同的兩個線程,正常運(yùn)行的時候,先運(yùn)行函數(shù)A,再運(yùn)行函數(shù)B,如此循環(huán),如果中斷觸發(fā)了,優(yōu)先級更高的函數(shù)C就會被優(yōu)先處理,整個程序運(yùn)行的流程其實就體現(xiàn)了單線程的線程管理和調(diào)度。

RT-thread的線程管理其實也是一樣的,線程是實現(xiàn)任務(wù)的載體,它是 RT-Thread 中最基本的調(diào)度單位,它描述了一個任務(wù)執(zhí)行的運(yùn)行環(huán)境,也描述了這個任務(wù)所處的優(yōu)先等級,重要的任務(wù)可設(shè)置相對較高的優(yōu)先級,非重要的任務(wù)可以設(shè)置較低的優(yōu)先級,不同的任務(wù)還可以設(shè)置相同的優(yōu)先級,輪流運(yùn)行。

線程的優(yōu)點

到這里,不知道大家有沒有一個疑惑,既然裸機(jī)程序函數(shù)的調(diào)度相當(dāng)于線程的調(diào)度,那為什么裸機(jī)的程序沒有線程這個概念呢?它們運(yùn)行的時候本質(zhì)都是一樣的呀,為什么很多人都說操作系統(tǒng)的運(yùn)行效率更高呢?

我的理解是這樣的,裸機(jī)的函數(shù)調(diào)度的方式比較單一,就是不斷循環(huán)的調(diào)用這些函數(shù),函數(shù)的優(yōu)先級也不多。而操作系統(tǒng)的每一個線程都可以設(shè)置優(yōu)先級,我們可以根據(jù)需要把重要的任務(wù)分配更高的優(yōu)先級,從而使得線程的調(diào)度不是單一的死循環(huán)調(diào)度,在多線程調(diào)度的時候,操作系統(tǒng)的存在使得代碼的運(yùn)行效率大大的提高。

舉個例子,有一個函數(shù)A,里面有多個延時函數(shù),有幾ms的,也有幾十ms的,那么這個函數(shù)A如果在裸機(jī)的程序里面跑的時候必須規(guī)規(guī)矩矩等待延時結(jié)束再往下跑,而如果是在RT-thread操作系統(tǒng)里面跑的話,當(dāng)執(zhí)行延時函數(shù)的時候,函數(shù)A的線程會被掛起,然后執(zhí)行其他線程里面優(yōu)先級最高的,等延時時間到了再返回,繼續(xù)在函數(shù)A往下跑。這樣一來,當(dāng)代碼量比較龐大,函數(shù)關(guān)系復(fù)雜的時候,操作系統(tǒng)的優(yōu)勢就體現(xiàn)出來了。

線程棧

RT-Thread 線程具有獨立的棧,當(dāng)進(jìn)行線程切換時,會將當(dāng)前線程的上下文存在棧中,當(dāng)線程要恢復(fù)運(yùn)行時,再從棧中讀取上下文信息,進(jìn)行恢復(fù)。

線程棧還用來存放函數(shù)中的局部變量:函數(shù)中的局部變量從線程??臻g中申請;函數(shù)中局部變量初始時從寄存器中分配(ARM 架構(gòu)),當(dāng)這個函數(shù)再調(diào)用另一個函數(shù)時,這些局部變量將放入棧中。

線程的類型

RT-thread的線程有兩種類型,分別是系統(tǒng)線程和用戶線程.

系統(tǒng)線程是由 RT-Thread 內(nèi)核創(chuàng)建的線程

用戶線程是由我們自己創(chuàng)建的線程,當(dāng)創(chuàng)建的線程被啟動之后就會加入任務(wù)的調(diào)度。

線程的狀態(tài)

線程在運(yùn)行的時候有多種狀態(tài),具體如下表所示:

在這里插入圖片描述

線程的優(yōu)先級

因為RT-Thread 的線程調(diào)度器是搶占式的,所以線程的優(yōu)先級非常重要。RT-Thread在運(yùn)行的時候會先從就緒線程列表中查找最高優(yōu)先級的線程運(yùn)行,運(yùn)行完了或者需要延時的時候會讓出cpu的使用權(quán),讓就緒線程列表中該線程以外的優(yōu)先級最高的線程運(yùn)行。

RT-Thread 最大支持 256 個線程優(yōu)先級 (0~255),數(shù)值越小的優(yōu)先級越高,0 為最高優(yōu)先級。在一些資源比較緊張的系統(tǒng)中,可以根據(jù)實際情況選擇只支持 8 個或 32 個優(yōu)先級的系統(tǒng)配置;對于 ARM Cortex-M 系列,普遍采用 32 個優(yōu)先級。最低優(yōu)先級默認(rèn)分配給空閑線程使用,用戶一般不使用。在系統(tǒng)中,當(dāng)有比當(dāng)前線程優(yōu)先級更高的線程就緒時,當(dāng)前線程將立刻被換出,高優(yōu)先級線程搶占處理器運(yùn)行。

線程時間片

每個線程都有時間片這個參數(shù),但時間片僅對優(yōu)先級相同的就緒態(tài)線程有效。系統(tǒng)對優(yōu)先級相同的就緒態(tài)線程采用時間片輪轉(zhuǎn)的調(diào)度方式進(jìn)行調(diào)度時,時間片起到約束線程單次運(yùn)行時長的作用。

假設(shè)有 2 個優(yōu)先級相同的就緒態(tài)線程 A 與 B,A 線程的時間片設(shè)置為 10,B 線程的時間片設(shè)置為 5,那么當(dāng)系統(tǒng)中不存在比 A 優(yōu)先級高的就緒態(tài)線程時,系統(tǒng)會在 A、B 線程間來回切換執(zhí)行,并且每次對 A 線程執(zhí)行 10 個節(jié)拍的時長,對 B 線程執(zhí)行 5 個節(jié)拍的時長。

線程入口函數(shù)

我前面也說過了,一個函數(shù)可以當(dāng)做是一個線程,在RT-Thread實際的操作中也是一樣,我們每創(chuàng)建一個線程,就必須創(chuàng)建一個線程的入口函數(shù)。

線程的入口函數(shù)一般有以下兩種代碼形式:

無限循環(huán)模式:

使用這種模式的時候需要注意的是,該線程一旦被運(yùn)行,就會導(dǎo)致優(yōu)先級比它低的線程一直不能夠被調(diào)度,因為這個線程本身是一個死循環(huán),它能夠一直在低優(yōu)先級線程前面被調(diào)度。

如果我們需要死循環(huán)但是又不需要一直死循環(huán)的時候,可以把這個線程配置較低的優(yōu)先級,或者在函數(shù)里面調(diào)用延時函數(shù),從而使得該線程能夠讓出cpu使用權(quán)。

void thread_entry(void* paramenter)
{
    while (1)
    {
    /* 等待事件的發(fā)生 */

    /* 對事件進(jìn)行服務(wù)、進(jìn)行處理 */
    }
}

順序執(zhí)行或有限次循環(huán)模式

此類線程不會循環(huán)或不會永久循環(huán),可謂是 “一次性” 線程,一定會被執(zhí)行完畢。而且在執(zhí)行完畢后,線程將被系統(tǒng)自動刪除。比如下面這種簡單的順序語句或者do whlie()、for()循環(huán)等。

static void thread_entry(void* parameter)
{
    /* 處理事務(wù) #1 *//* 處理事務(wù) #2 *//* 處理事務(wù) #3 */
}

線程錯誤碼

一個線程就是一個執(zhí)行場景,錯誤碼是與執(zhí)行環(huán)境密切相關(guān)的,所以每個線程配備了一個變量用于保存錯誤碼,線程的錯誤碼有以下幾種。

#define RT_EOK           0 /* 無錯誤     */
#define RT_ERROR         1 /* 普通錯誤     */
#define RT_ETIMEOUT      2 /* 超時錯誤     */
#define RT_EFULL         3 /* 資源已滿     */
#define RT_EEMPTY        4 /* 無資源     */
#define RT_ENOMEM        5 /* 無內(nèi)存     */
#define RT_ENOSYS        6 /* 系統(tǒng)不支持     */
#define RT_EBUSY         7 /* 系統(tǒng)忙     */
#define RT_EIO           8 /* IO 錯誤       */
#define RT_EINTR         9 /* 中斷系統(tǒng)調(diào)用   */
#define RT_EINVAL       10 /* 非法參數(shù)      */

線程狀態(tài)切換

RT-Thread 提供一系列的操作系統(tǒng)調(diào)用接口,使得線程的狀態(tài)在這五個狀態(tài)之間來回切換。幾種狀態(tài)間的轉(zhuǎn)換關(guān)系如下圖所示:

在這里插入圖片描述

1:線程通過調(diào)用函數(shù) rt_thread_create/init() 進(jìn)入到初始狀態(tài)(RT_THREAD_INIT)。

2:初始狀態(tài)的線程通過調(diào)用函數(shù) rt_thread_startup() 進(jìn)入到就緒狀態(tài)(RT_THREAD_READY);

3:就緒狀態(tài)的線程被調(diào)度器調(diào)度后進(jìn)入運(yùn)行狀態(tài)(RT_THREAD_RUNNING)。

4:當(dāng)處于運(yùn)行狀態(tài)的線程調(diào)用 rt_thread_delay(),rt_sem_take(),rt_mutex_take(),rt_mb_recv() 等函數(shù)或者獲取不到資源時,將進(jìn)入到掛起狀態(tài)(RT_THREAD_SUSPEND)。

5:掛起狀態(tài)的線程,如果等待超時依然未能獲得資源或由于其他線程釋放了資源,那么它將返回到就緒狀態(tài)。

6:掛起狀態(tài)的線程,如果調(diào)用 rt_thread_delete/detach() 函數(shù),將更改為關(guān)閉狀態(tài)(RT_THREAD_CLOSE)。

7:運(yùn)行狀態(tài)的線程,如果運(yùn)行結(jié)束,就會在線程的最后部分執(zhí)行 rt_thread_exit() 函數(shù),將狀態(tài)更改為關(guān)閉狀態(tài)。

#1:線程通過調(diào)用函數(shù) rt_thread_create/init() 進(jìn)入到初始狀態(tài)(RT_THREAD_INIT)。

2:初始狀態(tài)的線程通過調(diào)用函數(shù) rt_thread_startup() 進(jìn)入到就緒狀態(tài)(RT_THREAD_READY);

3:就緒狀態(tài)的線程被調(diào)度器調(diào)度后進(jìn)入運(yùn)行狀態(tài)(RT_THREAD_RUNNING)。

4:當(dāng)處于運(yùn)行狀態(tài)的線程調(diào)用 rt_thread_delay(),rt_sem_take(),rt_mutex_take(),rt_mb_recv() 等函數(shù)或者獲取不到資源時,將進(jìn)入到掛起狀態(tài)(RT_THREAD_SUSPEND)。

5:掛起狀態(tài)的線程,如果等待超時依然未能獲得資源或由于其他線程釋放了資源,那么它將返回到就緒狀態(tài)。

6:掛起狀態(tài)的線程,如果調(diào)用 rt_thread_delete/detach() 函數(shù),將更改為關(guān)閉狀態(tài)(RT_THREAD_CLOSE)。

7:運(yùn)行狀態(tài)的線程,如果運(yùn)行結(jié)束,就會在線程的最后部分執(zhí)行 rt_thread_exit() 函數(shù),將狀態(tài)更改為關(guān)閉狀態(tài)。

系統(tǒng)線程

系統(tǒng)線程是指由系統(tǒng)創(chuàng)建的線程,用戶線程是由用戶程序調(diào)用線程管理接口創(chuàng)建的線程,在 RT-Thread 內(nèi)核中的系統(tǒng)線程有空閑線程和主線程。

空閑線程

空閑線程是系統(tǒng)創(chuàng)建的最低優(yōu)先級的線程,線程狀態(tài)永遠(yuǎn)為就緒態(tài)。當(dāng)系統(tǒng)中無其他就緒線程存在時,調(diào)度器將調(diào)度到空閑線程,它通常是一個死循環(huán),且永遠(yuǎn)不能被掛起。關(guān)于空閑線程在 RT-Thread的特殊用途,我在以后的教程里面再說。

主線程

在系統(tǒng)啟動時,系統(tǒng)會創(chuàng)建 main 線程,用戶可以在 main() 函數(shù)里添加自己的應(yīng)用程序初始化代碼。但是在實際的項目運(yùn)用中,為了方便管理每一個功能模塊,我建議大家最好不要在main() 函數(shù)里面寫應(yīng)用的代碼,原因我這里不多說了,在后續(xù)的教程中我會一一講解。

三、編程講解
我這里只介紹線程最常用的最基本的用法,如果你們還想學(xué)習(xí)進(jìn)階的用法,可以到官網(wǎng)上查閱相關(guān)的資料。

第一步: 創(chuàng)建/初始化線程

創(chuàng)建線程創(chuàng)建的是一個動態(tài)線程,即線程??臻g的內(nèi)存是由系統(tǒng)自由分配的。初始化線程創(chuàng)建的是一個靜態(tài)線程,線程??臻g的內(nèi)存由用戶自己去指定。

創(chuàng)建線程函數(shù)如下圖所示:

系統(tǒng)線程

系統(tǒng)線程是指由系統(tǒng)創(chuàng)建的線程,用戶線程是由用戶程序調(diào)用線程管理接口創(chuàng)建的線程,在 RT-Thread 內(nèi)核中的系統(tǒng)線程有空閑線程和主線程。

空閑線程:

空閑線程是系統(tǒng)創(chuàng)建的最低優(yōu)先級的線程,線程狀態(tài)永遠(yuǎn)為就緒態(tài)。當(dāng)系統(tǒng)中無其他就緒線程存在時,調(diào)度器將調(diào)度到空閑線程,它通常是一個死循環(huán),且永遠(yuǎn)不能被掛起。關(guān)于空閑線程在 RT-Thread的特殊用途,我在以后的教程里面再說。

主線程:

在系統(tǒng)啟動時,系統(tǒng)會創(chuàng)建 main 線程,用戶可以在 main() 函數(shù)里添加自己的應(yīng)用程序初始化代碼。但是在實際的項目運(yùn)用中,為了方便管理每一個功能模塊,我建議大家最好不要在main() 函數(shù)里面寫應(yīng)用的代碼,原因我這里不多說了,在后續(xù)的教程中我會一一講解。

三、編程講解

我這里只介紹線程最常用的最基本的用法,如果你們還想學(xué)習(xí)進(jìn)階的用法,可以到官網(wǎng)上查閱相關(guān)的資料。

第一步: 創(chuàng)建/初始化線程

創(chuàng)建線程創(chuàng)建的是一個動態(tài)線程,即線程??臻g的內(nèi)存是由系統(tǒng)自由分配的。初始化線程創(chuàng)建的是一個靜態(tài)線程,線程棧空間的內(nèi)存由用戶自己去指定。

創(chuàng)建線程函數(shù)如下圖所示:

在這里插入圖片描述

編程示例:

/* 創(chuàng)建線程:led0 */
rt_thread_t thread1 = rt_thread_create("led0",       //線程名稱
                                       led0_entry,   //線程入口函數(shù)
                                       RT_NULL,      //線程入口函數(shù)參數(shù)
                                       1024,         //線程棧大小
                                       25,           //優(yōu)先級
                                       10);          //時間片

初始化線程函數(shù)的定義如下圖所示:

在這里插入圖片描述
編程示例:

/* 靜態(tài)線程參數(shù)定義 */
ALIGN(RT_ALIGN_SIZE)
static char led1_stack[1024];  //線程棧內(nèi)存空間
static struct rt_thread led1;  //線程句柄

/* 創(chuàng)建線程:led1 */
rt_thread_init(&led1,                    //線程句柄              
               "led1",                   //線程名稱
               led1_entry,               //線程入口函數(shù)
               RT_NULL,                  //線程入口函數(shù)參數(shù)
               &led1_stack[0],           //線程棧起始地址
               sizeof(led1_stack),       //線程棧大小
               THREAD_PRIORITY - 1,      //優(yōu)先級
               THREAD_TIMESLICE);        //時間片

第二步:啟動線程

在這里插入圖片描述
編程示例:

/* 創(chuàng)建線程:led0 */
rt_thread_t thread1 = rt_thread_create("led0",       //線程名稱
                                       led0_entry,   //線程入口函數(shù)
                                       RT_NULL,      //線程入口函數(shù)參數(shù)
                                       1024,         //線程棧大小
                                       25,           //優(yōu)先級
                                       10);          //時間片
/* 如果線程創(chuàng)建成功,啟動這個線程 */
if (thread1 != RT_NULL)
{
    rt_thread_startup(thread1);
}

第三步:編寫線程入口函數(shù)

線程的入口函數(shù)是由用戶自己定義的,我們只要保證入口函數(shù)的函數(shù)名和創(chuàng)建線程時所用的函數(shù)名一致即可。
編程示例:


```c
void led0_entry(void *parameter)
{
    while(1)
    {
        rt_pin_write(LED0_PIN, PIN_LOW);
        rt_thread_mdelay(1000);
        rt_pin_write(LED0_PIN, PIN_HIGH);
        rt_thread_mdelay(1000);
    }
}

四、項目實戰(zhàn)

我這里在main函數(shù)里面創(chuàng)建了一個動態(tài)和一個靜態(tài)線程,分別讓led0和led1閃爍。其實在實際的項目應(yīng)用中,一般不會在main函數(shù)加入很多應(yīng)用的代碼,而是用命令的方式把函數(shù)加入應(yīng)用列表,這個我在下一講才會介紹,所以這里就先這樣寫吧。

代碼如下:

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

#define LED0_PIN    GET_PIN(F, 9)
#define LED1_PIN    GET_PIN(F, 10)

#define THREAD_PRIORITY         25        //線程優(yōu)先級
#define THREAD_TIMESLICE        5         //線程時間片

/* 靜態(tài)線程參數(shù)定義 */
ALIGN(RT_ALIGN_SIZE)
static char led1_stack[1024];  //線程棧內(nèi)存空間
static struct rt_thread led1;  //線程句柄

/* led0線程入口函數(shù) */
void led0_entry(void *parameter)
{
    rt_pin_write(LED0_PIN, PIN_LOW);
    rt_thread_mdelay(1000);
    rt_pin_write(LED0_PIN, PIN_HIGH);
    rt_thread_mdelay(1000);
}

/* led1線程入口函數(shù) */
void led1_entry(void *parameter)
{
    rt_pin_write(LED1_PIN, PIN_LOW);
    rt_thread_mdelay(300);
    rt_pin_write(LED1_PIN, PIN_HIGH);
    rt_thread_mdelay(300);
}

int main(void)
{
    int i = 0;

    /* 把LED引腳設(shè)置為輸出 */
    rt_pin_mode(LED0_PIN, PIN_MODE_OUTPUT);
    rt_pin_mode(LED1_PIN, PIN_MODE_OUTPUT);

    /* 創(chuàng)建線程:led0 */
    rt_thread_t thread1 = rt_thread_create("led0",       //線程名稱
                                           led0_entry,   //線程入口函數(shù)
                                           RT_NULL,      //線程入口函數(shù)參數(shù)
                                           1024,         //線程棧大小
                                           25,           //優(yōu)先級
                                           10);          //時間片
    /* 如果線程創(chuàng)建成功,啟動這個線程 */
    if (thread1 != RT_NULL)
    {
        rt_thread_startup(thread1);
    }

    /* 創(chuàng)建線程:led1 */
    rt_thread_init(&led1,                    //線程句柄              
                   "led1",                   //線程名稱
                   led1_entry,               //線程入口函數(shù)
                   RT_NULL,                  //線程入口函數(shù)參數(shù)
                   &led1_stack[0],           //線程棧起始地址
                   sizeof(led1_stack),       //線程棧大小
                   THREAD_PRIORITY - 1,      //優(yōu)先級
                   THREAD_TIMESLICE);        //時間片
    rt_thread_startup(&led1);

    while (1)
    {
        rt_thread_mdelay(100);
    }
}

五、結(jié)束語

線程在RT-thread項目的實戰(zhàn)中運(yùn)用非常廣泛,因此,希望大家好好看一下線程相關(guān)的內(nèi)容,了解更多線程的用法。

好了,關(guān)于線程管理的編程講解就到這里,如果還有什么問題可以私信給我。如果需要本文對應(yīng)的源碼的話可以在博文前言部分的鏈接下載。

如果覺得這篇文章對你有用,點贊+關(guān)注支持一下博主唄。

后續(xù)我會繼續(xù)更新RT-thread入門教程系列,如果感興趣的同學(xué)可以關(guān)注一下博主,謝謝!

推薦器件

更多器件
器件型號 數(shù)量 器件廠商 器件描述 數(shù)據(jù)手冊 ECAD模型 風(fēng)險等級 參考價格 更多信息
AD73311ARSZ 1 Analog Devices Inc Single-Channel, 3 V and 5 V Front-End Processor for General Purpose Applications Including Speech and Telephony

ECAD模型

下載ECAD模型
$10.72 查看
S25FL512SAGBHIA13 1 Spansion Flash, 128MX4, PBGA24, FBGA-24
$59.58 查看
ASEM1-24.000MHZ-LC-T 1 Abracon Corporation MEMS OSC XO 24.0000MHZ CMOS SMD
$2.01 查看

相關(guān)推薦

電子產(chǎn)業(yè)圖譜