RT-Thread學習筆記1-啟動順序與執行緒建立

CrazyCatJack發表於2021-02-17


1. 啟動順序

  1. SystemInit()
  2. $Sub$$main()
  3. rtthread_startup()
  4. rt_application_init()
  5. main_thread_entry
  6. $Super$$main使用者主函式

2. 堆範圍

自由分配的記憶體(堆)起始地址為RAM的起始地址加上RW+ZI段後的地址區域。

編譯出的program size分為:

  1. Code: 程式碼段,存放程式的程式碼部分
  2. RO-data: 只讀資料段,存放程式中定義的常量
  3. RW-data: 讀寫資料段,存放初始化為非0值的全域性變數
  4. ZI-data: 0資料段,存放未初始化得全域性變數及初始化為0的變數

實際佔用空間情況為:

  1. RO Size包含了Code及RO-data,表示程式佔用flash空間的大小
  2. RW Size包含了RW-data及ZI-data,表示執行時佔用RAM的大小
  3. ROM Size包含了Code, RO Data和RW data,表示燒寫程式佔用flash空間的大小

板子上電後預設從flash啟動,啟動之後會將RW段中的RW-data(初始化的全域性變數)搬運到RAM中,但不會搬運RO段,即CPU的執行程式碼從flash中讀取,另外根據編譯器給出的ZI地址和大小,分配出ZI段,並將這塊RAM區域清零。動態記憶體堆為未使用的RAM空間,應用程式申請和釋放的記憶體都來自該空間
BsntdH.png

char *ptr;
ptr = rt_malloc(10);
if (ptr != RT_NULL)
{
    rt_memset(ptr, 0, 10);
    rt_kprintf("malloc success\n");
    rt_free(ptr);
    ptr = RT_NULL;
}

3. 執行緒建立

RT-Thread中,執行緒由三部分組成:執行緒程式碼(入口函式)、執行緒控制塊、執行緒堆疊

3.1 執行緒程式碼(入口函式)

無限迴圈結構
void thread_entry(void *parameter)
{
    while(1)
    {
        /* 等待事件發生 */
    
        /* 處理事件 */
    }
}

順序執行結構
void thread_entry(void *parameter)
{
    /* 事務1處理 */
    /* 事務2處理 */
    /* 事務3處理 */
}

3.2 執行緒控制塊

作業系統管理執行緒的一個資料結構。存放執行緒的一些資訊,比如優先順序、執行緒名稱、執行緒狀態等等,也包括執行緒與執行緒之間連線用的連結串列結構,執行緒等待時間集合等

struct rt_thread;
struct rt_thread *rt_thread_t;

3.3 執行緒棧

每個執行緒都有獨立的棧空間,執行緒切換時,系統會將當前執行緒的上下文儲存線上程棧中,當執行緒要恢復執行時,再從執行緒棧中讀取上下文資訊,恢復執行緒的執行。執行緒上下文是指執行緒執行時的環境,各個變數和資料包括所有的暫存器變數,堆疊資訊,記憶體資訊等。執行緒棧在形式上是一段連續的記憶體空間,可以通過定義一個陣列或者申請一段動態記憶體來作為執行緒的棧
建立執行緒:

建立靜態執行緒
rt_err_t rt_thread_init(struct rt_thread *thread,
const char *name,
void (*entry)(void *parameter),
void *parameter,
void *stack_start,
rt_uint32_t stack_size,
rt_uint8_t priority,
rt_uint32_t tick)

建立動態執行緒
rt_thread_t rt_thread_create(const char *name,
void (*entry(void *parameter),
void *parameter,
rt_uint32_t stack_size,
rt_uint8_t priority,
rt_uint32_t tick))

啟動執行緒
rt_err_t rt_thread_startup(rt_thread_t thread)
呼叫此函式後建立的執行緒會被加入到執行緒的就緒佇列,執行排程

rt_err_t thread_static_init()
{
    rt_err_t result;

    result = rt_thread_init(&thread,
        "test",
        thread_entry, RT_NULL,
        &thread_stack[0], sizeof(thread_stack),
        THREAD_PRIORITY, 10);

    if (result == RT_EOK)
        rt_thread_startup(&thread);
    else
        tc_stat(TC_STAT_END | TC_STAT_FAILED);

    return result;
}

int thread_dynamic_init()
{
    rt_thread_t tid;

    tid = rt_thread_create("test",
        thread_entry, RT_NULL,
        THREAD_STACK_SIZE, THREAD_PRIORITY, THREAD_TIMESLICE);
    if (tid != RT_NULL)
        rt_thread_startup(tid);
    else
        tc_stat(TC_STAT_END | TC_STAT_FAILED);

    return 0;
}

rt_thread_delay(15); // 根據時脈頻率決定。時脈頻率100HZ,那麼一次delay 10ms.此處就未150ms
rt_thread_sleep(15);
rt_thread_mdelay(15); // delay 15ms

區別:

  1. 資源分配形式不同:靜態執行緒的執行緒控制塊和執行緒棧是靜態分配的,而動態執行緒的這兩部分是執行時動態分配的
  2. 執行效率:如果堆空間是片外RAM,那麼動態執行緒的執行效率低於靜態執行緒。反之,如果都是片內RAM,則沒有差別

