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

2021-01-04 電子工程專輯
什麼是線程?

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

在裸機系統中, 系統的主體就是 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);
        
        rt_thread_delay(500);
    }
}


int main(void)
{
    rt_err_t result;  

    
    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);
            
            rt_thread_delay(500);
        }
    }
}


int main(void)
{
    rt_thread_t tid;  

    
    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編程中,何時使用動態空間,何時使用靜態空間,也需要根據實際情況平衡選擇。 

‧  END  

推薦閱讀:


關注公眾號『strongerHuang』,在後臺回復「1024」,查看更多精彩內容,回復「加群」,可加入黃工的技術交流群。


長按識別圖中二維碼關注

相關焦點

  • RT-Thread多線程學習後的總結
    關於多線程的使用和管理,RT-Thread官方提供了比較豐富的文檔作為參考,具體內容可以查看以下連結: https://www.rt-thread.org/document/site/programming-manual/thread/thread/ 本文是對RT-Thread多線程學習後的總結,並嘗試從如圖所示的以下幾個方面進行總結。
  • rt-thread內核對象標誌(object.flag)總結
    1 前言 在前面介紹rt-thread內核對象時(http://blog.csdn.net/flydream0?viewmode=contents),有介紹到rt-thread內核對象成員flag,但是沒有怎麼具體介紹他的含意,只是把它當做普通標誌來理解,這裡特意將此flag提出來總結一下,這也是這篇博文的目的. 首先,內核對象的flag確實是一標誌,且是8個位的數據,按位來定義的,那麼它到底各個位是怎麼定義的呢?答案是用在不同的內核對象時,它的各位定義會有所不同.
  • RISC-V單片機快速入門02-移植RT_Thread Nano
    一、基礎知識1.RT_Thread簡介RT-Thread是一個集實時作業系統(RTOS)內核、中間件組件和開發者社區於一體的技術平臺,由熊譜翔先生帶領併集合開源社區力量開發而成,RT-Thread/document/site/tutorial/nano/nano-port-gcc-riscv/an0042-nano-port-gcc-riscv/include &34;34;gd32vf103c_start.h&include &34;static rt_thread_t dynamic_thread = RT_NULL;void led_process_thread_entry(void
  • 一文詳解RT-Thread自動初始化
    官網文檔提及到了, (他們的文檔在這裡:https://www.rt-thread.org/document/site/programming-manual/basic/basic/#rt-thread_3),但是寫的只是概念層面上的,看完後會使用但原理還是不太清楚。之前研究過,今天把它總結下,寫出來分享。
  • RISC-V單片機快速入門03-基於RT_Thread Nano添加控制臺
    一、基礎知識1.FinS H簡介RT-Thread FinSH 是 RT-Thread 的命令行組件(shell),提供一套供用戶在命令行調用的操作接口,主要用於調試或查看系統信息。它可以使用串口 / 乙太網 / USB 等與 PC 機進行通信,使用 FinSH 組件基本命令的效果圖如下所示:
  • RT-Thread教程一之Linux下開發環境及QEMU配置
    RT-Thread原始碼Linux下安裝git後在工程目錄下(比~/Workplace)執行git clone https://github.com/RT-Thread/rt-thread我們看一下樹目錄結構rt-thread git:(master) tree . -L 1.
  • RT-Thread學習筆記+2.RTT的啟動分析
    然後在rt-thread\bsp\stm32\libraries目錄下,把其他用不到的庫文件也刪掉,只保留了F1的庫文件夾即可。最後強調一下:學習RTT,板子不是關鍵,不論是正點原子、野火、安富萊,還是F1、F4、F7都是可以的,不要糾結板子,最主要的了解RTT的使用方法。
  • RT-Thread Nano 3.1.3 正式發布
    從上圖中可以看出RT-Thread Nano的架構: 極簡版硬實時內核(可裁剪):內核包含線程管理、時鐘管理、中斷管理、內存管理、線程間同步與通信等功能,滿足輕量級作業系統的基本要求。從官網下載的 rtthread-nano 3.1.3 源碼壓縮包的目錄結構如下所示,基本與完整版的目錄保持一致: 文件夾名稱 說明 bsp 示例代碼 components/finsh FinSH 組件源碼,僅在移植FinSH時使用 docs 說明文檔 include 頭文件目錄 libcpu
  • 《rt-thread驅動框架分析》-i2c驅動
    軟體I2C驅動總結:rt-thread的軟體I2C,如果要對接其他平臺,只需要對接好結構體:struct rt_i2c_bit_ops。而軟體I2C的邏輯完全不用理會,全部由bit_opt層管理。= HAL_OK) { Error_Handler(); } return RT_EOK;}static rt_size_t i2c_xfer(struct rt_i2c_bus_device *bus, struct rt_i2c_msg msgs[], rt_uint32_t num){ struct rt_i2c_msg *msg; rt_int32_t
  • 玩轉RT-Thread之荔枝派Nano(全志F1C100S)一、新手上路
    github倉庫地址:https://github.com/RT-Thread,碼雲倉庫地址:https://gitee.com/rtthread。RT-Thread是率先完成荔枝派Nano移植的嵌入式實時RTOS作業系統,代碼已經合併進RT-Thread官方github倉庫,github地址為:https://github.com/RT-Thread/rt-thread/tree/master/bsp/allwinner_tina。
  • RISC-V單片機快速入門04-基於RT_Thread Nano添加FinSH
    一、基礎知識1.FinSH簡介RT-Thread FinSH 是 RT-Thread 的命令行組件(shell),提供一套供用戶在命令行調用的操作接口,主要用於調試或查看系統信息。它可以使用串口 / 乙太網 / USB 等與 PC 機進行通信,本文使用串口進行通信,使用 FinSH 組件基本命令的效果圖如下所示:
  • RT-Thread Studio開發環境搭建
    一、基礎知識RT-Thread Studio 主要包括工程創建和管理,代碼編輯,SDK管理,RT-Thread配置,構建配置,調試配置,程序下載和調試等功能,結合圖形化配置系統以及軟體包和組件資源,減少重複工作,提高開發效率。
  • 國產開源物聯網作業系統RT-Thread Smart正式上線(附原始碼下載地址)
    353EETC-電子工程專輯353EETC-電子工程專輯rt-smart內核即可包含基本功能,rt-smart用戶態應用環境採用musl libc提供POSIX接口調用及C運行環境,延續 RT-Thread 原有的生態,使用scons構建或其他的構建工具,例如xmake,cmake等,並對接 RT-Thread 在線軟體包;同時支持 POSIX,方便 Linux 應用的移植。
  • 國內自主物聯網作業系統 RT-Thread 3.0.1 發布
    平臺添加 menuconfig :使用 scons --memuconfig 啟用為 IAR 項目添加 LIBS 功能修復 filesystem_operation_table 問題新增用於 POSIX 兼容性的 mmap()/ munmap()API更多詳情可查閱發行說明:https://gitee.com/rtthread
  • RT-Thread Studio V1.1 新版本發布
    【官網下載地址】https://www.rt-thread.org/page/studio.html(或點擊文末閱讀原文下載) 由於篇幅有限,更多的功能和使用細節大家可以參閱RT-Thread Studio文檔中心的詳細介紹  : https://www.rt-thread.org
  • 在rt-thread下實現OTA在線固件更新功能
    Bootloader 在線獲取地址: http://iot.rt-thread.com登陸帳號在對固件進行打包操作前,先修改 stm32f407-atk-explorer/applications/main.c 中 APP_VERSION 宏的值為 2.0.0 作為參照,然後重新編譯一遍生成新的 rtthread.bin 文件,修改內容如下圖所示:
  • 基於RT-Thread 使用 wireshark 抓取 HTTPS 數據包
    ("random_c or master is null\n");21 return;22 }2324 host = (struct hostent *) gethostbyname(ip);25 if(host == RT_NULL)26 {27 rt_kprintf("Get host by name failed!
  • 線程的基本概念,實現多線程的四種基本方式
    (name + &34; + i); } }}public class ThreadDemo { public static void main(String[] args) { //定義線程 MyThread thread1 = new MyThread(&34;); MyThread
  • C/C++實戰028:多線程thread使用入門
    什麼是進程(Process)進程是指計算機中正在運行的程序,是系統進行資源分配和調度的基本單位。當一個程序被加載到內存運行時就會生成一個進程,作業系統在調度執行程序過程中會分配和管理資源。每個程序運行都會有一個獨立的進程,而且進程之間是相互獨立存在的。
  • 在父線程中ThreadLocal變量,怎樣在子線程中獲取?
    System.out.println(&34;+threadLocal.get()); } }); thread.start(); // 主線程輸出線程變量的值 System.out.println(&34;+threadLocal.get()); }}