FreeRTOS簡單內核實現7 阻塞鏈表

来源:https://www.cnblogs.com/lc-guo/p/18250552
-Advertisement-
Play Games

0、思考與回答 0.1、思考一 如何處理進入阻塞狀態的任務? 為了讓 RTOS 支持多優先順序,我們創建了多個就緒鏈表(數組形式),用每一個就緒鏈表表示一個優先順序,對於阻塞狀態的任務顯然要從就緒鏈表中移除,但是阻塞狀態的任務並不是永久阻塞了,等待一段時間後應該從阻塞狀態恢復,所以我們需要創建一個阻塞鏈 ...


0、思考與回答

0.1、思考一

如何處理進入阻塞狀態的任務?

為了讓 RTOS 支持多優先順序,我們創建了多個就緒鏈表(數組形式),用每一個就緒鏈表表示一個優先順序,對於阻塞狀態的任務顯然要從就緒鏈表中移除,但是阻塞狀態的任務並不是永久阻塞了,等待一段時間後應該從阻塞狀態恢復,所以我們需要創建一個阻塞鏈表用來存放進入阻塞狀態的任務

0.2、思考二

還有一個問題,xTicksToDelay 是一個 32 位的變數,如何處理其潛在的溢出問題?

假設使用一個 32 位的 xNextTaskUnblockTime 變數表示任務下次解除阻塞的時間,其一般應該由如下所示的程式代碼計算

// 任務下次解除阻塞的時間 = 當前滴答定時器計數值 + 要延時的滴答次數
xNextTaskUnblockTime = xConstTickCount + xTicksToWait;

可以看出 xNextTaskUnblockTime 變數隨著運行時間流逝存在溢出風險,因此我們需要再定義一個溢出阻塞鏈表用來存放所有下次解除阻塞的時間溢出的任務,這樣我們就擁有兩個阻塞鏈表,在滴答定時器中斷服務函數中如果一旦發現滴答定時器計數值全局變數溢出,就通過鏈表指針將這兩個鏈表交換,保證永遠處理的是正確的阻塞鏈表

1、阻塞鏈表

1.1、定義

/* task.c */
// 阻塞鏈表和其指針
static List_t xDelayed_Task_List1;
static List_t volatile *pxDelayed_Task_List;
// 溢出阻塞鏈表和其指針
static List_t xDelayed_Task_List2;
static List_t volatile *pxOverflow_Delayed_Task_List;

1.2、prvInitialiseTaskLists( )

由於新增加了阻塞鏈表和溢出阻塞鏈表,因此在鏈表初始化函數中除了需要初始化就緒鏈表數組外,還需要增加對阻塞鏈表和溢出阻塞鏈表的初始化操作,如下所示

/* task.c */
// 就緒列表初始化函數
void prvInitialiseTaskLists(void)
{
	// 省略未修改部分
	......
	// 初始化延時阻塞鏈表
	vListInitialise(&xDelayed_Task_List1);
	vListInitialise(&xDelayed_Task_List2);
	
	// 初始化指向延時阻塞鏈表的指針
	pxDelayed_Task_List = &xDelayed_Task_List1;
	pxOverflow_Delayed_Task_List = &xDelayed_Task_List2;
}

1.3、taskSWITCH_DELAYED_LISTS( )

為什麼需要阻塞鏈表和溢出阻塞鏈表需要交換?

閱讀 ” 0.2、思考二“ 小節內容

阻塞鏈表和溢出阻塞鏈表是如何實現交換的?

利用兩個指針進行交換

/* task.c */
// 記錄溢出次數
static volatile BaseType_t xNumOfOverflows = (BaseType_t)0;

// 延時阻塞鏈表和溢出延時阻塞鏈表交換
#define taskSWITCH_DELAYED_LISTS()\
{\
	List_t volatile *pxTemp;\
	pxTemp = pxDelayed_Task_List;\
	pxDelayed_Task_List = pxOverflow_Delayed_Task_List;\
	pxOverflow_Delayed_Task_List = pxTemp;\
	xNumOfOverflows++;\
	prvResetNextTaskUnblockTime();\
}

1.4、prvResetNextTaskUnblockTime( )

由於將任務插入溢出阻塞鏈表時不會更新 xNextTaskUnblockTime 變數,只有在將任務插入阻塞鏈表中時才會更新xNextTaskUnblockTime 變數,所以對於溢出阻塞鏈表中存在的任務沒有對應的喚醒時間,因此當心跳溢出切換阻塞鏈表時候,需要重設 xNextTaskUnblockTime 變數的值

/* task.c */
// 記錄下個任務解除阻塞時間
static volatile TickType_t xNextTaskUnblockTime = (TickType_t)0U;
// 函數聲明
static void prvResetNextTaskUnblockTime(void);

