隱隱感覺自己要把 一起來學rt-thread 抄一遍了
官方網站
查看代碼
以stm32f10x爲例 使用keil mdk 下載源碼進入\bsp\stm32f10x目錄 打開工程
啓動過程
在startup.c中找到main函數 如下
int main(void)
{
/* disable interrupt first */
rt_hw_interrupt_disable();
/* startup RT-Thread RTOS */
rtthread_startup();
return 0;
}
第一行 關中斷操作
第二行 啓動rt-thread
第三行 見鬼才能執行到的代碼
第二行代碼的rtthread_startup()函數如下
/**
* This function will startup RT-Thread RTOS.
*/
void rtthread_startup(void)
{
/* 初始化硬件平臺相關:時鐘設置、中斷設置、系統滴答設置、串口設置 */
rt_hw_board_init();
up_mcu_show();
/* 打印 RT-Thread 版本信息 */
rt_show_version();
/* init tick */
rt_system_tick_init();
/* 內核對象初始化 */
rt_system_object_init();
/* 系統定時器初始化 */
rt_system_timer_init();
/* 如果使用動態內存分配,則配置之 */
#ifdef RT_USING_HEAP
#if STM32_EXT_SRAM
rt_system_heap_init((void*)STM32_EXT_SRAM_BEGIN,(void*)STM32_EXT_SRAM_END);
#else
#ifdef __CC_ARM
rt_system_heap_init((void*)&Image$$RW_IRAM1$$ZI$$Limit,(void*)STM32_SRAM_EN
D);
#elif __ICCARM__
rt_system_heap_init(__segment_end("HEAP"),(void*)STM32_SRAM_END);
#else
/* init memory system */
rt_system_heap_init((void*)&__bss_end,(void*)STM32_SRAM_END);
#endif
#endif
#endif
/* 系統調度器初始化 */
rt_system_scheduler_init();
#ifdef RT_USING_DFS
/* init sdcard driver */
#if STM32_USE_SDIO
rt_hw_sdcard_init();
#else
rt_hw_msd_init();
#endif
#endif
/*下面可加入用戶所需的相關初始化 */
/*上面可加入用戶所需的相關初始化 */
/* 實時時鐘初始化 */
rt_hw_rtc_init();
/* 系統設備對象初始化 */
rt_device_init_all();
/* 用戶應用初始化 */
rt_application_init();
#ifdef RT_USING_FINSH
/* init finsh */
finsh_system_init();
finsh_set_device("uart1");
#endif
list_date();/*顯示當前時間 by jiezhi320 */
/* 初始化軟件定時器 */
rt_system_timer_thread_init();
/* 初始化空閒線程 */
rt_thread_idle_init();
/* 開始線程調度此後便進入各個線程的無限循環 */
rt_system_scheduler_start();
/* never reach here */
return;
}
上面的函數完成了系統啓動前的所有初始化動作,包括必要的硬件初始化、堆棧初始化、
系統相關組件初始化、用戶應用程序初始化,然後啓動調度機制。
1、 rt_hw_board_init()
完成中斷向量表設置、系統滴答時鐘設置,爲系統提供心跳、串口初始化,將系統輸入輸
出終端綁定到這個串口,後續系統運行信息就會從串口打印出來。
2、 rt_system_heap_init()
RT-Thread 提供動態內存管理機制(由 RT_USING_HEAP 宏來選擇性開啓,默認開啓),
這個函數用來設置需要系統來管理的內存段地址。對於 stm32f1這樣的芯片這裏有兩種選擇:
一種是除去編譯時分配的全局變量、靜態局部變量外的其他剩餘內存被設置爲系統堆空
間,被系統管理起來。比如魔笛 F1 板子芯片內部有 64k ram, 除去編譯後的 RW、 ZI 所佔
去的內存,剩餘的就讓系統管理起來:
rt_system_heap_init((void*)&Image$$RW_IRAM1$$ZI$$Limit, (void*)STM32_SRAM_END);
一種是板子有外擴 ram,這整個外擴 ram 可被設置爲堆空間,被系統管理起來
rt_system_heap_init((void*)STM32_EXT_SRAM_BEGIN, (void*)STM32_EXT_SRAM_END);
內存管理設置好以後,應用程序就可以使用 rt_malloc、 rt_realloc、 re_free 等函數了。
3、 rt_application_init()
這個函數是爲用戶準備的,用戶可以在這個函數裏創建自己的應用線程。
線程
線程的組成
RT-Thread 中的“線程”一般由三部分組成:線程代碼(函數)、線程控制塊、線程堆
棧
以led閃爍線程爲例
- 線程代碼:
void led_thread_entry(void* parameter)
{
unsigned int count=0;
rt_hw_led_init();
while(1)
{
#ifndef RT_USING_FINSH
rt_kprintf("led on, count : %d\r\n",count);
#endif
count++;
rt_hw_led_on(0);
rt_thread_delay( RT_TICK_PER_SECOND/2 );
#ifndef RT_USING_FINSH
rt_kprintf("led off\r\n");
#endif
rt_hw_led_off(0);
rt_thread_delay( RT_TICK_PER_SECOND/2 );
}
}
- 線程控制塊
/**
* Thread structure
*/
struct rt_thread
{
/* rt object */
char name[RT_NAME_MAX]; /**< the name of thread */
rt_uint8_t type; /**< type of object */
rt_uint8_t flags; /**< thread's flags */
#ifdef RT_USING_MODULE
void *module_id; /**< id of application module */
#endif
rt_list_t list; /**< the object list */
rt_list_t tlist; /**< the thread list */
/* stack point and entry */
void *sp; /**< stack point */
void *entry; /**< entry */
void *parameter; /**< parameter */
void *stack_addr; /**< stack address */
rt_uint16_t stack_size; /**< stack size */
/* error code */
rt_err_t error; /**< error code */
rt_uint8_t stat; /**< thread stat */
/* priority */
rt_uint8_t current_priority; /**< current priority */
rt_uint8_t init_priority; /**< initialized priority */
#if RT_THREAD_PRIORITY_MAX > 32
rt_uint8_t number;
rt_uint8_t high_mask;
#endif
rt_uint32_t number_mask;
#if defined(RT_USING_EVENT)
/* thread event */
rt_uint32_t event_set;
rt_uint8_t event_info;
#endif
rt_ubase_t init_tick; /**< thread's initialized tick */
rt_ubase_t remaining_tick; /**< remaining tick */
struct rt_timer thread_timer; /**< built-in thread timer */
void (*cleanup)(struct rt_thread *tid); /**< cleanup function when thread exit */
rt_uint32_t user_data; /**< private user data beyond this thread */
};
typedef struct rt_thread *rt_thread_t;
它記錄了線程的各個屬性,系統用線程控制塊鏈表對其進行管理。
- 線程堆棧
static rt_uint8_t led_stack[512];
線程堆棧是一段連續的內存塊,當線程切換後,爲了滿足線程切換和響應中斷時保存
cpu 寄存器中的內容及任務調用其它函數時的準備,每個線程都要配有自己的堆棧
創建一個線程
RT-Thread 中的線程分爲靜態線程—線程堆棧由編譯器靜態分配,使用 rt_thread_init()
函數創建和動態線程—線程堆棧由系統動態分配,使用 rt_thread_create()函數創建。
代碼:
/* 靜態線程的線程堆棧*/
static rt_uint8_t led1_stack[512];
/* 靜態線程的線程控制塊 */
static struct rt_thread led1_thread;
void demo_thread_creat(void)
{
rt_err_t result;
/* 動態線程的線程控制塊指針 */
rt_thread_t led2_thread;
rt_hw_led_init();
/* 創建靜態線程:優先級 20 ,時間片 2 個系統滴答 */
result =rt_thread_init(&led1_thread,"led1",
static_thread_entry, RT_NULL,
(rt_uint8_t*)&led1_stack[0],
sizeof(led1_stack),20,2);
if(result == RT_EOK)
{
rt_thread_startup(&led1_thread);
}
/* 創建動態線程:堆棧大小 512 bytes ,優先級 21 ,時間片 2 個系統滴答 */
led2_thread =rt_thread_create("led2",
dynamic_thread_entry, RT_NULL,
512,21,2);
if(led2_thread != RT_NULL)
rt_thread_startup(led2_thread);
}
- 靜態線程 VS 動態線程
從上例可看出,靜態、動態線程在做同樣的事情時,從效果上看,是沒有任何差別的!
那麼,我們在實際中如何抉擇?
使用靜態線程時,必須先定義靜態的線程控制塊,並且定義好堆棧空間,然後調用
rt_thread_init()來完成線程的初始化工作。採用這種方式,線程控制塊和堆棧佔用的內存
會放在 RW/ZI 段,這段空間在編譯時就已經確定,它不是可以動態分配的,所以不能被釋
放,而只能使用 rt_thread_detach()函數將該線程控制塊從對象管理器中脫離。
使用動態定義方式 rt_thread_create()時, RT-Thread 會動態申請線程控制塊和堆棧空間。
在編譯時,編譯器是不會感知到這段空間的,只有在程序運行時, RT-Thread 纔會從系統堆
中申請分配這段內存空間,當不需要使用該線程時,調用 rt_thread_delete()函數就會將這段
申請的內存空間重新釋放到內存堆中。
這兩種方式各有利弊,靜態定義方式會佔用 RW/ZI 空間,但是不需要動態分配內存,
運行時效率較高,實時性較好。動態方式不會佔用額外的 RW/ZI 空間,佔用空間小,但是
運行時需要動態分配內存,效率沒有靜態方式高
線程的調度和管理
線程狀態
線程優先級 系統時鐘
RT-Thread 共支持 256 個優先級(0-255,數值越小的優先級越高, 0 爲最高優先級, 255分配給空閒線程使用;線程總數不受限制,只和能提供給系統的 ram 有關),一般通過在 rt_config.h 配置文件中將系統配置爲 32 個優先級
/* PRIORITY_MAX */
#define RT_THREAD_PRIORITY_MAX 32
系統心跳時鐘 時間片 根據CPU的處理能力決定 一般設置爲10-100HZ,頻率越快,系統負荷越大。在stm32平臺一般設置爲100hz 每個時間片10ms 在rt_config.h中可自行設置
/* Tick per Second */
#define RT_TICK_PER_SECOND 100//1 秒 100 次即 10ms 一次
RT-Thread 中配置系統時鐘的代碼如下:
SystemInit();
/* NVIC Configuration */
NVIC_Configuration();
/* 一上電就儘快配置系統時鐘 */
SysTick_Config( SystemCoreClock / RT_TICK_PER_SECOND );
空閒線程
是系統線程中一個比較特殊的線程,它具備最低的優先級,當系統中無其他線程可運行時,調度器將調度到空閒線程。空閒線程通常是一個死循環,永遠不被掛起。在RT-Thread 實時操作系統中空閒線程提供了鉤子函數,可以讓系統在空閒的時候執行一定任務,例如系統運行指示燈閃爍, CPU 使用率統計等等。另外空閒線程還負責一些系統資源
回收。
線程調度規則
不同優先級的線程根據優先級順序進行調度;相同優先級的線程根
據時間片來調度
線程間同步和通信
禁止系統調度
一般可通過關閉中斷和調度器上鎖這兩種簡單的途徑來禁止系統調度,防止線程被打斷,從而保證臨界區不被破壞。
- 關閉中斷
void test1_thread_entry(void* parameter)
{
rt_base_t level;
while(1)
{
/* 關閉中斷*/
level = rt_hw_interrupt_disable();
/* 以下是臨界區*/
. . . .
/* 關閉中斷*/
rt_hw_interrupt_enable(level);
}
}
當我們關閉中斷後,系統將不能再進行調度,即也不會被其他線程搶佔
- 調度器上鎖
void test1_thread_entry(void* parameter)
{
while(1)
{
/* 調度器上鎖,上鎖後,將不再切換到其他線程,僅響應中斷 */
rt_enter_critical();
/* 以下進入臨界區 */
. . . .
/* 調度器解鎖 */
rt_exit_critical();
}
}
對調度器上鎖,系統依然能響應外部中斷,中斷服務例程依然有可能被運行
信號量的基本操作
RT-Thread 中的信號量有靜態和動態之分(同靜態線程、動態線程),和信號量有關的
操作如下:
初始化—rt_sem_init()(對應靜態信號量) ;
建立—rt_sem_create()(對應動態信號量);
獲取—rt_sem_take();
釋放—rt_sem_release();
脫離—rt_sem_detach()(對應靜態信號量) ;
刪除—rt_sem_delete()(對應動態信號量) ;
每釋放一次信號量加1 獲取一次信號量減一(如果不爲0) 可指定等待時間
做按鍵實驗的時候注意不要死循環釋放了多次信號量,否則會跟預想結果不一樣
互斥鎖
互斥鎖和信號量很相似, RT-Thread 中的互斥鎖也有靜態和動態之分,和互斥鎖有關的
操作如下:
初始化—rt_mutex_init()(對應靜態互斥鎖);
建立—rt_mutex_create()(對應動態互斥鎖);
獲取—rt_mutex_take();
釋放—rt_mutex_release();
脫離—rt_mutex_detach()(對應靜態信號量) ;
刪除—rt_mutex_delete()(對應動態信號量);
信號量到處都可以釋放,互斥鎖只有擁有其所有權才能釋放。
具有數據交換功能的ipc對象:
郵箱
郵箱是一種典型的任務間通信方法,一封郵件只能容納固定的4字節內容
中斷服務和定時器都可以像郵箱發送信息,只有線程能夠接收信息
RT-Thread 的郵箱中共可存放固定條數的郵件,郵箱容量在創建郵箱時設定需要在線程間傳遞比較大的消息時,可以傳遞指向一個緩衝區的指針
當郵箱滿時,線程等不再發送新郵件,返回-RT EFULL。
當郵箱空時,將可能掛起正在試圖接收郵件的線程,使其等待,當郵箱中有新郵件時,再喚醒等待在郵箱上的線程,使其能夠接收新郵件並繼續後續的處理
RT-Thread 中的郵箱依然是有靜態和動態之分,和郵箱有關的操作如下:
初始化—rt_mb_init()(對應靜態郵箱);
建立—rt_mb_create()(對應動態郵箱);
發送郵件—rt_mb_send();
接收郵件—rt_mb_recv();
脫離—rt_mb_detach()(對應靜態郵箱) ;
刪除—rt_mb_delete()(對應動態郵箱);
消息隊列
消息隊列能夠接受來自線程的不固定長度的消息(相比郵件的4字節)並把消息緩存在自己的內存空間中。其他線程也能夠從消息隊列中讀取相應的消息,而當消息隊列是空的時候,可以掛起讀取線程。而當有新的消息到達時,掛起的線程將被喚醒以接收並處理消息。
消息隊列的消息先傳給線程,也就是說,線程先得到的是最先進入消息隊列的消息,即先進先出原則(FIFO)。 RT-Thread 的消息隊列對象由多個元素組成,當消息隊列被創建時,它就被分配了消息隊列控制塊:隊列名,內存緩衝區,消息大小以及隊列長度等。同時每個消息隊列對象中包含着多個消息框,每個消息框可以存放一條消息。消息隊列中的第一個和最後一個消息框被分別稱爲隊首和隊尾,對應於消息隊列控制塊中的 msg_queue_head 和msg_queue_tail。所有消息隊列中的消息框總數即是消息隊列的長度,這個長度可在消息隊列創建時指定。
初始化—rt_mq_init()(對應靜態消息隊列) ;
建立—rt_mq_create()(對應動態消息隊列) ;
發送消息—rt_mq_send();
發送緊急消息—rt_mq_urgent();
接收消息—rt_mq_recv();
脫離—rt_mq_detach()(對應靜態消息隊列) ;
刪除—rt_mq_delete()(對應動態消息隊列) ;
消息隊列提供發送緊急消息的操作,當發送緊急消息時,消息塊不是掛到消息隊列的隊尾,而是掛到隊首,這樣,接收者能夠優先接收到緊急消息,從而及時進行消息處理。
IPC對象會造成線程阻塞,不要在中斷中嘗試執行獲取!!!
事件機制
RT-Thread 中的信號量主要用於“一對一”的線程同步,當需要“一對多”、“多對一”、“多對多”的同步時,就需要事件機制來處理了。
RT-Thread 中的事件用一個 32 位無符號整型變量來表示,變量中的一位代表一個事件,線程通過“邏輯與”或“邏輯或”與一個或多個事件建立關聯形成一個事件集
事件的相關操作如下:
初始化—rt_event_init()(對應靜態事件) ;
建立—rt_event_create()(對應動態事件) ;
發送事件—rt_event_send();
接收事件—rt_event_recv();
脫離—rt_event_detach()(對應靜態事件) ;
刪除—rt_event_delete()(對應動態事件) ;
全局變量
注意併發和競態問題