從0到1寫RT-Thread內核——空閑線程與阻塞延時的實現

? ? ? ?在之前寫的另外一篇文章——<從0到1寫RT-Thread內核——線程定義及切換的實現>中線程體內的延時使用的是軟件延時,即還是讓CPU空等來達到延時的效果。RTOS中的延時叫阻塞延時,即線程需要延時的時候,線程會放棄CPU的使用權,CPU可以去干其他的事情,當線程延時時間到,重新獲取CPU使用權,線程繼續運行,這樣就充分利用了CPU的資源,而不是干等著。

? ? ? ?當某個線程需要延時,進入阻塞狀態,如果沒有其他線程可以運行,RTOS都會為CPU創建一個空閑線程,這個時候CPU就運行空閑線程。在RT-Thread中,空閑線程是系統在初始化的時候創建的優先級最低的線程,空閑線程主要是做一些系統內存的清理工作。但為了簡單起見,這里我們的空閑線程只對一個全局變量進行計數。在實際應用中,當系統進入空閑線程的時候,可在空閑線程中讓單片機進入休眠或者低功耗等操作。

我們把空閑線程與阻塞延時的實現分為以下兩大步:

一.實現空閑線程

1.定義空閑線程的棧

#include <rtthread.h>
#include <rthw.h>
#define IDLE_THREAD_STACK_SIZE      512 
ALIGN(RT_ALIGN_SIZE)
static rt_uint8_t rt_thread_stack[IDLE_THREAD_STACK_SIZE];

2.定義空閑線程控制塊

struct rt_thread idle;

3.定義空閑線程函數