// 重設 xNextTaskUnblockTime 變數值
static void prvResetNextTaskUnblockTime(void)
{
	TCB_t *pxTCB;
	// 切換阻塞鏈表後,阻塞鏈表為空
	if(listLIST_IS_EMPTY(pxDelayed_Task_List) != pdFALSE)
	{
		// 下次解除延時的時間為可能的最大值
		xNextTaskUnblockTime = portMAX_DELAY;
	}
	else
	{
		// 如果阻塞鏈表不為空,下次解除延時的時間為鏈表頭任務的阻塞時間
		(pxTCB) = (TCB_t *)listGET_OWNER_OF_HEAD_ENTRY(pxDelayed_Task_List);
		xNextTaskUnblockTime=listGET_LIST_ITEM_VALUE(&((pxTCB)->xStateListItem));
	}
}

1.5、prvAddCurrentTaskToDelayedList( )

將當前任務加入到阻塞鏈表中,具體流程可以參看程式註釋,對於延時到期時間未溢出的任務會插入到阻塞鏈表中,而對於延時到期時間溢出的任務會插入溢出阻塞鏈表中

/* task.c */
// 將當前任務添加到阻塞鏈表中
static void prvAddCurrentTaskToDelayedList(TickType_t xTicksToWait)
{
	TickType_t xTimeToWake;
	// 當前滴答定時器中斷次數
	const TickType_t xConstTickCount = xTickCount;
	// 成功從就緒鏈表中移除該阻塞任務
	if(uxListRemove((ListItem_t *)&(pxCurrentTCB->xStateListItem)) == 0)
	{
		// 將當前任務的優先順序從優先順序點陣圖中刪除
		portRESET_READY_PRIORITY(pxCurrentTCB->uxPriority, uxTopReadyPriority);
	}
	// 計算延時到期時間
	xTimeToWake = xConstTickCount + xTicksToWait;
	// 將延時到期值設置為阻塞鏈表中節點的排序值
	listSET_LIST_ITEM_VALUE(&(pxCurrentTCB->xStateListItem), xTimeToWake);
	// 如果延時到期時間會溢出
	if(xTimeToWake < xConstTickCount)
	{
		// 將其插入溢出阻塞鏈表中
		vListInsert((List_t *)pxOverflow_Delayed_Task_List,
		           (ListItem_t *)&(pxCurrentTCB->xStateListItem));
	}
	// 沒有溢出
	else
	{
		// 插入到阻塞鏈表中
		vListInsert((List_t *)pxDelayed_Task_List,
		           (ListItem_t *) &( pxCurrentTCB->xStateListItem));
		
		// 更新下一個任務解鎖時刻變數 xNextTaskUnblockTime 的值
		if(xTimeToWake < xNextTaskUnblockTime)
		{
			xNextTaskUnblockTime = xTimeToWake;
		}
	}
}

2、修改內核程式

2.1、vTaskStartScheduler( )

/* task.c */
void vTaskStartScheduler(void)
{
	// 省略創建空閑任務函數
	......
	
	// 初始化滴答定時器計數值,感覺有點兒多餘?全局變數定義時候已被初始化為 0
	xTickCount = (TickType_t)0U;
	
	if(xPortStartScheduler() != pdFALSE){}
}

2.2、vTaskDelay( )

阻塞延時函數,當任務調用阻塞延時函數時會將任務從就緒鏈表中刪除,然後加入到阻塞鏈表中

/* task.c */
// 阻塞延時函數
void vTaskDelay(const TickType_t xTicksToDelay)
{
	// 將當前任務加入到阻塞鏈表
	prvAddCurrentTaskToDelayedList(xTicksToDelay);
	
	// 任務切換
	taskYIELD();
}

2.3、xTaskIncrementTick( )

利用 RTOS 的心跳(滴答定時器中斷服務函數)對阻塞任務進行處理,具體流程如下所示

/* task.c */
// 任務阻塞延時處理
BaseType_t xTaskIncrementTick(void)
{
	TCB_t *pxTCB = NULL;
	TickType_t xItemValue;
	BaseType_t xSwitchRequired = pdFALSE;
	
	// 更新系統時基計數器 xTickCount
	const TickType_t xConstTickCount = xTickCount + 1;
	xTickCount = xConstTickCount;

	// 如果 xConstTickCount 溢出,則切換延時列表
	if(xConstTickCount == (TickType_t)0U)
	{
		taskSWITCH_DELAYED_LISTS();
	}
	
	// 最近的延時任務延時到期
	if(xConstTickCount >= xNextTaskUnblockTime)
	{
		for(;;)
		{
			// 延時阻塞鏈表為空則跳出 for 迴圈
			if(listLIST_IS_EMPTY(pxDelayed_Task_List) != pdFALSE)
			{
				// 設置下個任務解除阻塞時間為最大值,也即永不解除阻塞
				xNextTaskUnblockTime = portMAX_DELAY;
				break;
			}
			else
			{
				// 依次獲取延時阻塞鏈表頭節點
				pxTCB=(TCB_t *)listGET_OWNER_OF_HEAD_ENTRY(pxDelayed_Task_List);
				// 依次獲取延時阻塞鏈表中所有節點解除阻塞的時間
				xItemValue = listGET_LIST_ITEM_VALUE(&(pxTCB->xStateListItem));
				
				// 當阻塞鏈表中所有延時到期的任務都被移除則跳出 for 迴圈
				if(xConstTickCount < xItemValue)
				{
					xNextTaskUnblockTime = xItemValue;
					break;
				}
				
				// 將任務從延時列表移除,消除等待狀態
				(void)uxListRemove(&(pxTCB->xStateListItem));
				
				// 將解除等待的任務添加到就緒列表
				prvAddTaskToReadyList(pxTCB);
#if(configUSE_PREEMPTION == 1)
				// 如果解除阻塞狀態的任務優先順序比當前任務優先順序高,則需要進行任務調度
				if(pxTCB->uxPriority >= pxCurrentTCB->uxPriority)
				{
					xSwitchRequired = pdTRUE;
				}
#endif
			}
		}
	}
	return xSwitchRequired;
}