4. 系統滴答時鐘

心跳時鐘由硬體定時器的定時中斷產生。稱之為系統滴答或者時鐘節拍。其頻率需要根據CPU的處理能力來決定。始終街拍使得核心可以將執行緒延時若干個時鐘節拍,以及執行緒等待時間發生時,超時的依據。頻率越快,核心函式介入系統執行的概率越大,核心佔用的處理器時間就越長,系統的負荷就越大。頻率越小,時間處理精度又不夠。在stm32平臺上一般設定系統滴答頻率為100HZ,即每個滴答的時間是10ms。在rtconfig.h中的RT_TICK_PER_SECOND巨集,就是代表的HZ數

5. GPIO驅動架構操作IO

#include <rt_device.h>
IO初始化
void rt_pin_mode(rt_base_t pin, rt_base_t mode)
PIN_MODE_OUTPUT
PIN_MODE_INPUT
PIN_MODE_INPUT_PULLUP
PIN_MODE_INPUT_PULLDOWN
PIN_MODE_OUTPUT_OD

IO寫入
void rt_pin_write(rt_base_t pin, rt_base_t value)
PIN_HIGH
PIN_LOW

IO讀出
int rt_pin_read(rt_base_t pin)

首先通過看drv_gpio.c中的巨集,得知我們設定的晶片有多少個腳。再看__STM32_PIN(2, E, 4).那麼這裡傳入2,就表示要操作PE4引腳

使用msh中的命令:list_thread。列出當前所有執行緒的棧使用情況
BghJMD.png
可以先將執行緒棧大小設定一個固定值(比如2048),線上程執行時通過該命令檢視執行緒棧的使用情況,瞭解執行緒棧使用的實際情況,根據情況設定合理的大小。一般將執行緒棧最大使用量設定為70%

6. 執行緒優先順序 & 時間片

優先順序

分別描述了執行緒競爭處理器資源的能力和持有處理器時間長短的能力。RT-Thread最大支援256個優先順序,數值越小優先順序越高,0為最高優先順序,最低優先順序保留給空閒執行緒idle。可以通過rt_config.h中的RT_THREAD_PRIORITY_MAX巨集,修改最大支援的優先順序。針對STM32預設設定最大支援32個優先順序。具體應用中,執行緒總數不受限制,能建立的執行緒總數之和具體硬體平臺的記憶體有關

時間片

只有在相同優先順序的就緒態執行緒中起作用,時間片起到約束執行緒單次執行時長的作用,其單位是一個系統街拍(OS Tick)

優先順序搶佔排程

當有高優先順序執行緒處於就緒態後,就會發生任務排程

時間片輪詢排程

相同優先順序的執行緒,作業系統按照時間片大小輪流排程執行緒,時間片起到約束執行緒單次執行時長的作用。保證同優先順序任務輪流佔有處理器

7. 鉤子函式

空閒執行緒

特殊的系統執行緒,具有最低的優先順序。系統中無其他就緒執行緒可執行時,排程器將排程到空閒執行緒。空閒執行緒負責一些系統資源回收以及將一些處於關閉態的執行緒從執行緒排程列表中移除的動作。空閒執行緒在形式上是一個無限迴圈結構,且永遠不被掛起。在RT-Thread實時作業系統中空閒執行緒向使用者提供了鉤子函式,空閒執行緒鉤子函式可以在系統空閒的時候,執行一些非緊急事務,例如系統執行指示燈閃爍,CPU使用率統計等等

rt_err_t rt_thread_idle_sethook(void(*hook)(void))
rt_err rt_thread_idle_delhook(void(*hook)(void))

注意:

  1. 空閒執行緒是一個執行緒狀態永遠為就緒態的執行緒,所以鉤子函式中執行的相關程式碼必須保證空閒執行緒在任何時刻都不會被掛起,例如rt_thread_delay(), rt_sem_take()等可能會導致執行緒掛起的阻塞類函式,都不能再鉤子函式中呼叫。
  2. 空閒執行緒可以設定多個鉤子函式(有最大限制)

系統排程鉤子函式

系統上下文切換是最普遍的時間,如果使用者想知道在某一個時刻發生了什麼樣的執行緒切換,RT-Thread提供了一個系統排程鉤子函式,這個鉤子函式在系統進行任務切換時執行,通過這個鉤子函式,可以瞭解到系統任務排程時的資訊

rt_scheduler_sethook(void(*hook)(struct rt_thread *from, struct rt_thread *to))

參考文獻

  1. RT-Thread視訊中心核心入門
  2. RT-Thread文件中心

本文作者: CrazyCatJack

本文連結: https://www.cnblogs.com/CrazyCatJack/p/14408835.html

版權宣告:本部落格所有文章除特別宣告外,均採用 BY-NC-SA 許可協議。轉載請註明出處!

關注博主:如果您覺得該文章對您有幫助,可以點選文章右下角推薦一下,您的支援將成為我最大的動力!


相關文章