FreeRTOS任務切換的實現

来源:https://www.cnblogs.com/RAM-YAO/p/18351408
-Advertisement-
Play Games

第十八章 machine.Timer類實驗 1)實驗平臺:正點原子DNK210開發板 2)章節摘自【正點原子】DNK210使用指南 - CanMV版 V1.0 3)購買鏈接:https://detail.tmall.com/item.htm?&id=782801398750 4)全套實驗源碼+手冊+ ...


在進行FreeRTOS任務切換的介紹前,我們先來瞭解一下SVC和PendSV。

SVC和PendSV

SVC(系統服務調用,亦簡稱系統調用)和 PendSV(可懸起系統調用),它們多用於在操作系統之上的軟體開發中。SVC用於產生系統函數調用的請求。操作系統不讓用戶直接訪問硬體,而是通過提供一些系統服務函數,用戶程式通過使用SVC發出對系統服務函數的調用請求,以系統服務函數間接的去訪問硬體。因此當用戶程式想要控制特定的硬體時,它就會產生一個SVC異常,然後操作系統提供的SVC異常服務常式得到執行,它再調用相關的操作系統函數,由它來完成用戶程式請求的服務。

PendSV(可懸起的系統調用),它和 SVC 協同使用。一方面,SVC異常是必須立即得到響應的(若因優先順序不比當前正處理的高,或是其它原因使之無法立即響應,將上訪成硬 fault),應用程式執行 SVC 時都是希望所需的請求立即得到響應。另一方面,PendSV 則不同,它是可以像普通的中斷一樣被懸起的(不像 SVC那樣會上訪)。OS 可以利用它“緩期執行”一個異常,直到其它重要的任務完成後才執行動作。懸起 PendSV 的方法是:手工往 NVIC 的 PendSV 懸起寄存器中寫 1。懸起後,如果優先順序不夠高,則將緩期等待執行。

PendSV 的典型使用場合是在上下文切換時(在不同任務之間切換)。例如,一個系統中有兩個就緒的任務,上下文切換被觸發的場合可以是:

1.執行一個系統調用

2.系統滴答定時器(SYSTICK)中斷

舉個簡單的例子來輔助理解。假設有這麼一個系統,裡面有兩個就緒的任務,並且通過 SysTick 異常啟動上下文切換。

在典型的RTOS中,任務的執行時間被分為多個時間片,每個任務執行相對應時間片。上圖的任務切換是在Systick中斷中完成的,每觸發一次Systick中斷就會進行任務切換。但若在產生Systick異常時正在響應一個中斷,則Systick異常會搶占其中斷。在這種情況下,OS不得執行上下文切換,否則將使中斷請求被延遲,,而且在真實系統中,延時時間往往是不可預知的,這對於實時操作系統來說是不能容忍的。由於存在中斷請求,ARM Cortex-M 不允許返回線程模式,因此,將會產生用法錯誤異常(Usage Fault)。

在一些 RTOS 的設計中,會通過判斷是否存在中斷請求,來決定是否進行任務切換。雖然可以通過檢查 xPSR 或 NVIC 中的中斷活躍寄存器來判斷是否存在中斷請求,但是這樣可能會影響系統的性能,甚至可能出現當某中斷源的頻率和 SysTick 異常的頻率比較接近時,會發生“共振”。中斷源在 SysTick 中斷前後不斷產生中斷請求,導致系統無法進行任務切換的情況。

使用PendSV就能解決這個問題,PendSV 異常會自動延遲上下文切換的請求,直到其它的 ISR 都完成了處理後才放行。為實現這個機制,需要把 PendSV 編程為最低優先順序的異常。如果 OS 檢測到某 IRQ 正在活動並且被 SysTick 搶占,它將懸起一個 PendSV 異常,以便緩期執行上下文切換。

1.任務A呼叫SVC來請求任務切換(例如,任務A正在等待某些工作完成)。

2.內核接到請求,做好上下文切換的準備,並且懸起一個PendSV異常。

3.當退出SVC後,立即進入PendSV,在PendSV中執行上下文切換。

4.當PendSV執行完畢後,返回線程模式,切換到執行任務B。

5.發生了一個中斷,開始執行中斷服務程式。

6.在中斷執行過程中,發生了Systick異常(用於內核時鐘節拍),並且搶占了該中斷。

7.操作系統執行必要的操作,如更新內部時間計數、遍歷延遲任務隊列等,然後掛起PendSV異常,準備進行任務切換。

8.當Systick退出後,回到先前被搶占的ISR中,ISR繼續執行。