/* task.h */
// 修改函數聲明
BaseType_t xTaskIncrementTick(void);
/* FreeRTOSConfig.h */
// 支持搶占優先順序
#define configUSE_PREEMPTION                    1

2.4、xPortSysTickHandler( )

無其他變化,只是將任務切換從函數體內修改到函數體外

/* port.c */
// SysTick 中斷
void xPortSysTickHandler(void)
{
	// 關中斷
	vPortRaiseBASEPRI();
	// 更新系統時基
	if(xTaskIncrementTick() != pdFALSE)
	{
		taskYIELD();
	}
	// 開中斷
	vPortSetBASEPRI(0);
}

3、實驗

3.1、測試

FreeRTOS簡單內核實現6 優先順序 文章中 "3.1、測試" 小節內容一致

如果使用的開發環境為 Keil 且程式工作不正常,可以勾選 Use MicroLIB 試試,如下圖所示

3.2、待改進

當前 RTOS 簡單內核已實現的功能有

  1. 靜態方式創建任務
  2. 手動切換任務
  3. 臨界段保護
  4. 任務阻塞延時
  5. 支持任務優先順序
  6. 阻塞鏈表

當前 RTOS 簡單內核存在的缺點有

  1. 不支持時間片輪詢

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 痞子衡嵌入式半月刊: 第 102 期 這裡分享嵌入式領域有用有趣的項目/工具以及一些熱點新聞,農曆年分二十四節氣,希望在每個交節之日準時發佈一期。 本期刊是開源項目(GitHub: JayHeng/pzh-mcu-bi-weekly),歡迎提交 issue,投稿或推薦你知道的嵌入式那些事兒。 上期回 ...
  • 本文是IMX6ULL開發板spi OLED驅動學習筆記,方便後面查看時快速的回顧,而不需要一點點的看視頻 視頻地址: https://www.bilibili.com/video/BV1Yb4y1t7Uj?p=144&spm_id_from=pageDriver&vd_source=1d93d6a5 ...
  • 在嵌入式Linux設備中,經常使用jffs2文件系統來作為參數區的文件系統格式。至於為什麼要使用jffs2來作為參數區的文件系統,我猜大部分人都沒有做過多的思考。 jffs2在2001年被設計出來,距今已過二十多年,現在在嵌入式設備中它還在被大量使用、說明這套設計本身是沒有問題。 但是,你是否有... ...
  • 為了更好的閱讀體驗,請點擊這裡 先學習一下 zsh 的配置吧~ 參考資料 從 0 開始:教你如何配置 zsh powerlevel10k 如何給 Xshell 配置呢 當我安裝完 oh-my-zsh、powerlevel10k、fast-syntax-highlighting、以及若幹(powerl ...
  • 腳本工具可以幫助我們完成一些需要重覆勞動的工作; 基礎語法: "#"為註釋符號 1:#指定腳本運行環境為 /bin/bash #! /bin/bash 2:輸入參數,xxx為變數名,多個變數名用空格隔開read xxx 輸出參數echo xxx 3: 變數和運算符的定義:這是每個編程語言必不缺少的部 ...
  • 在伺服器linux系統環境下,想要上傳和下載文件到本地PC通常是比較麻煩的, 在這個過程中,將層級複雜的文件夾壓縮成壓縮包再進行上傳/下載更為方便, 其中常用到的linux指令就是 zip / unzip, 文件壓縮指令 zip 個人認為,在日常科研中,常用的參數有兩個: -q 不顯示指令執行過程( ...
  • 0、思考與回答 0.1、思考一 為什麼要增加時間片輪詢? 目前的 RTOS 內核已經支持搶占優先順序,即高優先順序的任務會搶占低優先順序的任務得到執行,但是對於同等優先順序的任務,如果不支持時間片輪詢,則只能有一個任務運行,並且由於優先順序相同所以除延時阻塞到期外也不會發生任務調度,因此需要增加時間片輪詢保證 ...
  • 本文介紹在Linux Ubuntu操作系統的電腦中,安裝Anaconda環境與Python語言的方法。 在之前的文章Anaconda與Python環境在Windows中的部署中,我們介紹了在Win10電腦中,安裝Anaconda環境與Python語言的方法;而在本文中,我們就詳細介紹一下在Linux ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...