rt_ubase_t  rt_idletask_ctr = 0;void rt_thread_idle_entry(void *parameter)
{parameter = parameter;while (1){rt_idletask_ctr ++;/* 進行系統調度,這個是我自己后來加的,野火官方的例程是沒有調用rt_schedule,        *我覺得那樣是錯誤的,因為這里不加rt_schedule的話程序執行到空閑線程就回不去了*///rt_schedule();//這里補充一下,野火的例程并沒有錯,每次產生滴答定時器中斷都會調用調度器,所以這里不需要調用調度器的}
}

4.空閑線程初始化

/*** @ingroup SystemInit** 初始化空閑線程,啟動空閑線程** @note 當系統初始化的時候該函數必須被調用*/
void rt_thread_idle_init(void)
{/* 初始化線程 */rt_thread_init(&idle,"idle",rt_thread_idle_entry,RT_NULL,&rt_thread_stack[0],sizeof(rt_thread_stack));/* 將空閑線程插入到就緒列表中優先級最低的鏈表中 */rt_list_insert_before( &(rt_thread_priority_table[RT_THREAD_PRIORITY_MAX-1]),&(idle.tlist) );
}

以上4步在之前的文章——<從0到1寫RT-Thread內核——線程定義及切換的實現>有詳細介紹過了,這里就不再過多解釋。

二.實現阻塞延時

? ? ? 阻塞延時的阻塞是指線程調用該延時函數后,線程會被剝離CPU使用權,然后進入阻塞狀態,直到延時結束,線程會重新獲取CPU使用權才可繼續運行,在線程阻塞的這段時間,CPU可以去執行其他的線程,如果其他的線程也在延時狀態,那么CPU就將運行空閑線程。我們定義一個延時函數rt_thread_delay函數,其代碼清單如下圖:

? ? ? 上面的代碼中我們通過thread->remaining_tick來判斷某個線程的延時是否結束,thread->remaining_tick是在SysTick_Handler中遞減的,其代碼如下:

void SysTick_Handler(void)
{/* 進入中斷 */rt_interrupt_enter();rt_tick_increase();/* 離開中斷 */rt_interrupt_leave();
}
/* *    rt_interrupt_nest為中斷計數器,是一個全局變量,用來記錄中斷嵌套次數。*    每進入一個中斷函數,就會加一*    每離開一個中斷函數,就會減一*/
volatile rt_uint8_t rt_interrupt_nest;/*** 當BSP文件的中斷服務函數進入時會調用該函數* * @note 請不要在應用程序中調用該函數** @see rt_interrupt_leave*/
void rt_interrupt_enter(void)
{rt_base_t level;/* 關中斷 */level = rt_hw_interrupt_disable();/* 中斷計數器++ */rt_interrupt_nest ++;/* 開中斷 */rt_hw_interrupt_enable(level);
}/*** 當BSP文件的中斷服務函數離開時會調用該函數** @note 請不要在應用程序中調用該函數** @see rt_interrupt_enter*/
void rt_interrupt_leave(void)
{rt_base_t level;/* 關中斷 */level = rt_hw_interrupt_disable();/* 中斷計數器-- */rt_interrupt_nest --;/* 開中斷 */rt_hw_interrupt_enable(level);
}
//rt_tick 為系統時基計數器,是一個全局變量,用來記錄產生了多少次SysTick中斷
static rt_tick_t rt_tick = 0;
extern rt_list_t rt_thread_priority_table[RT_THREAD_PRIORITY_MAX];void rt_tick_increase(void)
{rt_ubase_t i;struct rt_thread *thread;rt_tick ++;/* 掃描就緒列表中所有線程的remaining_tick,如果不為0,則減1 */for(i=0; i<RT_THREAD_PRIORITY_MAX; i++){thread = rt_list_entry( rt_thread_priority_table[i].next,struct rt_thread,tlist);if(thread->remaining_tick > 0){thread->remaining_tick --;}}/* 系統調度 */rt_schedule();
}

? ? ? ?下面我們來看一下main函數和線程1和線程2的函數體,在main函數中我們分別初始化了空閑線程、線程1和線程2并將它們插入到對應就緒列表的鏈表中去,然后就開始了系統調度(rt_system_scheduler_start),先從線程1開始執行(這里不理解的話,可以看另外一篇博客:<從0到1寫RT-Thread內核——線程定義及切換的實現>),如果某個因為線程調用了rt_thread_delay函數而被阻塞了的話就運行另外的線程(此時線程阻塞剩余時長remaining_tick在SysTick_Handler中不斷遞減),如果非空閑線程都被阻塞了才運行空閑線程,如果某個線程的remaining_tick遞減到為0了,則又繼續運行該線程。

/************************************************************************* @brief  main函數* @param  無* @retval 無** @attention*********************************************************************** */
int main(void)
{	/* 硬件初始化 *//* 將硬件相關的初始化放在這里,如果是軟件仿真則沒有相關初始化代碼 *//* 關中斷,在程序開始的時候把中斷關閉是一個好習慣,等系統初始化完畢,線程創建完畢,啟動系統                                        * 調度的時候會重新打開中斷(在rt_hw_context_switch_to函數中會再開啟中斷并設置中斷標位)。* 如果一開始不關閉中斷,接下來SysTick初始化完成,然后再初始化系統和創建線程,如果系統初始化* 和線程創建的時間大于SysTick中斷周期的話,那么就會出現系統或者線程還沒準備好的情況下就先執* 行了SysTick中斷服務函數,在該函數中進行了系統調度,顯示這是不合理的。*/rt_hw_interrupt_disable();/* 初始化SysTick,調用固件庫函數SysTick_Config來實現,* 配置中斷周期為10ms(為100),中斷優先級為最低*/SysTick_Config( SystemCoreClock / RT_TICK_PER_SECOND );/* 調度器初始化 */rt_system_scheduler_init();/* 初始化空閑線程 */    rt_thread_idle_init();	/* 初始化線程1 */rt_thread_init( &rt_flag1_thread,                 /* 線程控制塊 */"rt_flag1_thread",                /* 線程名字,字符串形式 */flag1_thread_entry,               /* 線程入口地址 */RT_NULL,                          /* 線程形參 */&rt_flag1_thread_stack[0],        /* 線程棧起始地址 */sizeof(rt_flag1_thread_stack) );  /* 線程棧大小,單位為字節 *//* 將線程插入到就緒列表 */rt_list_insert_before( &(rt_thread_priority_table[0]),&(rt_flag1_thread.tlist) );/* 初始化線程2 */rt_thread_init( &rt_flag2_thread,                 /* 線程控制塊 */"rt_flag2_thread",                /* 線程名字,字符串形式 */flag2_thread_entry,               /* 線程入口地址 */RT_NULL,                          /* 線程形參 */&rt_flag2_thread_stack[0],        /* 線程棧起始地址 */sizeof(rt_flag2_thread_stack) );  /* 線程棧大小,單位為字節 *//* 將線程插入到就緒列表 */rt_list_insert_before( &(rt_thread_priority_table[1]),&(rt_flag2_thread.tlist) );/* 啟動系統調度器 */rt_system_scheduler_start(); 
}

?

/* 線程1 */
void flag1_thread_entry( void *p_arg )
{for( ;; ){
#if 0flag1 = 1;delay( 100 );		flag1 = 0;delay( 100 );/* 線程切換,這里是手動切換 */		rt_schedule();
#elseflag1 = 1;rt_thread_delay(2); 		flag1 = 0;rt_thread_delay(2);
#endif        }
}/* 線程2 */
void flag2_thread_entry( void *p_arg )
{for( ;; ){
#if 0flag2 = 1;delay( 100 );		flag2 = 0;delay( 100 );/* 線程切換,這里是手動切換 */rt_schedule();
#elseflag2 = 1;rt_thread_delay(2); 		flag2 = 0;rt_thread_delay(2);
#endif        }
}

程序運行結果如下,其實這種阻塞延時的原理和我們在單片機裸機中采用的"前后臺輪詢"是非常相似的。

最后聲明一下,我這里只是對學習的知識點進行總結,本文章的大多數知識來自于野火公司出版的《RT-Thread 內核實現與應用開發實戰—基于STM32》,這本書非常不錯,有志學習RT-Thread物聯網操作系統的人可以考慮一下。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/384522.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/384522.shtml
英文地址,請注明出處:http://en.pswp.cn/news/384522.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

從0到1寫RT-Thread內核——支持多優先級

在本章之前&#xff0c;RT-Thread還沒有支持多優先級&#xff0c;我們手動指定了第一個運行的線程&#xff0c;并在此之后三個線程&#xff08;包括空閑線程&#xff09;互相切換&#xff0c;在本章中我們加入優先級的功能&#xff0c;第一個運行的程序是就緒列表里優先級最高的…

Linux串口阻塞與非阻塞

Linux串口編程的阻塞與否可以在open函數中設置&#xff0c;例如&#xff1a; 打開時使用&#xff1a; fd open(USAR1, O_RDWR | O_NOCTTY );//阻塞式讀寫fd open("/dev/ttyAT2",O_RDWR|O_NOCTTY|O_NDELAY); //非阻塞讀寫 除了用open函數之外還可以在open函數之…

vi中如何實現批量替換

將文件tihuan&#xff08;假設此文本中字符a&#xff09;中的所有字符a換成字符w&#xff0c;其命令為&#xff1a; 1。vi tihuan 2。按esc鍵 3。按shift&#xff1a; 4。在&#xff1a;后輸入 %s/a/w/g 其中s為&#xff1a;substitute&#xff0c;%表示所有行&#xff0c;…

C++里數組名+1和數組名的地址+1的區別

C/C里面的數組名字會退化為指針&#xff0c;所以數組名a實際指的是數組的第一個元素的地址。而數組名作為指針來講有特殊性&#xff0c;它正在它所指向的內存區域中&#xff0c;&a的值和a的數值是相同的&#xff08;可以輸出觀察一下&#xff09;&#xff0c;但是類型和意義…

棧空間和堆空間的區別

棧空間用于存儲函數參數和局部變量&#xff0c;所需空間由系統自動分配&#xff0c;回收也由系統管理&#xff0c;無需人工干預&#xff1b;堆空間用于存儲動態分配的內存塊&#xff0c;分配和釋放空間均由程序員控制&#xff0c;有可能產生內存泄漏。 棧空間作為一個嚴格后進…

AD軟件之模塊化原理圖

首先我們創建兩個原理圖文件 然后我們在Sheet2.SchDoc里放置一個頁面符并雙擊綠色的方框 選擇目標文件 我們選擇我們剛才創建的Sheet4.SchDoc 然后在 視圖——>面板——>Navigator選項 里點一下交互式導航 就可以看到Sheet4.SchDoc被添加到Sheet2.SchDoc下面了 通過上面…

進程與線程的區別(面試題)

進程與線程的區別 1.進程是資源分配最小單位&#xff0c;線程是程序執行的最小單位&#xff1b; 2..進程有自己獨立的地址空間&#xff0c;每啟動一個進程&#xff0c;系統都會為其分配地址空間&#xff0c;建立數據表來維護代碼段、堆棧段和數據段&#xff0c;線程沒有獨立的…

查找表的原理與結構 什么是競爭與冒險現象?怎樣判斷?如何消除?

查找表的原理與結構&#xff1a; 查找表&#xff08;look-up-table&#xff09;簡稱為LUT&#xff0c;LUT本質上就是一個RAM。目前FPGA中多使用4輸入的LUT&#xff0c;所以每一個LUT可以看成一個有 4位地址線的16x1的RAM。當用戶通過原理圖或HDL語言描述了一個邏輯電路以…

AD軟件操作技巧

本文介紹一些關于AD軟件的實用小操作&#xff0c;這些小技巧可以大大的減少我們的工作量 一.批量操作絲印&#xff08;或者操作別的東西也可以&#xff0c;主要是凸顯批量操作的思想&#xff09; 如下圖假設我們工程里有很多絲印和焊盤等等&#xff0c;現在我想改批量地修改絲…

冒泡排序算法,C語言冒泡排序算法詳解

冒泡排序是最簡單的排序方法&#xff0c;理解起來容易。雖然它的計算步驟比較多&#xff0c;不是最快的&#xff0c;但它是最基本的&#xff0c;初學者一定要掌握。 冒泡排序的原理是&#xff1a;從左到右&#xff0c;相鄰元素進行比較。每次比較一輪&#xff0c;就會找到序列中…

白話經典算法系列之六 快速排序 快速搞定

快速排序由于排序效率在同為O(N*logN)的幾種排序方法中效率較高&#xff0c;因此經常被采用&#xff0c;再加上快速排序思想----分治法也確實實用&#xff0c;因此很多軟件公司的筆試面試&#xff0c;包括像騰訊&#xff0c;微軟等知名IT公司都喜歡考這個&#xff0c;還有大大小…

c語言中判斷一個字符串是否包含另一個字符串

1. 使用庫函數 string.h strstr函數 函數名: strstr 功 能: 在串中查找指定字符串的第一次出現 用 法: char *strstr(char *str1, char *str2); 說明&#xff1a;返回指向第一次出現str2位置的指針&#xff0c;如果沒找到則返回NULL。 調用函數,判斷返回值是否等于NULL…

C語言截取從某位置開始指定長度子字符串方法

c語言標準庫沒有截取部分字符串的函數&#xff0c;為啥&#xff1f;因為用現有函數strncpy&#xff0c;很容易做到&#xff01; char dest[4] {""}; char src[] {"123456789"}; strncpy(dest, src, 3); puts(dest); 輸出結果為 123 看到了嗎&#xff…

Modbus通訊協議詳細解釋

https://blog.csdn.net/rxiang12/article/details/79125813

V4L2框架分析

V4L2是Video for linux2的簡稱,為linux中關于視頻設備的內核驅動。v4L2是針對uvc&#xff08;USB Video Class&#xff09;免驅usb設備的編程框架&#xff0c;主要用于采集usb攝像頭等。 下圖是V4L2的框架&#xff0c;首先系統核心層分配設置注冊一個名為cdev結構體變量&#x…

Linux下IO多路復用之select函數的使用

select函數的作用&#xff1a; 如果我們的程序里有兩個需要阻塞的地方&#xff0c;例如要從服務器讀數據&#xff0c;同時還要從鍵盤上讀數據&#xff08;若不采用阻塞而用查詢的方式則大量占用系統資源&#xff09;。這個時候我們就有兩處阻塞&#xff0c;你當然可以用多線程或…

條件變量實現線程同步

(1) 什么是條件變量實現線程同步?   假如我們的程序中有兩個線程&#xff0c;一個是生產者線程&#xff0c;另一個是消費者線程&#xff0c;生產者線程每隔一段時間把數據寫入到緩沖區buffer中&#xff0c;而消費者線程則每隔一段時間從buffer中取出數據&#xff0c;為了避免…

mjpg-streamer框架分析

mjpg-streamer程框架圖如下所示&#xff1a; 程序運行起來后&#xff0c;主進程根據傳入的參數設置的輸入輸出通道打開對應的輸入輸出動態鏈接庫&#xff0c;并依次調用以下函數 1、輸入---倉庫-----輸出&#xff08;mjpg-streamer.h&#xff09; &#xff08;1&#xff09;gl…

用strace工具跟蹤系統調用

Linux下可以用strace工具查看應用程序的系統調用。 strace -h 查看能調用的參數 1.strace -o xwatv.log xwatv //-o xwatv.log 是指定將跟蹤信息存放在xwatv.log中&#xff0c;xwatv是指要跟蹤的命令或應用程序 2.把生成的log文件拷貝回windows下進行分析 主要分析open…

linux字符驅動之概念介紹

一、字符驅動框架 問&#xff1a;應用程序open、read、write如何找到驅動程序的open、read、write函數&#xff1f; 答:應用程序的open、read、write是在C庫里面實現的&#xff0c;它里面通過swi val指令去觸發一個異常&#xff0c;這個異常就會進入到內核空間&#xff0c;在內…