9.當中斷執行完畢後,PendSV服務常式開始執行,完成任務切換。

10.當PendSV執行完畢後,回到任務A,同時系統再次進入線程模式。

FreeRTOS中的任務切換就是在PendSV中完成的。

PendSV 中斷服務函數

FreeRTOS 在 PendSV 的中斷中,完成任務切換,PendSV 的中斷服務函數由 FreeRTOS 編寫,將 PendSV 的中斷服務函數定義成函數 xPortPendSVHandler()。針 對 ARM Cortex-M3 和針對 ARM Cortex-M4 和 ARM Cortex-M7 內 核 的 函 數xPortPendSVHandler()稍有不同,其主要原因在於 ARM Cortex-M4 和 ARM Cortex-M7 內核具有浮點單元,因此在進行任務切換的時候,還需考慮是否保護和恢復浮點寄存器的值。針對 ARM Cortex-M3 內核的函數xPortPendSVHandler(),具體的代碼如下所示

__asm void xPortPendSVHandler( void )
{
	/* 導入全局變數及函數 */
	extern uxCriticalNesting;
	extern pxCurrentTCB;
	extern vTaskSwitchContext;
	
	/* 8 位元組對齊 */
	PRESERVE8

	/* 從 PSP 寄存器(進程堆棧指針)中獲取當前任務的堆棧指針 */
	mrs r0, psp
	
	/* isb 指令用於確保指令的嚴格順序執行,以提高代碼的可靠性和正確性 */
	isb
	/* 將 pxCurrentTCB 的 地址值 載入到 r3 寄存器中,即指向當前運行任務控制塊的指針 */
	ldr	r3, =pxCurrentTCB		/* Get the location of the current TCB. */
	/* 將 r3 寄存器中存儲的記憶體地址所對應的數據載入到 r2 寄存器中,即當前運行任務控制塊的首地址 */
	ldr	r2, [r3]

	/* 將 R4~R11 入棧到當前運行任務的任務棧中 */
	stmdb r0!, {r4-r11}			/* Save the remaining registers. */
	/* r0 的內容會被存儲到由 R2 寄存器指定的記憶體地址中
	將當前任務的堆棧指針中的內容存到 R2. R2 指向的地址為此時的任務棧指針 */
	str r0, [r2]				/* Save the new top of stack into the first member of the TCB. */


	/* 將 r3 和 r14 保存到內核堆棧MSP中,然後設置 basepri 寄存器的值為
	configMAX_SYSCALL_INTERRUPT_PRIORITY,屏蔽受 FreeRTOS 管理的所有中斷。
	然後執行數據同步操作(dsb)和指令同步操作(isb),確保之前的操作已完成 */
	stmdb sp!, {r3, r14}
	mov r0, #configMAX_SYSCALL_INTERRUPT_PRIORITY
	msr basepri, r0
	dsb
	isb

	/* 跳轉到函數 vTaskSeitchContext
	 * 主要用於更新 pxCurrentTCB,
	 * 使其指向最高優先順序的就緒態任務
	 */
	bl vTaskSwitchContext
	mov r0, #0
	/* 將 basepri 寄存器的值恢復為 0,為 0 表示允許所有優先順序的中斷 */
	msr basepri, r0
	
	/* 這部分代碼從棧中彈出 r3 和 r14 的值,這兩個寄存器用於保存任務
	控制塊指針和鏈接寄存器。然後,它載入當前任務控制塊中的棧頂指針
	到 r0 中,接著將 r4-r11 的值從棧中彈出並存儲到相應的寄存器中,
	最後使用 msr 指令將當前任務的棧指針存儲到 psp 寄存器中 */
	

	/* 將 R3、R14 重新從 MSP 指向的棧中出棧 
	R3 為 pxCurrentTCB 的地址值,
	pxCurrentTCB 已經在函數 vTaskSwitchContext 中更新為最高優先順序的就緒態任務
	因此 r1 為 pxCurrentTCB 的值,即當前最高優先順序就緒態任務控制塊的首地址 */
	ldmia sp!, {r3, r14}
	ldr r1, [r3]
	/* r0 為最高優先順序就緒態任務的任務棧指針 */
	ldr r0, [r1]				/* pxCurrentTCB中的第一項是任務棧棧頂 */
	
	/* 從最高優先順序就緒態任務的任務棧中出棧 R4~R11 和 R14 
	註意:這裡出棧的 R14 為 EXC_RETURN,其保存了任務是否使用浮點單元的信息 */
	ldmia r0!, {r4-r11}			/* Pop the registers and the critical nesting count. */
	/* 使用 msr 指令將 r0 的值存儲到 psp 寄存器中,用作任務的棧指針 */
	msr psp, r0

	isb
	/*  使用 bx 指令 跳轉到切換後的任務運行
		執行此指令,CPU 會自動從 PSP 指向的任務棧中,
		出棧 R0、R1、R2、R3、R12、LR、PC、xPSR 寄存器,
		接著 CPU 就跳轉到 PC 指向的代碼位置運行,
		也就是任務上次切換時運行到的位置 */
	bx r14
	nop
}

所以從上面的代碼可以分析出,FreeRTOS在進行任務切換的時候首先會對當前任務的狀態信息進行入棧保存,保存到任務的任務棧中。然後調用vTaskSwitchContext函數更新當前最高優先順序任務的指向,然後從切換後的任務的任務棧中進行出棧,恢復新的任務的狀態信息,最後使用bx指令跳轉到切換後的任務執行。

從該函數中只看到了程式保存和恢復CPU信息中的部分寄存器信息(R4R11),這是因為硬體會自動入棧和出棧其他CPU寄存器信息。在任務運行的時候,CPU使用進程堆棧指針PSP最為棧空間來使用,也就是使用運行任務的任務棧。當Systick中斷發生時,在跳轉到Systick中斷服務函數運行前,硬體會自動除將R4R11寄存器的其他CPU寄存器入棧,因此就將任務切換前的部分信息保存到了對應任務的任務棧中,當退出PendSV時,會自動從棧空間中恢復這部分信息,以供任務正常運行。

可以通過結合下麵兩張圖來結合理解:

任務棧如下圖所示:

vTaskSwitchContext() 函數

void vTaskSwitchContext(void)
{
	/* 任務調度器未在運行 */
	if (uxSchedulerSuspended != (UBaseType_t)pdFALSE)
	{
		/* 調度程式當前掛起-不允許進行上下文切換,直接退出函數 */
		xYieldPending = pdTRUE;
	}
	else
	{
		xYieldPending = pdFALSE;
		traceTASK_SWITCHED_OUT();

#if (configGENERATE_RUN_TIME_STATS == 1)
		{
#ifdef portALT_GET_RUN_TIME_COUNTER_VALUE
			portALT_GET_RUN_TIME_COUNTER_VALUE(ulTotalRunTime);
#else
			ulTotalRunTime = portGET_RUN_TIME_COUNTER_VALUE();
#endif

			if (ulTotalRunTime > ulTaskSwitchedInTime)
			{
				pxCurrentTCB->ulRunTimeCounter += (ulTotalRunTime - ulTaskSwitchedInTime);
			}
			else
			{
				mtCOVERAGE_TEST_MARKER();
			}
			ulTaskSwitchedInTime = ulTotalRunTime;
		}
#endif /* configGENERATE_RUN_TIME_STATS */

		taskCHECK_FOR_STACK_OVERFLOW();

		/* 此函數用於將 pxCurrentTCB 更新為指向優先順序最高的就緒態任務 */
		taskSELECT_HIGHEST_PRIORITY_TASK();
		traceTASK_SWITCHED_IN();

#if (configUSE_NEWLIB_REENTRANT == 1)
		{
			_impure_ptr = &(pxCurrentTCB->xNewLib_reent);
		}
#endif /* configUSE_NEWLIB_REENTRANT */
	}
}

該函數內部通過調用巨集函數 taskSELECT_HIGHEST_PRIORITY_TASK(),來將pxCurrentTCB 設置為指向優先順序最高的就緒態任務。

taskSELECT_HIGHEST_PRIORITY_TASK()如下:

#define taskSELECT_HIGHEST_PRIORITY_TASK()                                               \
	{                                                                                    \
		UBaseType_t uxTopPriority;                                                       \
                                                                                         \
		/* Find the highest priority list that contains ready tasks. */                  \
		/* 查找就緒態任務列表中最高的任務優先順序 */                     \
		portGET_HIGHEST_PRIORITY(uxTopPriority, uxTopReadyPriority);                     \
		/* 此任務優先順序不能是最低的任務優先順序 */                        \
		configASSERT(listCURRENT_LIST_LENGTH(&(pxReadyTasksLists[uxTopPriority])) > 0);  \
		/* 讓 pxCurrentTCB 指向該任務優先順序就緒態任務列表中的任務 */ \
		listGET_OWNER_OF_NEXT_ENTRY(pxCurrentTCB, &(pxReadyTasksLists[uxTopPriority]));  \
	}\

需要註意的是,FreeRTOS 提供了兩種從任務優先順序記錄中查找優先順序最高任務優先等級的方式,一種是由純 C 代碼實現的,這種方式適用於所有運行 FreeRTOS 的 MCU;另外一種方式則是使用了硬體計算前導零的指令,因此這種方式並不適用於所有運行 FreeRTOS 的 MCU,而僅適用於具有有相應硬體指令的 MCU。具體使用哪種方式,用戶可以在 FreeRTOSConfig.h 文件中進行配置。

使用此方法就限制了系統最大的優先順序數量不能超過 32,即最高優先等級為 31,不過對於絕大多數的應用場合,32 個任務優先順序等級已經足夠使用了。如果不使用次方法,理論上任務優先順序沒有上限。

軟體方式如下:

#define taskSELECT_HIGHEST_PRIORITY_TASK()                                              \
	{                                                                                   \
		UBaseType_t uxTopPriority = uxTopReadyPriority;                                 \
                                                                                        \
		/* Find the highest priority queue that contains ready tasks. */                \
		while (listLIST_IS_EMPTY(&(pxReadyTasksLists[uxTopPriority])))                  \
		{                                                                               \
			configASSERT(uxTopPriority);                                                \
			--uxTopPriority;                                                            \
		}                                                                               \
                                                                                        \
		/* listGET_OWNER_OF_NEXT_ENTRY indexes through the list, so the tasks of        \
		the	same priority get an equal share of the processor time. */                  \
		listGET_OWNER_OF_NEXT_ENTRY(pxCurrentTCB, &(pxReadyTasksLists[uxTopPriority])); \
		uxTopReadyPriority = uxTopPriority;                                             \
	} /* taskSELECT_HIGHEST_PRIORITY_TASK */

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

-Advertisement-
Play Games
更多相關文章
  • 本章將和大家分享Linux系統中的管道命令、grep命令、sed命令和awk命令。廢話不多說,下麵我們直接進入主題。 一、管道命令 Linux 中的管道命令(pipe)是一種非常強大的特性,它允許你將一個命令的輸出作為另一個命令的輸入。管道命令極大地增強了命令行的靈活性和功能,使得複雜的數據處理任務 ...
  • 1. 配置 1.1 如果是配置全局文件,則編輯/etc/mail.rc 1.2 如果是配置當前用戶,則編輯~/.mailrc 2. 配置文件內容 # 這裡填入smtp地址,這裡的xxx為qq或者163等,如果用的雲伺服器,安全組策略要開放465/25埠,入站和出站都要開放該埠 set smtp= ...
  • 一、背景 在公司軟體的實際開發中,當一個版本的客戶端安裝包本地調試、測試驗證都沒問題後外發,到用戶實際機器上出問題了,怎麼辦? 很多人說,讓客戶給出復現步驟,我來試試!但是按照步驟操作之後還是沒效果。這時你又想到了是不是環境的差異,但是又說不上來是哪裡出問題。提供兩個辦法:1.寫日誌,編一個相近版本 ...
  • 1、用戶操作 阿裡雲預設是 root 用戶,我們一般要自己創建一個用戶,然後給該用戶 sudo 許可權 添加用戶 sudo adduser newUserName 賦予sudo許可權 sudo usermod -aG sudo newUserName 刪除用戶 sudo deluser --remove ...
  • 1、Docker 基本概念 什麼是 Docker? Docker 是一個開源的容器化平臺,允許開發者封裝他們的應用程式及其所有依賴項到一個標準化的單元中,這個單元被稱為“容器”。容器可以在任何支持 Docker 的環境中運行,從而確保應用程式的可移植性和一致性。 Docker 的優勢 一致性和可移植 ...
  • 大家好,我是痞子衡,是正經搞技術的痞子。今天痞子衡給大家分享的是i.MXRT1050在GPIO上增加RC延時電路後導致邊沿中斷誤觸發問題探析。 前段時間有一個 RT1052 客戶反饋了一個有趣的問題,他們設計得是一個帶 LCD 屏交互的應用,應用以官方 SDK 里的 lvgl_demo_widget ...
  • 家裡的機頂盒淘汰下來,博主想要物盡其用,看看是否能將其改造為一臺Linux"開發機",為其安裝Ubuntu系統,故開始倒騰 ...
  • 寫在前面 本隨筆是非常菜的菜雞寫的。如有問題請及時提出。 可以聯繫:[email protected] GitHhub:https://github.com/WindDevil (目前啥也沒有 本節重點 主要是對 任務 的概念進行進一步擴展和延伸:形成 任務運行狀態:任務從開始到結束執行過程中所處的 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...