贛州市城鄉(xiāng)建設(shè)局官方網(wǎng)站風(fēng)云榜百度
文章目錄
- 0x01 FreeRTOS文件夾
- FreeRTOSConfig.h文件內(nèi)容
- 上面定義的宏決定FreeRTOS.h文件中的定義
- 0x02 創(chuàng)建任務(wù)
- 創(chuàng)建靜態(tài)任務(wù)過程configSUPPORT_STATIC_ALLOCATION
- 創(chuàng)建動(dòng)態(tài)任務(wù)過程configSUPPORT_DYNAMIC_ALLOCATION
- 0x03 FreeRTOS啟動(dòng)流程
- 啟動(dòng)流程概述
- 0x04 任務(wù)管理
- 任務(wù)調(diào)度器
- 任務(wù)狀態(tài)遷移
- vTaskSuspend()
- vTaskSuspendAll()
- vTaskResume()
- xTaskResumeFromISR()
- xTaskResumeAll()
- vTaskDelete()
- vTaskDelay()
- vTaskDelayUntil()
- 任務(wù)設(shè)計(jì)要點(diǎn)
- 任務(wù)執(zhí)行的時(shí)間
- 0x05 消息隊(duì)列
- 運(yùn)作原理
- 阻塞機(jī)制
- 消息隊(duì)列控制塊
- 消息隊(duì)列常用函數(shù)
- xQueueCreate()
- xQueueGenericCreate()
- prvInitialiseNewQueue()
- xQueueGenericReset()
- xQueueCreateStatic()
- vQueueDelete()
- 向消息隊(duì)列發(fā)送消息
- xQueueSend()
- xQueueSendToBack()
- xQueueSendFromISR()
- xQueueSendToBackFromISR()
- xQueueSendToFront()
- xQueueSendToFrontFromISR()
- xQueueGenericSend()
- xQueueGenericSendFromISR()
- 向消息隊(duì)列讀取消息函數(shù)
- xQueueReceive()
- xQueuePeek()
- xQueueReceiveFromISR()、xQueuePeekFromISR()
- 消息隊(duì)列使用注意
- 對(duì)消息隊(duì)列進(jìn)行讀寫實(shí)現(xiàn)
- 0x06 信號(hào)量
- 概念
- 二值信號(hào)量
- 計(jì)數(shù)信號(hào)量
- 互斥信號(hào)量
- 遞歸信號(hào)量
- 二值信號(hào)量運(yùn)作機(jī)制
- 計(jì)數(shù)信號(hào)量運(yùn)作機(jī)制
- 信號(hào)量控制塊
- 信號(hào)量函數(shù)接口
- 創(chuàng)建信號(hào)量函數(shù)
- xSemaphoreCreateBinary()
- xSemaphoreCreateCounting()
- xQueueCreateCountingSemaphore()
- 刪除信號(hào)量函數(shù)
- vSemaphoreDelete()
- 信號(hào)量釋放函數(shù)
- xSemaphoreGive()
- xSemaphoreGiveFromISR()
- 信號(hào)量獲取函數(shù)
- xSemaphoreTake()
- xSemaphoreTakeFromISR()
- 信號(hào)量實(shí)現(xiàn)
0x01 FreeRTOS文件夾
FreeRTOS 文件夾下的 Source 文件夾里面包含的是 FreeRTOS內(nèi)核的源代碼,Demo 文件夾里面包含了 FreeRTOS 官方為各個(gè)單片機(jī)移植好的工程代碼。從Demo中可以得到FreeRTOSConfig.h
。
- Source文件夾
-
include
以及各種.c文件包含的是FreeRTOS的通用頭文件和C文件,這兩部分的文件試用于各種編譯器和處理器,是通用的。 -
portblle
:里面很多與編譯器相關(guān)的文件夾,在不同的編譯器中使用不同的支持文件。Keil文件與RVDS的文件是一樣的,其中的MemMang
文件是與內(nèi)存管理相關(guān)的。
-
RVDS
:這個(gè)文件夾包含了各種處理器相關(guān)的文件夾,FreeRTOS需要軟硬結(jié)合,不同的硬件接口文件是不一樣的,需要編寫代碼來進(jìn)行關(guān)聯(lián),這部分關(guān)聯(lián)則叫關(guān)聯(lián)文件,一般由匯編和C聯(lián)合編寫。FreeRTOS 為我們提供了 cortex-m0、m3、m4 和 m7 等內(nèi)核的單片機(jī)的接口文件,只要是使用了這些內(nèi)核的 mcu 都可以使用里面的接口文件。里面的文件,里面只有“port.c”與“portmacro.h”兩個(gè)文件,port.c 文件里面的內(nèi)容是由 FreeRTOS 官方的技術(shù)人員為 Cortex-M3 內(nèi)核的處理器寫的接口文件,里面核心的上下文切換代碼是由匯編語言編寫而成。portmacro.h 則是 port.c 文件對(duì)應(yīng)的頭文件,主要是一些數(shù)據(jù)類型和宏定義。
-
MemMang
:文件夾下存放的是跟內(nèi)存管理相關(guān)的,總共有五個(gè) heap 文件以及一個(gè) readme 說明文件,這五個(gè) heap 文件在移植的時(shí)候必須使用一個(gè),因?yàn)?FreeRTOS 在創(chuàng)建內(nèi)核對(duì)象的時(shí)候使用的是動(dòng)態(tài)分配內(nèi)存,而這些動(dòng)態(tài)內(nèi)存分配的函數(shù)則在這幾個(gè)文件里面實(shí)現(xiàn),不同的分配算法會(huì)導(dǎo)致不同的效率與結(jié)果。 -
Demo
:各種開發(fā)平臺(tái)完整的Demo。 -
FreeRTOS-Plus
:reeRTOS-Plus 文件夾里面包含的是第三方的產(chǎn)品,一般我們不需要使用,FreeRTOSPlus 的預(yù)配置演示項(xiàng)目組件(組件大多數(shù)都要收費(fèi)),大多數(shù)演示項(xiàng)目都是在 Windows 環(huán)境中運(yùn)行的,使用 FreeRTOS windows 模擬器。
FreeRTOSConfig.h文件內(nèi)容
#ifndef FREERTOS_CONFIG_H
#define FREERTOS_CONFIG_H// 針對(duì)不同的編譯器調(diào)用不同的stdint.h文件
// 針對(duì)不同的編譯器調(diào)用不同的 stdint.h 文件,在 MDK 中,我們默
認(rèn)的是__CC_ARM。
#if defined(__ICCARM__) || defined(__CC_ARM) || defined(__GNUC__)#include <stdint.h>extern uint32_t SystemCoreClock;
#endif
// 斷言
#define vAssertCalled(char,int) printf("Error:%s,%d\r\n",char,int)
#define configASSERT(x) if((x)==0) vAssertCalled(__FILE__,__LINE__)// FREERTOS基礎(chǔ)配置選項(xiàng)//1:RTOS使用搶占式調(diào)度器 0:RTOS使用協(xié)作式調(diào)度器(時(shí)間片)
//協(xié)作式操作系統(tǒng)是任務(wù)主動(dòng)釋放CPU后,切換到下一個(gè)任務(wù),任務(wù)切換的時(shí)機(jī)完全取決于正在運(yùn)行的任務(wù)。
#define configUSE_PREEMPTION 1
//支持靜態(tài)內(nèi)存
#define configSUPPORT_STATIC_ALLOCATION 1
//支持動(dòng)態(tài)內(nèi)存申請(qǐng)
#define configSUPPORT_DYNAMIC_ALLOCATION 1// 置 1:使用空閑鉤子(Idle Hook 類似于回調(diào)函數(shù));置 0:忽略空閑鉤子
// 空閑任務(wù)鉤子是一個(gè)函數(shù),這個(gè)函數(shù)由用戶來實(shí)現(xiàn), FreeRTOS 規(guī)定了函數(shù)的名字和參數(shù):void vApplicationIdleHook(void ),
// 這個(gè)函數(shù)在每個(gè)空閑任務(wù)周期都會(huì)被調(diào)用
// 對(duì)于已經(jīng)刪除的 RTOS 任務(wù),空閑任務(wù)可以釋放分配給它們的堆棧內(nèi)存。
// 因此必須保證空閑任務(wù)可以被 CPU 執(zhí)行,使用空閑鉤子函數(shù)設(shè)置 CPU 進(jìn)入省電模式是很常見的,不可以調(diào)用會(huì)引起空閑任務(wù)阻塞的 API 函數(shù)
#define configUSE_IDLE_HOOK 0// 時(shí)間片鉤子是一個(gè)函數(shù),這個(gè)函數(shù)由用戶來實(shí)現(xiàn), FreeRTOS 規(guī)定了函數(shù)的名字和參數(shù):void vApplicationTickHook(void )
// 時(shí)間片中斷可以周期性的調(diào)用, 函數(shù)必須非常短小,不能大量使用堆棧,不能調(diào)用以”FromISR" 或 "FROM_ISR”結(jié)尾的 API 函數(shù)
#define configUSE_TICK_HOOK 0
//置 1:使用時(shí)間片鉤子(Tick Hook);置 0:忽略時(shí)間片鉤子
//寫入實(shí)際的 CPU 內(nèi)核時(shí)鐘頻率,也就是 CPU 指令執(zhí)行頻率,通常稱為 Fclk, Fclk 為供給 CPU 內(nèi)核的時(shí)鐘信號(hào),我們所說的 cpu 主頻為 XX MHz,就是指的這個(gè)時(shí)鐘信號(hào),相應(yīng)的,1/Fclk 即為 cpu 時(shí)鐘周期。
#define configCPU_CLOCK_HZ ( SystemCoreClock )//RTOS 系統(tǒng)節(jié)拍中斷的頻率。即一秒中斷的次數(shù),每次中斷 RTOS 都會(huì)進(jìn)行任務(wù)調(diào)度
//在 FreeRTOS 中,系統(tǒng)延時(shí)和阻塞時(shí)間都是以 tick 為單位,配置 configTICK_RATE_HZ 的值可以改變中斷的頻率,從而間接改變了 FreeRTOS 的時(shí)鐘周期(T=1/f)
#define configTICK_RATE_HZ ((TickType_t)1000)//可使用的最大優(yōu)先級(jí)
//低優(yōu)先級(jí)數(shù)值表示低優(yōu)先級(jí)任務(wù)
#define configMAX_PRIORITIES ( 16 )//空閑任務(wù)使用的堆棧大小
#define configMINIMAL_STACK_SIZE ((uint16_t)128)
///系統(tǒng)所有總的堆大小
//FreeRTOS 內(nèi)核總計(jì)可用的有效的 RAM 大小
#define configTOTAL_HEAP_SIZE ((size_t)3072)//任務(wù)名字字符串長度
//這里定義的長度包括字符串結(jié)束符’\0’
#define configMAX_TASK_NAME_LEN ( 16 )//啟用可視化跟蹤調(diào)試
#define configUSE_TRACE_FACILITY 1
//與宏 configUSE_TRACE_FACILITY 同時(shí)為 1 時(shí)會(huì)編譯下面 3 個(gè)函數(shù)
// prvWriteNameToBuffer()\ vTaskList()\ vTaskGetRunTimeStats()
#define configUSE_STATS_FORMATTING_FUNCTIONS 1//系統(tǒng)節(jié)拍計(jì)數(shù)器變量數(shù)據(jù)類型,1 表示為 16 位無符號(hào)整形,0 表示為 32 位無符號(hào)整形
//這個(gè)值位數(shù)的大小決定了能計(jì)算多少個(gè) tick
#define configUSE_16_BIT_TICKS 0// 互斥量以及隊(duì)列長度設(shè)置
#define configUSE_MUTEXES 1
#define configQUEUE_REGISTRY_SIZE 8//某些運(yùn)行 FreeRTOS 的硬件有兩種方法選擇下一個(gè)要執(zhí)行的任務(wù):通用方法和特定于硬件的方法(以下簡稱“特殊方法”)。
//一般是硬件計(jì)算前導(dǎo)零指令,如果所使用的,MCU 沒有這些硬件指令的話此宏應(yīng)該設(shè)置為 0
//通用方法:
//1.configUSE_PORT_OPTIMISED_TASK_SELECTION 為 0 或者硬件不支持這種特殊方法。
//2.可以用于所有 FreeRTOS 支持的硬件
//3.完全用 C 實(shí)現(xiàn),效率略低于特殊方法。
//4.不強(qiáng)制要求限制最大可用優(yōu)先級(jí)數(shù)目
//特殊方法:
//1.必須將 configUSE_PORT_OPTIMISED_TASK_SELECTION 設(shè)置為 1。
//2.依賴一個(gè)或多個(gè)特定架構(gòu)的匯編指令(一般是類似計(jì)算前導(dǎo)零[CLZ]指令)。
//3.比通用方法更高效
//4.一般強(qiáng)制限定最大可用優(yōu)先級(jí)數(shù)目為 32
#define configUSE_PORT_OPTIMISED_TASK_SELECTION 1
//啟用協(xié)程,啟用協(xié)程以后必須添加文件 croutine.c
#define configUSE_CO_ROUTINES 0
//協(xié)程的有效優(yōu)先級(jí)數(shù)目
#define configMAX_CO_ROUTINE_PRIORITIES ( 2 )//可選函數(shù)配置選項(xiàng)
#define INCLUDE_vTaskPrioritySet 1
#define INCLUDE_uxTaskPriorityGet 1
#define INCLUDE_vTaskDelete 1
#define INCLUDE_vTaskCleanUpResources 0
#define INCLUDE_vTaskSuspend 1
#define INCLUDE_vTaskDelayUntil 0
#define INCLUDE_vTaskDelay 1
#define INCLUDE_xTaskGetSchedulerState 1//與中斷有關(guān)選項(xiàng)
#ifdef __NVIC_PRIO_BITS/* __BVIC_PRIO_BITS will be specified when CMSIS is being used. */#define configPRIO_BITS __NVIC_PRIO_BITS
#else#define configPRIO_BITS 4
#endif//中斷最低優(yōu)先級(jí)
//這里是中斷優(yōu)先級(jí),中斷優(yōu)先級(jí)的數(shù)值越小,優(yōu)先級(jí)越高。而 FreeRTOS 的任務(wù)優(yōu)先級(jí)是,任務(wù)優(yōu)先級(jí)數(shù)值越小,任務(wù)優(yōu)先級(jí)越低
#define configLIBRARY_LOWEST_INTERRUPT_PRIORITY 15//系統(tǒng)可管理的最高中斷優(yōu)先級(jí)
//中斷優(yōu)先級(jí)數(shù)值在 0、1、2、3、4 的這些中斷是不受 FreeRTOS 管理的,不可被屏蔽,也不能調(diào)用 FreeRTOS 中的 API 函數(shù)接口,而中斷優(yōu)先級(jí)在 5 到 15的這些中斷是受到系統(tǒng)管理,可以被屏蔽的。
#define configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY 5
//用于配置 basepri 寄存器的,當(dāng) basepri 設(shè)置為某個(gè)值的時(shí)候,會(huì)讓系統(tǒng)不響應(yīng)比該優(yōu)先級(jí)低的中斷,而優(yōu)先級(jí)比之更高的中斷則不受影響。
#define configKERNEL_INTERRUPT_PRIORITY ( configLIBRARY_LOWEST_INTERRUPT_PRIORITY << (8 - configPRIO_BITS) )
//對(duì)需要配置的 SysTick 與 PendSV 進(jìn)行偏移
//在 port.c 中會(huì)用到 configKERNEL_INTERRUPT_PRIORITY 這個(gè)宏定義來配置SCB_SHPR3(系統(tǒng)處理優(yōu)先級(jí)寄存器,地址為:0xE000 ED20)
//中斷優(yōu)先級(jí) 0(具有最高的邏輯優(yōu)先級(jí))不能被 basepri 寄存器屏蔽,因此,configMAX_SYSCALL_INTERRUPT_PRIORITY 絕不可以設(shè)置成 0。
#define configMAX_SYSCALL_INTERRUPT_PRIORITY ( configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY << (8 - configPRIO_BITS) )//與中斷服務(wù)函數(shù)有關(guān)的配置選項(xiàng)
#define vPortSVCHandler SVC_Handler
#define xPortPendSVHandler PendSV_Handler
#define xPortSysTickHandler SysTick_Handler#endif
上面定義的宏決定FreeRTOS.h文件中的定義
// FreeRTOS基礎(chǔ)配置
// 1使能時(shí)間片調(diào)度(默認(rèn)式使能)
#define configUSE_TIME_SLICING 1
// 置 1:使能低功耗 tickless 模式;置 0:保持系統(tǒng)節(jié)拍(tick)中斷一直運(yùn)行
#define configUSE_TICKLESS_IDLE 0
// 空閑任務(wù)放棄 CPU 使用權(quán)給其他同優(yōu)先級(jí)的用戶任務(wù)
//滿足條件才會(huì)起作用:1:啟用搶占式調(diào)度;2:用戶任務(wù)優(yōu)先級(jí)與空閑任務(wù)優(yōu)先級(jí)相等。一般不建議使用這個(gè)功能,能避免盡量避免,1:設(shè)置用戶任務(wù)優(yōu)先級(jí)比空閑任務(wù)優(yōu)先級(jí)高,2:這個(gè)宏定義配置為 0。
#define configIDLE_SHOULD_YIELD 1
// 是否啟動(dòng)隊(duì)列
#define configUSE_QUEUE_SETS 0
// 開啟任務(wù)通知功能,默認(rèn)開啟
#define configUSE_TASK_NOTIFICATIONS 1
// 使用互斥信號(hào)量
#define configUSE_MUTEXES 0
// 使用遞歸互斥信號(hào)量
#define configUSE_RECURSIVE_MUTEXES 0
// 1 使用計(jì)數(shù)信號(hào)量
#define configUSE_COUNTING_SEMAPHORES 0
// 設(shè)置可以注冊(cè)的信號(hào)量和消息隊(duì)列個(gè)數(shù)
#define configQUEUE_REGISTRY_SIZE 0U
#define configUSE_APPLICATION_TASK_TAG 0
//使用內(nèi)存申請(qǐng)失敗鉤子函數(shù)
#define configUSE_MALLOC_FAILED_HOOK 0
//大于 0 時(shí)啟用堆棧溢出檢測功能,如果使用此功能用戶必須提供一個(gè)棧溢出鉤子函數(shù),如果使用的話,此值可以為 1 或者 2,因?yàn)橛袃煞N棧溢出檢測方法
#define configCHECK_FOR_STACK_OVERFLOW 0
//啟用運(yùn)行時(shí)間統(tǒng)計(jì)功能
#define configGENERATE_RUN_TIME_STATS 0
//啟用軟件定時(shí)器
#define configUSE_TIMERS 0
- 搶占式調(diào)度:在這種調(diào)度方式中,系統(tǒng)總是選擇優(yōu)先級(jí)最高的任務(wù)進(jìn)行調(diào)度,并且 一旦高優(yōu)先級(jí)的任務(wù)準(zhǔn)備就緒之后,它就會(huì)馬上被調(diào)度而不等待低優(yōu)先級(jí)的任務(wù)主動(dòng)放棄 CPU,高優(yōu)先級(jí)的任務(wù)搶占了低優(yōu)先級(jí)任務(wù)的 CPU 使用權(quán),這就是搶占。在實(shí)時(shí)操作系統(tǒng)中,這樣子的方式往往是最適用的。而協(xié)作式調(diào)度則是由任務(wù)主動(dòng)放棄CPU,然后才進(jìn)行任務(wù)調(diào)度。
- 當(dāng)優(yōu)先級(jí)相同的時(shí)候,就會(huì)采用時(shí)間片調(diào)度,這意味著 RTOS 調(diào)度器總是運(yùn)行處于最高優(yōu)先級(jí)的就緒任務(wù),在每個(gè)FreeRTOS 系統(tǒng)節(jié)拍中斷時(shí)在相同優(yōu)先級(jí)的多個(gè)任務(wù)間進(jìn)行任務(wù)切換。如果宏configUSE_TIME_SLICING 設(shè)置為 0,FreeRTOS 調(diào)度器仍然總是運(yùn)行處于最高優(yōu)先級(jí)的就 緒任務(wù),但是當(dāng) RTOS 系統(tǒng)節(jié)拍中斷發(fā)生時(shí),相同優(yōu)先級(jí)的多個(gè)任務(wù)之間不再進(jìn)行任務(wù)切換,而是在執(zhí)行完高優(yōu)先級(jí)的任務(wù)之后才進(jìn)行任務(wù)切換。
0x02 創(chuàng)建任務(wù)
- 任務(wù)里面的延時(shí)函數(shù)必須使用 FreeRTOS 里面提供的延時(shí)函數(shù),并不能使用我們裸機(jī)編程中的那種延時(shí)。
- 這兩種的延時(shí)的區(qū)別是 FreeRTOS 里面的延時(shí)是阻塞延時(shí),即調(diào)用 vTaskDelay()函數(shù)的時(shí)候,當(dāng)前任務(wù)會(huì)被掛起,調(diào)度器會(huì)切換到其它就緒的任務(wù),從而實(shí)現(xiàn)多任務(wù)。
- 如果還是使用裸機(jī)編程中的那種延時(shí),那么整個(gè)任務(wù)就成為了一個(gè)死循環(huán),如果恰好該任務(wù)的優(yōu)先級(jí)是最高的,那么系統(tǒng)永遠(yuǎn)都是在這個(gè)任務(wù)中運(yùn)行,比它優(yōu)先級(jí)更低的任務(wù)無法運(yùn)行,根本無法實(shí)現(xiàn)多任務(wù)。
- 任務(wù)必須是一個(gè)死循環(huán),否則任務(wù)將通過 LR 返回,如果 LR 指向了非法的內(nèi)存就會(huì)產(chǎn)生 HardFault_Handler,而 FreeRTOS 指向一個(gè)任務(wù)退出函數(shù)prvTaskExitError(),里面是一個(gè)死循環(huán),那么任務(wù)返回之后就在死循環(huán)中執(zhí)行,這樣子的任務(wù)是不安全的,所以避免這種情況,任務(wù)一般都是死循環(huán)并且無返回值的。
創(chuàng)建靜態(tài)任務(wù)過程configSUPPORT_STATIC_ALLOCATION
使用靜態(tài)創(chuàng)建任務(wù)時(shí),需要將configSUPPORT_STATIC_ALLOCATION
這個(gè)宏定義為1,并且需要實(shí)現(xiàn)函數(shù)vApplicationGetIdleTaskMemory()與 vApplicationGetTimerTaskMemory(),這兩個(gè)函數(shù)是用戶設(shè)定的空閑(Idle)任務(wù)與定時(shí)器(Timer)任務(wù)的堆棧大小,必須由用戶自己分配,而不能是動(dòng)態(tài)分配。并且需要定義一些全局變量如下:
并且創(chuàng)建好任務(wù)句柄。
使用STM32CubeMX生成的代碼中,使用的是如下的宏定義進(jìn)行線程的創(chuàng)建:
// 靜態(tài)創(chuàng)建
#define osThreadStaticDef(name, thread, priority, instances, stacksz, buffer, control) \
const osThreadDef_t os_thread_def_##name = \
{ #name, (thread), (priority), (instances), (stacksz), (buffer), (control) }
宏定義中,##的作用就是把2個(gè)宏參數(shù)連接為1個(gè)數(shù),或?qū)崿F(xiàn)字符串的連接,#的作用就是將#后面的宏參數(shù)進(jìn)行字符串的操作,也就是將#后面的參數(shù)兩邊加上一對(duì)雙引號(hào)使其成為字符串。
所以
osThreadDef(Display, DisLCD_Task,osPriorityNormal, 0, 128);
//相當(dāng)于
const osThreadDef_t os_thread_def_Display = { "Display", (DisLCD_Task), (osPriorityNormal), (0), (128) }
對(duì)于osThreadDef_t
為一個(gè)結(jié)構(gòu)體,并且具有一個(gè)函數(shù)指針,
typedef struct os_thread_def {char *name; ///< Thread name os_pthread pthread; ///< start address of thread functionosPriority tpriority; ///< initial thread priorityuint32_t instances; ///< maximum number of instances of that thread functionuint32_t stacksize; ///< stack size requirements in bytes; 0 is default stack size
#if( configSUPPORT_STATIC_ALLOCATION == 1 )uint32_t *buffer; ///< stack buffer for static allocation; NULL for dynamic allocationosStaticThreadDef_t *controlblock; ///< control block to hold thread's data for static allocation; NULL for dynamic allocation
#endif
} osThreadDef_t;
typedef void (*os_pthread) (void const *argument);
typedef enum {osPriorityIdle = -3, ///< priority: idle (lowest)osPriorityLow = -2, ///< priority: lowosPriorityBelowNormal = -1, ///< priority: below normalosPriorityNormal = 0, ///< priority: normal (default)osPriorityAboveNormal = +1, ///< priority: above normalosPriorityHigh = +2, ///< priority: highosPriorityRealtime = +3, ///< priority: realtime (highest)osPriorityError = 0x84 ///< system cannot determine priority or thread has illegal priority
} osPriority;
通過以上我們就相當(dāng)于使用任務(wù)名創(chuàng)建了一個(gè)結(jié)構(gòu)體變量,之后傳入函數(shù)osThreadCreate
中進(jìn)行任務(wù)創(chuàng)建:
#define osThread(name) \
&os_thread_def_##name
osThreadDef(defaultTask, StartDefaultTask, osPriorityNormal, 0, 128);
defaultTaskHandle = osThreadCreate(osThread(defaultTask), NULL);
osThreadCreate函數(shù)中調(diào)用了xTaskCreateStatic函數(shù)進(jìn)行創(chuàng)建任務(wù)。
- 在 FreeRTOS 系統(tǒng)中,每一個(gè)任務(wù)都是獨(dú)立的,他們的運(yùn)行環(huán)境都單獨(dú)的保存在他們 的??臻g當(dāng)中。那么在定義好任務(wù)函數(shù)之后,我們還要為任務(wù)定義一個(gè)棧,目前我們使用的是靜態(tài)內(nèi)存,所以任務(wù)棧是一個(gè)獨(dú)立的全局變量。
- 在大多數(shù)系統(tǒng)中需要做??臻g地址對(duì)齊,在 FreeRTOS 中是以 8 字節(jié)大小對(duì)齊,并且會(huì)檢查堆棧是否已經(jīng)對(duì)齊,其中 portBYTE_ALIGNMENT 是在 portmacro.h 里面定義的一個(gè)宏,其值為 8,就是配置為按 8 字節(jié)對(duì)齊,當(dāng)然用戶可以選擇按 1、2、4、8、16、32 等字節(jié)對(duì)齊。
- xTaskCreateStatic這個(gè)函數(shù)將任務(wù)主體函數(shù)、任務(wù)棧、任務(wù)控制塊聯(lián)合在一起。讓任務(wù)可以隨時(shí)被系統(tǒng)啟動(dòng)。
當(dāng)任務(wù)創(chuàng)建好后,是處于任務(wù)就緒(Ready),在就緒態(tài)的任務(wù)可以參與操作系統(tǒng)的調(diào)度。但是此時(shí)任務(wù)僅僅是創(chuàng)建了,還未開啟任務(wù)調(diào)度器,也沒創(chuàng)建空閑任務(wù)與定時(shí)器任務(wù)(如果使能了 configUSE_TIMERS 這個(gè)宏定義),那這兩個(gè)任務(wù)就是在啟動(dòng)任務(wù)調(diào)度器中實(shí)現(xiàn),每個(gè)操作系統(tǒng),任務(wù)調(diào)度器只啟動(dòng)一次,之后就不會(huì)再次執(zhí)行了。開啟調(diào)度使用函數(shù)vTaskStartScheduler()
。
創(chuàng)建動(dòng)態(tài)任務(wù)過程configSUPPORT_DYNAMIC_ALLOCATION
該任務(wù)任務(wù)使用的棧和任務(wù)控制塊是在創(chuàng)建任務(wù)的時(shí)候FreeRTOS 動(dòng)態(tài)分配的,并不是預(yù)先定義好的全局變量。在上面的任務(wù)中,任務(wù)控制塊和任務(wù)棧的內(nèi)存空間都是從內(nèi)部的 SRAM 里面分配的,具體分配到哪個(gè)地址由編譯器決定。
現(xiàn)在我們開始使用動(dòng)態(tài)內(nèi)存,即堆,其實(shí)堆也是內(nèi)存,也屬于 SRAM。FreeRTOS 做法是在 SRAM 里面定義一個(gè)大數(shù)組,也就是堆內(nèi)存,供 FreeRTOS 的動(dòng)態(tài)內(nèi)存分配函數(shù)使用,在第一次使用的時(shí)候,系統(tǒng)會(huì)將定義的堆內(nèi)存進(jìn)行初始化,這些代碼在 FreeRTOS 提供的內(nèi)存管理方案中實(shí)現(xiàn)。
使用動(dòng)態(tài)內(nèi)存時(shí)候,不用跟使用靜態(tài)內(nèi)存那樣要預(yù)先定義好一個(gè)全局的靜態(tài)的任務(wù)控制塊空間。任務(wù)控制塊是在任務(wù)創(chuàng)建的時(shí)候分配內(nèi)存空間創(chuàng)建,任務(wù)創(chuàng)建函數(shù)會(huì)返回一個(gè)指針,用于指向任務(wù)控制塊,所以要預(yù)先為任務(wù)棧定義一個(gè)任務(wù)控制塊指針,也是我們常說的任務(wù)句柄:
//任務(wù)句柄是一個(gè)指針,用于指向一個(gè)任務(wù),當(dāng)任務(wù)創(chuàng)建好之后,它就具有了一個(gè)任務(wù)句柄以后我們要想操作這個(gè)任務(wù)都需要通過這個(gè)任務(wù)句柄,如果是自身的任務(wù)操作自己,那么這個(gè)句柄可以為 NULL。
typedef TaskHandle_t osThreadId;
osThreadId ReceiveHandle;
osThreadId SendHandle;
/* definition and creation of Receive */
osThreadDef(Receive, ReceiveTask, osPriorityIdle, 0, 128);
ReceiveHandle = osThreadCreate(osThread(Receive), NULL);/* definition and creation of Send */
osThreadDef(Send, SendTask, osPriorityIdle, 0, 128);
SendHandle = osThreadCreate(osThread(Send), NULL);// 動(dòng)態(tài)創(chuàng)建
#define osThreadDef(name, thread, priority, instances, stacksz) \
const osThreadDef_t os_thread_def_##name = \
{ #name, (thread), (priority), (instances), (stacksz), NULL, NULL }typedef struct os_thread_def {char *name; ///< Thread name os_pthread pthread; ///< start address of thread functionosPriority tpriority; ///< initial thread priorityuint32_t instances; ///< maximum number of instances of that thread functionuint32_t stacksize; ///< stack size requirements in bytes; 0 is default stack size
#if( configSUPPORT_STATIC_ALLOCATION == 1 )uint32_t *buffer; ///< stack buffer for static allocation; NULL for dynamic allocationosStaticThreadDef_t *controlblock; ///< control block to hold thread's data for static allocation; NULL for dynamic allocation
#endif
} osThreadDef_t;
typedef void (*os_pthread) (void const *argument);
typedef enum {osPriorityIdle = -3, ///< priority: idle (lowest)osPriorityLow = -2, ///< priority: lowosPriorityBelowNormal = -1, ///< priority: below normalosPriorityNormal = 0, ///< priority: normal (default)osPriorityAboveNormal = +1, ///< priority: above normalosPriorityHigh = +2, ///< priority: highosPriorityRealtime = +3, ///< priority: realtime (highest)osPriorityError = 0x84 ///< system cannot determine priority or thread has illegal priority
} osPriority;
此時(shí)調(diào)用的是函數(shù)xTaskCreate
來創(chuàng)建任務(wù),并且在如下進(jìn)行創(chuàng)建:
堆內(nèi)存的大小為 configTOTAL_HEAP_SIZE , 在FreeRTOSConfig.h 中由我們自己定義,configSUPPORT_DYNAMIC_ALLOCATION 這個(gè)宏定義在使用 FreeRTOS 操作系統(tǒng)的時(shí)候必須開啟。
之后即可啟動(dòng)任務(wù):vTaskStartScheduler()。
0x03 FreeRTOS啟動(dòng)流程
第一種啟動(dòng)流程:
這種啟動(dòng)方式也就是上面講解的啟動(dòng)方式。
- 外設(shè)硬件初始化
- RTOS系統(tǒng)初始化
- 創(chuàng)建各種任務(wù)
- 啟動(dòng)RTOS調(diào)度器
- 編寫函數(shù)實(shí)體:
- 任務(wù)實(shí)體通常是一個(gè)不帶返回值的無限循環(huán)的 C 函數(shù),函數(shù)體必須有阻塞的情況出現(xiàn),不然任務(wù)(如果優(yōu)先權(quán)恰好是最高)會(huì)一直在 while 循環(huán)里面執(zhí)行,導(dǎo)致其它任務(wù)沒有執(zhí)行的機(jī)會(huì)。
第二種啟動(dòng)流程:
在 main 函數(shù)中將硬件和 RTOS 系統(tǒng)先初始化好,然后創(chuàng)建一個(gè)啟動(dòng)任務(wù)后就啟動(dòng)調(diào)度器,然后在啟動(dòng)任務(wù)里面創(chuàng)建各種應(yīng)用任務(wù),當(dāng)所有任務(wù)都創(chuàng)建成功后,啟動(dòng)任務(wù)把自己刪除。
啟動(dòng)流程概述
系統(tǒng)上電時(shí)第一個(gè)執(zhí)行的啟動(dòng)文件是由匯編編寫的復(fù)位函數(shù)Reset_Handler
,復(fù)位函數(shù)會(huì)調(diào)用C庫函數(shù)__main
,其函數(shù)主要工作是初始化系統(tǒng)的堆棧。
創(chuàng)建任務(wù)xTaskCreate()函數(shù)
在 main()函數(shù)中,我們直接可以對(duì) FreeRTOS 進(jìn)行創(chuàng)建任務(wù)操作,因?yàn)?FreeRTOS 會(huì)自動(dòng)幫我們做初始化的事情,比如初始化堆內(nèi)存。其實(shí)也就是對(duì)即將使用的堆棧進(jìn)行初始化,根據(jù)任務(wù)個(gè)數(shù)來決定。在此函數(shù)中,進(jìn)行了堆內(nèi)存的初始化:
其分配內(nèi)存函數(shù):
void *pvPortMalloc( size_t xWantedSize )
{
BlockLink_t *pxBlock, *pxPreviousBlock, *pxNewBlockLink;
void *pvReturn = NULL;vTaskSuspendAll();{/*如果這是對(duì) malloc 的第一次調(diào)用,那么堆將需要初始化來設(shè)置空閑塊列表。 */if( pxEnd == NULL ){prvHeapInit();}else{mtCOVERAGE_TEST_MARKER();}....}
}
其堆的初始化函數(shù):
static void prvHeapInit( void )
{BlockLink_t *pxFirstFreeBlock;uint8_t *pucAlignedHeap;size_t uxAddress;size_t xTotalHeapSize = configTOTAL_HEAP_SIZE;/* 確保堆在正確對(duì)齊的邊界上啟動(dòng)。 */uxAddress = ( size_t ) ucHeap;if( ( uxAddress & portBYTE_ALIGNMENT_MASK ) != 0 ){uxAddress += ( portBYTE_ALIGNMENT - 1 );uxAddress &= ~( ( size_t ) portBYTE_ALIGNMENT_MASK );xTotalHeapSize -= uxAddress - ( size_t ) ucHeap;}pucAlignedHeap = ( uint8_t * ) uxAddress;/*xStart 用于保存指向空閑塊列表中第一個(gè)項(xiàng)目的指針。 void 用于防止編譯器警告 */xStart.pxNextFreeBlock = ( void * ) pucAlignedHeap;xStart.xBlockSize = ( size_t ) 0;/*pxEnd 用于標(biāo)記空閑塊列表的末尾,并插入堆空間的末尾 */uxAddress = ( ( size_t ) pucAlignedHeap ) + xTotalHeapSize;uxAddress -= xHeapStructSize;uxAddress &= ~( ( size_t ) portBYTE_ALIGNMENT_MASK );pxEnd = ( void * ) uxAddress;pxEnd->xBlockSize = 0;pxEnd->pxNextFreeBlock = NULL;/*首先,有一個(gè)空閑塊,其大小可以占用整個(gè)堆空間,減去 pxEnd 占用的空間*/pxFirstFreeBlock = ( void * ) pucAlignedHeap;pxFirstFreeBlock->xBlockSize = uxAddress - ( size_t ) pxFirstFreeBlock;pxFirstFreeBlock->pxNextFreeBlock = pxEnd;/*只存在一個(gè)塊 - 它覆蓋整個(gè)可用堆空間。因?yàn)槭莿偝跏蓟亩褍?nèi)存 */xMinimumEverFreeBytesRemaining = pxFirstFreeBlock->xBlockSize;xFreeBytesRemaining = pxFirstFreeBlock->xBlockSize;/* 計(jì)算出size_t變量的頂部位的位置。 */xBlockAllocatedBit = ( ( size_t ) 1 ) << ( ( sizeof( size_t ) * heapBITS_PER_BYTE ) - 1 );
}
開啟調(diào)度器函數(shù)vTaskStartScheduler()
在創(chuàng)建完任務(wù)的時(shí)候,我們需要開啟調(diào)度器,因?yàn)閯?chuàng)建僅僅是把任務(wù)添加到系統(tǒng)中,還沒真正調(diào)度,并且空閑任務(wù)也沒實(shí)現(xiàn),定時(shí)器任務(wù)也沒實(shí)現(xiàn),這些都是在開啟調(diào)度函數(shù)vTaskStartScheduler()中實(shí)現(xiàn)的。為什么要空閑任務(wù)?因?yàn)?FreeRTOS 一旦啟動(dòng),就必須要保證系統(tǒng)中每時(shí)每刻都有一個(gè)任務(wù)處于運(yùn)行態(tài)(Runing),并且空閑任務(wù)不可以被掛起與刪除,空閑任務(wù)的優(yōu)先級(jí)是最低的,以便系統(tǒng)中其他任務(wù)能隨時(shí)搶占空閑任務(wù)的 CPU 使用權(quán)。
void vTaskStartScheduler( void )
{BaseType_t xReturn;/* 創(chuàng)建的是靜態(tài)的空閑任務(wù) */#if( configSUPPORT_STATIC_ALLOCATION == 1 ){StaticTask_t *pxIdleTaskTCBBuffer = NULL;StackType_t *pxIdleTaskStackBuffer = NULL;uint32_t ulIdleTaskStackSize;/* 空閑任務(wù)是使用用戶提供的 RAM 創(chuàng)建的 - 獲取然后 RAM 的地址創(chuàng)建空閑任務(wù)。這是靜態(tài)創(chuàng)建任務(wù),我們不用管 */vApplicationGetIdleTaskMemory( &pxIdleTaskTCBBuffer, &pxIdleTaskStackBuffer, &ulIdleTaskStackSize );xIdleTaskHandle = xTaskCreateStatic( prvIdleTask,configIDLE_TASK_NAME,ulIdleTaskStackSize,( void * ) NULL, /*lint !e961. The cast is not redundant for all compilers. */( tskIDLE_PRIORITY | portPRIVILEGE_BIT ),pxIdleTaskStackBuffer,pxIdleTaskTCBBuffer ); /*lint !e961 MISRA exception, justified as it is not a redundant explicit cast to all supported compilers. */if( xIdleTaskHandle != NULL ){xReturn = pdPASS;}else{xReturn = pdFAIL;}}#else{/* 使用動(dòng)態(tài)分配的 RAM 創(chuàng)建空閑任務(wù)。 */xReturn = xTaskCreate( prvIdleTask,configIDLE_TASK_NAME,configMINIMAL_STACK_SIZE,( void * ) NULL,( tskIDLE_PRIORITY | portPRIVILEGE_BIT ),&xIdleTaskHandle ); /*lint !e961 MISRA exception, justified as it is not a redundant explicit cast to all supported compilers. */}#endif /* configSUPPORT_STATIC_ALLOCATION */#if ( configUSE_TIMERS == 1 ){/* 如果使能了 configUSE_TIMERS 宏定義 表明使用定時(shí)器,需要?jiǎng)?chuàng)建定時(shí)器任務(wù)*/if( xReturn == pdPASS ){xReturn = xTimerCreateTimerTask();}else{mtCOVERAGE_TEST_MARKER();}}#endif /* configUSE_TIMERS */if( xReturn == pdPASS ){#ifdef FREERTOS_TASKS_C_ADDITIONS_INIT{freertos_tasks_c_additions_init();}#endif/* 此處關(guān)閉中斷,以確保不會(huì)發(fā)生中斷在調(diào)用 xPortStartScheduler()之前或期間。 堆棧的創(chuàng)建的任務(wù)包含打開中斷的狀態(tài)因此,當(dāng)?shù)谝粋€(gè)任務(wù)時(shí),中斷將自動(dòng)重新啟用開始運(yùn)行。 */portDISABLE_INTERRUPTS();#if ( configUSE_NEWLIB_REENTRANT == 1 ){/* 不需要理會(huì),這個(gè)宏定義沒打開 */_impure_ptr = &( pxCurrentTCB->xNewLib_reent );}#endif /* configUSE_NEWLIB_REENTRANT */xNextTaskUnblockTime = portMAX_DELAY;//xSchedulerRunning 等于 pdTRUE,表示調(diào)度器開始運(yùn)行了,而xTickCount 初始化需要初始化為 0,這個(gè) xTickCount 變量用于記錄系統(tǒng)的時(shí)間,在節(jié)拍定時(shí)器(SysTick)中斷服務(wù)函數(shù)中進(jìn)行自加。xSchedulerRunning = pdTRUE;xTickCount = ( TickType_t ) 0U;/* 如果定義了 configGENERATE_RUN_TIME_STATS,則以下內(nèi)容必須定義宏以配置用于生成的計(jì)時(shí)器/計(jì)數(shù)器運(yùn)行時(shí)計(jì)數(shù)器時(shí)基。目前沒啟用該宏定義 */portCONFIGURE_TIMER_FOR_RUN_TIME_STATS();/* 調(diào)用 xPortStartScheduler 函數(shù)配置相關(guān)硬件,如滴答定時(shí)器、FPU、pendsv 等 *///調(diào)用函數(shù) xPortStartScheduler()來啟動(dòng)系統(tǒng)節(jié)拍定時(shí)器(一般都是使用 SysTick)并啟動(dòng)第一個(gè)任務(wù)。因?yàn)樵O(shè)置系統(tǒng)節(jié)拍定時(shí)器涉及到硬件特性,因此函數(shù)xPortStartScheduler()由移植層提供(在 port.c 文件實(shí)現(xiàn)),不同的硬件架構(gòu),這個(gè)函數(shù)的代碼也不相同//在 Cortex-M3 架構(gòu)中,FreeRTOS 為了任務(wù)啟動(dòng)和任務(wù)切換使用了三個(gè)異常:SVC、PendSV 和 SysTickif( xPortStartScheduler() != pdFALSE ){/* 如果 xPortStartScheduler 函數(shù)啟動(dòng)成功,則不會(huì)運(yùn)行到這里*/}else{/* 不會(huì)運(yùn)行到這里,除非調(diào)用 xTaskEndScheduler() 函數(shù) */}}else{/* 只有在內(nèi)核無法啟動(dòng)時(shí)才會(huì)到達(dá)此行,因?yàn)闆]有足夠的堆內(nèi)存來創(chuàng)建空閑任務(wù)或計(jì)時(shí)器任務(wù)。此處使用了斷言,會(huì)輸出錯(cuò)誤信息,方便錯(cuò)誤定位 */configASSERT( xReturn != errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY );}/* 如果 INCLUDE_xTaskGetIdleTaskHandle 設(shè)置為 0,則防止編譯器警告,這意味著在其他任何地方都不使用 xIdleTaskHandle。暫時(shí)不用理會(huì) */( void ) xIdleTaskHandle;
}
如果在 FreeRTOSConfig.h 中使能了 configUSE_TIMERS 這個(gè)宏定義,那么需要創(chuàng)建一個(gè)定時(shí)器任務(wù),這個(gè)定時(shí)器任務(wù)也是調(diào)用 xTaskCreate()函數(shù)完成創(chuàng)建,過程十分簡單,這也是系統(tǒng)的初始化內(nèi)容,在調(diào)度器啟動(dòng)的過程中發(fā)現(xiàn)必要初始化的東西,FreeRTOS 就會(huì)幫我們完成:
xTimerCreateTimerTask()函數(shù):
BaseType_t xTimerCreateTimerTask( void )
{BaseType_t xReturn = pdFAIL;/* 檢查使用了哪些活動(dòng)計(jì)時(shí)器的列表,以及用于與計(jì)時(shí)器服務(wù)通信的隊(duì)列,已經(jīng)初始化。 */prvCheckForValidListAndQueue();if( xTimerQueue != NULL ){//靜態(tài)創(chuàng)建#if( configSUPPORT_STATIC_ALLOCATION == 1 ){StaticTask_t *pxTimerTaskTCBBuffer = NULL;StackType_t *pxTimerTaskStackBuffer = NULL;uint32_t ulTimerTaskStackSize;vApplicationGetTimerTaskMemory( &pxTimerTaskTCBBuffer, &pxTimerTaskStackBuffer, &ulTimerTaskStackSize );xTimerTaskHandle = xTaskCreateStatic( prvTimerTask,configTIMER_SERVICE_TASK_NAME,ulTimerTaskStackSize,NULL,( ( UBaseType_t ) configTIMER_TASK_PRIORITY ) | portPRIVILEGE_BIT,pxTimerTaskStackBuffer,pxTimerTaskTCBBuffer );if( xTimerTaskHandle != NULL ){xReturn = pdPASS;}}// 動(dòng)態(tài)創(chuàng)建#else{xReturn = xTaskCreate( prvTimerTask,configTIMER_SERVICE_TASK_NAME,configTIMER_TASK_STACK_DEPTH,NULL,( ( UBaseType_t ) configTIMER_TASK_PRIORITY ) | portPRIVILEGE_BIT,&xTimerTaskHandle );}#endif /* configSUPPORT_STATIC_ALLOCATION */}else{mtCOVERAGE_TEST_MARKER();}configASSERT( xReturn );return xReturn;
}
xPortStartScheduler()函數(shù)的調(diào)用,主要為了啟動(dòng)系統(tǒng)節(jié)拍定時(shí)器:
- SVC:SVC(系統(tǒng)服務(wù)調(diào)用,亦簡稱系統(tǒng)調(diào)用)用于任務(wù)啟動(dòng),有些操作系統(tǒng)不允許應(yīng)用程序直接訪問硬件,而是通過提供一些系統(tǒng)服務(wù)函數(shù),用戶程序使用 SVC 發(fā)出對(duì)系統(tǒng)服務(wù)函數(shù)的呼叫請(qǐng)求,以這種方法調(diào)用它們來間接訪問硬件,它就會(huì)產(chǎn)生一個(gè)SVC 異常。
- **PendSV(可掛起系統(tǒng)調(diào)用)**用于完成任務(wù)切換,它是可以像普通的中斷一樣被掛起的,它的最大特性是如果當(dāng)前有優(yōu)先級(jí)比它高的中斷在運(yùn)行,PendSV 會(huì)延遲執(zhí)行,直到高優(yōu)先級(jí)中斷執(zhí)行完畢,這樣子產(chǎn)生的 PendSV 中斷就不會(huì)打斷其他中斷的運(yùn)行。
- SysTick 用于產(chǎn)生系統(tǒng)節(jié)拍時(shí)鐘,提供一個(gè)時(shí)間片,如果多個(gè)任務(wù)共享同一個(gè)優(yōu)先級(jí),則每次 SysTick 中斷,下一個(gè)任務(wù)將獲得一個(gè)時(shí)間片。
這里將 PendSV 和 SysTick 異常優(yōu)先級(jí)設(shè)置為最低,這樣任務(wù)切換不會(huì)打斷某個(gè)中斷服務(wù)程序,中斷服務(wù)程序也不會(huì)被延遲,這樣簡化了設(shè)計(jì),有利于系統(tǒng)穩(wěn)定。這樣系統(tǒng)時(shí)間也不會(huì)有偏差,因?yàn)?SysTick 只是當(dāng)次響應(yīng)中斷被延遲了,而 SysTick 是硬件定時(shí)器,它一直在計(jì)時(shí),這一次的溢出產(chǎn)生中斷與下一次的溢出產(chǎn)生中斷的時(shí)間間隔是一樣的,至于系統(tǒng)是否響應(yīng)還是延遲響應(yīng),這個(gè)與 SysTick 無關(guān),它照樣在計(jì)時(shí)。
如果在使用第二種啟動(dòng)流程,也就是:
在 main 函數(shù)中將硬件和 RTOS 系統(tǒng)先初始化好,然后創(chuàng)建一個(gè)啟動(dòng)任務(wù)后就啟動(dòng)調(diào)度器,然后在啟動(dòng)任務(wù)里面創(chuàng)建各種應(yīng)用任務(wù),當(dāng)所有任務(wù)都創(chuàng)建成功后,啟動(dòng)任務(wù)把自己刪除。
如果我們創(chuàng)建的任務(wù)比第一個(gè)任務(wù)的優(yōu)先級(jí)高的時(shí)候怎么辦。假設(shè)是在臨界區(qū)創(chuàng)建任務(wù),任務(wù)只能在退出臨界區(qū)的時(shí)候才執(zhí)行最高優(yōu)先級(jí)任務(wù)。假設(shè)如果當(dāng)前沒有臨界區(qū),就會(huì)分為以下三種情況:
1、應(yīng)用任務(wù)的優(yōu)先級(jí)比初始任務(wù)的優(yōu)先級(jí)高,那創(chuàng)建完后立馬去執(zhí)行剛剛創(chuàng)建的應(yīng)用任務(wù),當(dāng)應(yīng)用任務(wù)被阻塞時(shí),繼續(xù)回到初始任務(wù)被打斷的地方繼續(xù)往下執(zhí)行,直到所有應(yīng)用任務(wù)創(chuàng)建完成,最后初始任務(wù)把自己刪除,完成自己的使命;
2、應(yīng)用任務(wù)的優(yōu)先級(jí)與初始任務(wù)的優(yōu)先級(jí)一樣,那創(chuàng)建完后根據(jù)任務(wù)的時(shí)間片來執(zhí)行,直到所有應(yīng)用任務(wù)創(chuàng)建完成,最后初始任務(wù)把自己刪除,完成自己的使命;
3、應(yīng)用任務(wù)的優(yōu)先級(jí)比初始任務(wù)的優(yōu)先級(jí)低,那創(chuàng)建完后任務(wù)不會(huì)被執(zhí)行,如果還有應(yīng)用任務(wù)緊接著創(chuàng)建應(yīng)用任務(wù),如果應(yīng)用任務(wù)的優(yōu)先級(jí)出現(xiàn)了比初始任務(wù)高或者相等的情況,請(qǐng)參考 1 和 2 的處理方式,直到所有應(yīng)用任務(wù)創(chuàng)建完成,最后初始任務(wù)把自己刪除,完成自己的使命。
0x04 任務(wù)管理
FreeRTOS 的任務(wù)可認(rèn)為是一系列獨(dú)立任務(wù)的集合。每個(gè)任務(wù)在自己的環(huán)境中運(yùn)行。在任何時(shí)刻,只有一個(gè)任務(wù)得到運(yùn)行,FreeRTOS 調(diào)度器決定運(yùn)行哪個(gè)任務(wù)。調(diào)度器會(huì)不斷的啟動(dòng)、停止每一個(gè)任務(wù),宏觀看上去所有的任務(wù)都在同時(shí)在執(zhí)行。
作為任務(wù),不需要對(duì)調(diào)度器的活動(dòng)有所了解,**在任務(wù)切入切出時(shí)保存上下文環(huán)境(寄存器值、堆棧內(nèi)容)是調(diào)度器主要的職責(zé)。**為了實(shí)現(xiàn)這點(diǎn),每個(gè) FreeRTOS 任務(wù)都需要有自己的棧空間。當(dāng)任務(wù)切出時(shí),它的執(zhí)行環(huán)境會(huì)被保存在該任務(wù)的棧空間中,這樣當(dāng)任務(wù)再次運(yùn)行時(shí),就能從堆棧中正確的恢復(fù)上次的運(yùn)行環(huán)境,任務(wù)越多,需要的堆??臻g就越大,而一個(gè)系統(tǒng)能運(yùn)行多少個(gè)任務(wù),取決于系統(tǒng)的可用的 SRAM。
FreeRTOS 中的任務(wù)是搶占式調(diào)度機(jī)制,高優(yōu)先級(jí)的任務(wù)可打斷低優(yōu)先級(jí)任務(wù),低優(yōu)先級(jí)任務(wù)必須在高優(yōu)先級(jí)任務(wù)阻塞或結(jié)束后才能得到調(diào)度。。同時(shí) FreeRTOS 也支持時(shí)間片輪轉(zhuǎn)調(diào)度方式,只不過時(shí)間片的調(diào)度是不允許搶占任務(wù)的 CPU 使用權(quán)。任務(wù)通常會(huì)運(yùn)行在一個(gè)死循環(huán)中,也不會(huì)退出,如果一個(gè)任務(wù)不再需要,可以調(diào)用 FreeRTOS 中的任務(wù)刪除 API 函數(shù)接口顯式地將其刪除。
任務(wù)調(diào)度器
在系統(tǒng)中除了中斷處理函數(shù)、調(diào)度器上鎖部分的代碼和禁止中斷的代碼是不可搶占的之外,系統(tǒng)的其他部分都是可以搶占的。
系統(tǒng)理論上可以支持無數(shù)個(gè)優(yōu)先級(jí)(0 ~ N,優(yōu)先級(jí)數(shù)值越小的任務(wù)優(yōu)先級(jí)越低,0 為最低優(yōu)先級(jí),分配給空閑任務(wù)使用,一般不建議用戶來使用這個(gè)優(yōu)先級(jí)。假如使能了 configUSE_PORT_OPTIMISED_TASK_SELECTION 這個(gè)宏(在 FreeRTOSConfig.h 文件定義),一般強(qiáng)制限定最大可用優(yōu)先級(jí)數(shù)目為 32。
在系統(tǒng)中,當(dāng)有比當(dāng)前任務(wù)優(yōu)先級(jí)更高的任務(wù)就緒時(shí),當(dāng)前任務(wù)將立刻被換出,高優(yōu)先級(jí)任務(wù)搶占處理器運(yùn)行。
**一個(gè)操作系統(tǒng)如果只是具備了高優(yōu)先級(jí)任務(wù)能夠“立即”獲得處理器并得到執(zhí)行的特點(diǎn),那么它仍然不算是實(shí)時(shí)操作系統(tǒng)。**例如一個(gè)包含 n 個(gè)就緒任務(wù)的系統(tǒng)中,如果僅僅從頭找到尾,那么這個(gè)時(shí)間將直接和 n 相關(guān),而下一個(gè)就緒任務(wù)抉擇時(shí)間的長短將會(huì)極大的影響系統(tǒng)的實(shí)時(shí)性。
FreeRTOS 內(nèi)核中采用兩種方法尋找最高優(yōu)先級(jí)的任務(wù):
第一種是通用的方法,在就緒鏈表中查找從高優(yōu)先級(jí)往低查找 uxTopPriority,因?yàn)樵趧?chuàng)建任務(wù)的時(shí)候已經(jīng)將優(yōu)先級(jí)進(jìn)行排序,查找到的第一個(gè) uxTopPriority 就是我們需要的任務(wù),然后通過 uxTopPriority 獲取對(duì)應(yīng)的任務(wù)控制塊。
第二種方法則是特殊方法,利用計(jì)算前導(dǎo)零指令 CLZ,直接在uxTopReadyPriority 這個(gè) 32 位的變量中直接得出 uxTopPriority,這樣子就知道哪一個(gè)優(yōu)先級(jí)任務(wù)能夠運(yùn)行,這種調(diào)度算法比普通方法更快捷,但受限于平臺(tái)(在 STM32 中我們就使用這種方法)。
FreeRTOS 內(nèi)核中也允許創(chuàng)建相同優(yōu)先級(jí)的任務(wù)。相同優(yōu)先級(jí)的任務(wù)采用時(shí)間片輪轉(zhuǎn)方式進(jìn)行調(diào)度(也就是通常說的分時(shí)調(diào)度器),時(shí)間片輪轉(zhuǎn)調(diào)度僅在當(dāng)前系統(tǒng)中無更高優(yōu)先級(jí)就緒任務(wù)存在的情況下才有效。為了保證系統(tǒng)的實(shí)時(shí)性,系統(tǒng)盡最大可能地保證高優(yōu)先級(jí)的任務(wù)得以運(yùn)行。任務(wù)調(diào)度的原則是一旦任務(wù)狀態(tài)發(fā)生了改變,并且當(dāng)前運(yùn)行的任務(wù)優(yōu)先級(jí)小于優(yōu)先級(jí)隊(duì)列組中任務(wù)最高優(yōu)先級(jí)時(shí),立刻進(jìn)行任務(wù)切換(除非當(dāng)前系統(tǒng)處于中斷處理程序中或禁止任務(wù)切換的狀態(tài))。
任務(wù)狀態(tài)遷移
- 創(chuàng)建任務(wù)→就緒態(tài)(Ready):任務(wù)創(chuàng)建完成后進(jìn)入就緒態(tài),表明任務(wù)已準(zhǔn)備就緒,隨時(shí)可以運(yùn)行,只等待調(diào)度器進(jìn)行調(diào)度。
- 就緒態(tài)→運(yùn)行態(tài)(Running):發(fā)生任務(wù)切換時(shí),就緒列表中最高優(yōu)先級(jí)的任務(wù)被執(zhí)行,從而進(jìn)入運(yùn)行態(tài)。
- 運(yùn)行態(tài)→就緒態(tài):有更高優(yōu)先級(jí)任務(wù)創(chuàng)建或者恢復(fù)后,會(huì)發(fā)生任務(wù)調(diào)度,此刻就緒列表中最高優(yōu)先級(jí)任務(wù)變?yōu)檫\(yùn)行態(tài),那么原先運(yùn)行的任務(wù)由運(yùn)行態(tài)變?yōu)榫途w態(tài),依然在就緒列表中,等待最高優(yōu)先級(jí)的任務(wù)運(yùn)行完畢繼續(xù)運(yùn)行原來的任務(wù)(此處可以看做是 CPU 使用權(quán)被更高優(yōu)先級(jí)的任務(wù)搶占了)。
- 運(yùn)行態(tài)→阻塞態(tài)(Blocked):正在運(yùn)行的任務(wù)發(fā)生阻塞(掛起、延時(shí)、讀信號(hào)量等待)時(shí),該任務(wù)會(huì)從就緒列表中刪除,任務(wù)狀態(tài)由運(yùn)行態(tài)變成阻塞態(tài),然后發(fā)生任務(wù)切換,運(yùn)行就緒列表中當(dāng)前最高優(yōu)先級(jí)任務(wù)。
- 阻塞態(tài)→就緒態(tài):阻塞的任務(wù)被恢復(fù)后(任務(wù)恢復(fù)、延時(shí)時(shí)間超時(shí)、讀信號(hào)量超時(shí)或讀到信號(hào)量等),此時(shí)被恢復(fù)的任務(wù)會(huì)被加入就緒列表,從而由阻塞態(tài)變成就緒態(tài);如果此時(shí)被恢復(fù)任務(wù)的優(yōu)先級(jí)高于正在運(yùn)行任務(wù)的優(yōu)先級(jí),則會(huì)發(fā)生任務(wù)切換,將該任務(wù)將再次轉(zhuǎn)換任務(wù)狀態(tài),由就緒態(tài)變成運(yùn)行態(tài)。
就緒態(tài)、阻塞態(tài)、運(yùn)行態(tài)→掛起態(tài)(Suspended):任務(wù)可以通過調(diào)用 vTaskSuspend() API 函數(shù)都可以將處于任何狀態(tài)的任務(wù)掛起,被掛起的任務(wù)得不到CPU 的使用權(quán),也不會(huì)參與調(diào)度,除非它從掛起態(tài)中解除。而把一個(gè)掛起狀態(tài)的任務(wù)回復(fù)的唯一途徑是調(diào)用vTaskResume()或者是vTaskResumeFromISR()。
掛起態(tài)與阻塞態(tài)的區(qū)別,當(dāng)任務(wù)有較長的時(shí)間不允許運(yùn)行的時(shí)候,我們可以掛起任務(wù),這樣子調(diào)度器就不會(huì)管這個(gè)任務(wù)的任何信息,直到我們調(diào)用恢復(fù)任務(wù)的 API 函數(shù);而任務(wù)處于阻塞態(tài)的時(shí)候,系統(tǒng)還需要判斷阻塞態(tài)的任務(wù)是否超時(shí),是否可以解除阻塞。
- 阻塞(Blocked):如果任務(wù)當(dāng)前正在等待某個(gè)時(shí)序或外部中斷,我們就說這個(gè)任務(wù)處于阻塞狀態(tài),該任務(wù)不在就緒列表中。
- 掛起態(tài)(Suspended):處于掛起態(tài)的任務(wù)對(duì)調(diào)度器而言是不可見的,讓一個(gè)任務(wù)進(jìn)入掛起狀態(tài)的唯一辦法就是調(diào)用 vTaskSuspend()函數(shù)。
vTaskSuspend()
掛起指定任務(wù)。被掛起的任務(wù)絕不會(huì)得到 CPU 的使用權(quán),不管該任務(wù)具有什么優(yōu)先級(jí),也無法參與調(diào)度,除非他從掛起態(tài)中解除。
如果想要使任務(wù)進(jìn)行掛起,需要設(shè)置宏INCLUDE_vTaskSuspend
配置為1.
void vTaskSuspend( TaskHandle_t xTaskToSuspend ){TCB_t *pxTCB;taskENTER_CRITICAL();{/* 如果在此處傳遞 null,那么它正在被掛起的正在運(yùn)行的任務(wù) */pxTCB = prvGetTCBFromHandle( xTaskToSuspend );traceTASK_SUSPEND( pxTCB );/* 從就緒/阻塞列表中刪除任務(wù)并放入掛起列表中 */if( uxListRemove( &( pxTCB->xStateListItem ) ) == ( UBaseType_t ) 0 ){//更新最高優(yōu)先級(jí)//在使用通用方法找到最高優(yōu)先級(jí)任務(wù)時(shí),它用來記錄最高優(yōu)先級(jí)任務(wù)的優(yōu)先級(jí)。//在使用硬件方法找到最高優(yōu)先級(jí)任務(wù)時(shí),它的每一位(共 32bit)的狀態(tài)代表這個(gè)優(yōu)先級(jí)上邊,有沒有就緒的任務(wù)taskRESET_READY_PRIORITY( pxTCB->uxPriority );}else{mtCOVERAGE_TEST_MARKER();}/* 如果任務(wù)在等待事件,也從等待事件列表中移除 */if( listLIST_ITEM_CONTAINER( &( pxTCB->xEventListItem ) ) != NULL ){( void ) uxListRemove( &( pxTCB->xEventListItem ) );}else{mtCOVERAGE_TEST_MARKER();}// 將任務(wù)狀態(tài)添加到掛起列表中vListInsertEnd( &xSuspendedTaskList, &( pxTCB->xStateListItem ) );#if( configUSE_TASK_NOTIFICATIONS == 1 ){if( pxTCB->ucNotifyState == taskWAITING_NOTIFICATION ){/* The task was blocked to wait for a notification, but isnow suspended, so no notification was received. */pxTCB->ucNotifyState = taskNOT_WAITING_NOTIFICATION;}}#endif}taskEXIT_CRITICAL();if( xSchedulerRunning != pdFALSE ){/* 重置下一個(gè)預(yù)期的解除阻塞時(shí)間,重新計(jì)算一下還要多長時(shí)間執(zhí)行下一個(gè)任務(wù)。如果下個(gè)任務(wù)的解鎖,剛好是被掛起的那個(gè)任務(wù),那么變量 NextTaskUnblockTime 就不對(duì)了,所以要重新從延時(shí)列表中獲取一下。 */taskENTER_CRITICAL();{prvResetNextTaskUnblockTime();}taskEXIT_CRITICAL();}else{mtCOVERAGE_TEST_MARKER();}if( pxTCB == pxCurrentTCB ){if( xSchedulerRunning != pdFALSE ){/* 當(dāng)前的任務(wù)已經(jīng)被掛起。 */configASSERT( uxSchedulerSuspended == 0 );/*調(diào)度器在運(yùn)行時(shí),如果這個(gè)掛起的任務(wù)是當(dāng)前任務(wù),立即切換任務(wù)。*/portYIELD_WITHIN_API();}else{/* 調(diào)度器未運(yùn)行(xSchedulerRunning == pdFALSE ),但 pxCurrentTCB 指向的任務(wù)剛剛被暫停,所以必須調(diào)整 pxCurrentTCB 以指向其他任務(wù)。首先調(diào)用函數(shù) listCURRENT_LIST_LENGTH()判斷一下系統(tǒng)中所有的任務(wù)是不是都被掛起了,也就是查看列表 xSuspendedTaskList的長度是不是等于 uxCurrentNumberOfTasks,事實(shí)上并不會(huì)發(fā)生這種情況,因?yàn)榭臻e任務(wù)是不允許被掛起和阻塞的,必須保證系統(tǒng)中無論如何都有一個(gè)任務(wù)可以運(yùn)行*/if( listCURRENT_LIST_LENGTH( &xSuspendedTaskList ) == uxCurrentNumberOfTasks ){/* 沒有其他任務(wù)準(zhǔn)備就緒,因此將 pxCurrentTCB 設(shè)置回 NULL,以便在創(chuàng)建下一個(gè)任務(wù)時(shí) pxCurrentTCB 將被設(shè)置為指向它,實(shí)際上并不會(huì)執(zhí)行到這里 */pxCurrentTCB = NULL;}else{/*有其他任務(wù),則切換到其他任務(wù)*/vTaskSwitchContext();}}}else{mtCOVERAGE_TEST_MARKER();}}
任務(wù)的掛起與恢復(fù)函數(shù)在很多時(shí)候都是很有用的,比如我們想暫停某個(gè)任務(wù)運(yùn)行一段時(shí)間,但是我們又需要在其恢復(fù)的時(shí)候繼續(xù)工作,那么刪除任務(wù)是不可能的,因?yàn)閯h除了任務(wù)的話,任務(wù)的所有的信息都是不可能恢復(fù)的了,刪除是完完全全刪除了,里面的資源都被系統(tǒng)釋放掉,但是掛起任務(wù)就不會(huì)這樣子,調(diào)用掛起任務(wù)函數(shù),僅僅是將任務(wù)進(jìn)入掛起態(tài),其內(nèi)部的資源都會(huì)保留下來,同時(shí)也不會(huì)參與系統(tǒng)中任務(wù)的調(diào)度,當(dāng)調(diào)用恢復(fù)函數(shù)的時(shí)候,整個(gè)任務(wù)立即從掛起態(tài)進(jìn)入就緒態(tài),并且參與任務(wù)的調(diào)度,如果該任務(wù)的優(yōu)先級(jí)是當(dāng)前就緒態(tài)優(yōu)先級(jí)最高的任務(wù),那么立即會(huì)按照掛起前的任務(wù)狀態(tài)繼續(xù)執(zhí)行該任務(wù),從而達(dá)到我們需要的效果,注意,是繼續(xù)執(zhí)行,也就是說,掛起任務(wù)之前是什么狀態(tài),都會(huì)被系統(tǒng)保留下來,在恢復(fù)的瞬間,繼續(xù)執(zhí)行。
其掛起代碼可以如下:
void ReceiveTask(void const * argument)
{/* USER CODE BEGIN ReceiveTask *//* Infinite loop */int i = 0;for(;;){printf("Task1 count: %d ----------\r\n",i++);if(i==10) vTaskSuspend(NULL);osDelay(1000);}/* USER CODE END ReceiveTask */
}
vTaskSuspendAll()
將所有的任務(wù)進(jìn)行掛起,這個(gè)函數(shù)是可以進(jìn)行嵌套的,其實(shí)就是在掛起調(diào)度器。
調(diào)度器被掛起后則不能進(jìn)行上下文切換,但是中斷還是使能的。 當(dāng)調(diào)度器被掛起的時(shí)候,如果有中斷需要進(jìn)行上下文切換, 那么這個(gè)任務(wù)將會(huì)被掛起,在調(diào)度器恢復(fù)之后才執(zhí)行切換任務(wù)。
void vTaskSuspendAll( void )
{//uxSchedulerSuspended 用于記錄調(diào)度器是否被掛起,該變量默認(rèn)初始值為 pdFALSE,表明調(diào)度器是沒被掛起的,每調(diào)用一次 vTaskSuspendAll()函數(shù)就將變量加一,用于記錄調(diào)用了多少次 vTaskSuspendAll()函數(shù)。++uxSchedulerSuspended;
}
調(diào)度器恢復(fù)可以調(diào)用 xTaskResumeAll() 函數(shù),調(diào)用了多少次的 vTaskSuspendAll() 就要調(diào)用多少次xTaskResumeAll()進(jìn)行恢復(fù)。
vTaskResume()
任務(wù)恢復(fù)就是讓掛起的任務(wù)重新進(jìn)入就緒狀態(tài),恢復(fù)的任務(wù)會(huì)保留掛起前的狀態(tài)信息,在恢復(fù)的時(shí)候根據(jù)掛起時(shí)的狀態(tài)繼續(xù)運(yùn)行。如果被恢復(fù)任務(wù)在所有就緒態(tài)任務(wù)中,處于最高優(yōu)先級(jí)列表的第一位,那么系統(tǒng)將進(jìn)行任務(wù)上下文的切換。如果想使用這個(gè)函數(shù)這個(gè)時(shí)候需要將宏INCLUDE_vTaskSuspend配置為1.
void vTaskResume( TaskHandle_t xTaskToResume )
{/* 根據(jù) xTaskToResume 獲取對(duì)應(yīng)的任務(wù)控制塊 */TCB_t * const pxTCB = ( TCB_t * ) xTaskToResume;/* 檢查要恢復(fù)的任務(wù)是否被掛起,如果沒被掛起,恢復(fù)調(diào)用任務(wù)沒有意義 */configASSERT( xTaskToResume );//該參數(shù)不能為 NULL,同時(shí)也無法恢復(fù)當(dāng)前正在執(zhí)行的任務(wù).if( ( pxTCB != NULL ) && ( pxTCB != pxCurrentTCB ) ){//進(jìn)入臨界區(qū)taskENTER_CRITICAL();{if( prvTaskIsTaskSuspended( pxTCB ) != pdFALSE ){traceTASK_RESUME( pxTCB );/* 由于我們處于臨界區(qū),即使任務(wù)被掛起,我們也可以訪問任務(wù)的狀態(tài)列表。將要恢復(fù)的任務(wù)從掛起列表中刪除*/( void ) uxListRemove( &( pxTCB->xStateListItem ) );/* 將要恢復(fù)的任務(wù)添加到就緒列表中去*/prvAddTaskToReadyList( pxTCB );/*如果剛剛恢復(fù)的任務(wù)優(yōu)先級(jí)比當(dāng)前任務(wù)優(yōu)先級(jí)更高,則需要進(jìn)行任務(wù)的切換*/if( pxTCB->uxPriority >= pxCurrentTCB->uxPriority ){/* 因?yàn)榛謴?fù)的任務(wù)在當(dāng)前情況下的優(yōu)先級(jí)最高,調(diào)用 taskYIELD_IF_USING_PREEMPTION()進(jìn)行一次任務(wù)切換 */taskYIELD_IF_USING_PREEMPTION();}else{mtCOVERAGE_TEST_MARKER();}}else{mtCOVERAGE_TEST_MARKER();}}/* 退出臨界區(qū) */taskEXIT_CRITICAL();}else{mtCOVERAGE_TEST_MARKER();}
}
vTaskResume()函數(shù)用于恢復(fù)掛起的任務(wù)。無論任務(wù)在掛起時(shí)候調(diào)用過多少次這個(gè)vTaskSuspend()函數(shù),也只需調(diào)用一次 vTaskResume ()函數(shù)即可將任務(wù)恢復(fù)運(yùn)行,當(dāng)然,無論調(diào)用多少次的 vTaskResume()函數(shù),也只在任務(wù)是掛起態(tài)的時(shí)候才進(jìn)行恢復(fù)??梢詣?chuàng)建如下兩個(gè)任務(wù)看到其掛起,恢復(fù)的效果:
void ReceiveTask(void const * argument)
{/* USER CODE BEGIN ReceiveTask *//* Infinite loop */int i = 0;for(;;){printf("Task1 count: %d ----------\r\n",i++);if(i%10==0) vTaskSuspend(NULL);osDelay(1000);}/* USER CODE END ReceiveTask */
}void SendTask(void const * argument)
{/* USER CODE BEGIN SendTask *//* Infinite loop */int i = 0;for(;;){printf("Task2 count: %d\r\n",i++);if(i==15) vTaskResume(ReceiveHandle);osDelay(1000);}/* USER CODE END SendTask */
}
xTaskResumeFromISR()
xTaskResumeFromISR()與 vTaskResume()一樣都是用于恢復(fù)被掛起的任務(wù),不一樣的是 xTaskResumeFromISR() 專門用在中斷服務(wù)程序中。無 論 通 過 調(diào) 用 一 次 或 多 次vTaskSuspend()函數(shù)而被掛起的任務(wù),也只需調(diào)用一次 xTaskResumeFromISR()函數(shù)即可解掛。
這個(gè)函數(shù)使用前需要把宏INCLUDE_vTaskSuspend 和INCLUDE_vTaskResumeFromISR 都定義為 1 才有效。
BaseType_t xTaskResumeFromISR( TaskHandle_t xTaskToResume )
{//定義一個(gè)是否需要進(jìn)行任務(wù)切換的變量 xYieldRequired,默認(rèn)為pdFALSE,當(dāng)任務(wù)恢復(fù)成功并且需要任務(wù)切換的話則重置為 pdTRUE,以表示需要進(jìn)行任務(wù)切換。BaseType_t xYieldRequired = pdFALSE;//根據(jù) xTaskToResume 任務(wù)句柄獲取對(duì)應(yīng)的任務(wù)控制塊。TCB_t * const pxTCB = ( TCB_t * ) xTaskToResume;//用于保存關(guān)閉中斷的狀態(tài)UBaseType_t uxSavedInterruptStatus;//檢查要恢復(fù)的任務(wù)是存在,如果不存在,調(diào)用恢復(fù)任務(wù)函數(shù)沒有任何意義。configASSERT( xTaskToResume );portASSERT_IF_INTERRUPT_PRIORITY_INVALID();//調(diào)用 portSET_INTERRUPT_MASK_FROM_ISR()函數(shù)設(shè)置 basepri寄存器用于屏蔽系統(tǒng)可管理的中斷,防止被處理被其他中斷打斷,當(dāng) basepri 設(shè)置為configMAX_SYSCALL_INTERRUPT_PRIORITY 的時(shí)候(該宏在 FreeRTOSConfig.h 中定義,現(xiàn)在配置為 5),會(huì)讓系統(tǒng)不響應(yīng)比該優(yōu)先級(jí)低的中斷,而優(yōu)先級(jí)比之更高的中斷則不受影響。uxSavedInterruptStatus = portSET_INTERRUPT_MASK_FROM_ISR();{if( prvTaskIsTaskSuspended( pxTCB ) != pdFALSE ){traceTASK_RESUME_FROM_ISR( pxTCB );/* 檢查可以訪問的就緒列表,檢查調(diào)度器是否被掛起 */if( uxSchedulerSuspended == ( UBaseType_t ) pdFALSE ){/* 如果剛剛恢復(fù)的任務(wù)優(yōu)先級(jí)比當(dāng)前任務(wù)優(yōu)先級(jí)更高需要進(jìn)行一次任務(wù)的切換xYieldRequired = pdTRUE 表示需要進(jìn)行任務(wù)切換 */if( pxTCB->uxPriority >= pxCurrentTCB->uxPriority ){xYieldRequired = pdTRUE;}else{mtCOVERAGE_TEST_MARKER();}//可以訪問就緒列表,因此可以將任務(wù)從掛起列表刪除,然后添加到就緒列表中。( void ) uxListRemove( &( pxTCB->xStateListItem ) );prvAddTaskToReadyList( pxTCB );}else{/* 無法訪問就緒列表,因此任務(wù)將被添加到待處理的就緒列表中,直到調(diào)度器被恢復(fù)再進(jìn)行任務(wù)的處理。*///因?yàn)?uxSchedulerSuspended 調(diào)度器被掛起,無法訪問就緒列表,因此任務(wù)將被添加到待處理的就緒列表中,直到調(diào)度器被恢復(fù)再進(jìn)行任務(wù)的處理。vListInsertEnd( &( xPendingReadyList ), &( pxTCB->xEventListItem ) );}}else{mtCOVERAGE_TEST_MARKER();}}//調(diào)用 portCLEAR_INTERRUPT_MASK_FROM_ISR()函數(shù)清除basepri 的設(shè)置,恢復(fù)屏蔽的中斷。portCLEAR_INTERRUPT_MASK_FROM_ISR( uxSavedInterruptStatus );//返回 xYieldRequired 結(jié)果,在外部選擇是否進(jìn)行任務(wù)切換。return xYieldRequired;
}
使用此函數(shù)需要注意:
- 當(dāng)函數(shù)的返回值為 pdTRUE 時(shí):恢復(fù)運(yùn)行的任務(wù)的優(yōu)先級(jí)等于或高于正在運(yùn)行的任 務(wù) , 表 明 在 中 斷 服 務(wù) 函 數(shù) 退 出 后 必 須 進(jìn) 行 一 次 上 下 文 切 換 , 使 用portYIELD_FROM_ISR()進(jìn)行上下文切換。當(dāng)函數(shù)的返回值為 pdFALSE 時(shí):恢復(fù)運(yùn)行的任務(wù)的優(yōu)先級(jí)低于當(dāng)前正在運(yùn)行的任務(wù),表明在中斷服務(wù)函數(shù)退出后不需要進(jìn)行上下文切換。
- xTaskResumeFromISR() 通常被認(rèn)為是一個(gè)危險(xiǎn)的函數(shù),因?yàn)樗恼{(diào)用并非是固定的,中斷可能隨時(shí)來來臨。所以,xTaskResumeFromISR()不能用于任務(wù)和中斷間的同步,如果中斷恰巧在任務(wù)被掛起之前到達(dá),這就會(huì)導(dǎo)致一次中斷丟失(任務(wù)還沒有掛起,調(diào)用 xTaskResumeFromISR()函數(shù)是沒有意義的,只能等下一次中斷)。這種情況下,可以使用信號(hào)量或者任務(wù)通知來同步就可以避免這種情況。
使用:
xTaskResumeAll()
BaseType_t xTaskResumeAll( void )
{TCB_t *pxTCB = NULL;BaseType_t xAlreadyYielded = pdFALSE;/* 如果 uxSchedulerSuspended 為 0,則此函數(shù)與先前對(duì) vTaskSuspendAll()的調(diào)用不匹配,不需要調(diào)用 xTaskResumeAll()恢復(fù)調(diào)度器。 */configASSERT( uxSchedulerSuspended );/*進(jìn)入臨界區(qū)*/taskENTER_CRITICAL();{--uxSchedulerSuspended;if( uxSchedulerSuspended == ( UBaseType_t ) pdFALSE ){if( uxCurrentNumberOfTasks > ( UBaseType_t ) 0U ){/* 將任何準(zhǔn)備好的任務(wù)從待處理就緒列表移動(dòng)到相應(yīng)的就緒列表中。 */while( listLIST_IS_EMPTY( &xPendingReadyList ) == pdFALSE ){pxTCB = ( TCB_t * ) listGET_OWNER_OF_HEAD_ENTRY( ( &xPendingReadyList ) );( void ) uxListRemove( &( pxTCB->xEventListItem ) );( void ) uxListRemove( &( pxTCB->xStateListItem ) );prvAddTaskToReadyList( pxTCB );/* 如果移動(dòng)的任務(wù)的優(yōu)先級(jí)高于當(dāng)前任務(wù),需要進(jìn)行一次任務(wù)的切換 */if( pxTCB->uxPriority >= pxCurrentTCB->uxPriority ){//任務(wù)切換xYieldPending = pdTRUE;}else{mtCOVERAGE_TEST_MARKER();}}if( pxTCB != NULL ){/*在調(diào)度器被掛起時(shí),任務(wù)被解除阻塞,這可能阻止了重新計(jì)算下一個(gè)解除阻塞時(shí)間,在這種情況下,重置下一個(gè)任務(wù)的解除阻塞時(shí)間*/prvResetNextTaskUnblockTime();}/*在掛起時(shí)有滴答定時(shí)器的計(jì)時(shí),并且在這段時(shí)間有任務(wù)解除阻塞,由于調(diào)度器的掛起導(dǎo)致沒法切換任務(wù),當(dāng)恢復(fù)調(diào)度器的時(shí)候應(yīng)立即處理這些任務(wù)。這樣確保了滴答定時(shí)器的計(jì)數(shù)不會(huì)滑動(dòng),并且任何在延時(shí)的任務(wù)都會(huì)在正確的時(shí)間恢復(fù)。*/{UBaseType_t uxPendedCounts = uxPendedTicks; /* Non-volatile copy. */if( uxPendedCounts > ( UBaseType_t ) 0U ){do{// 調(diào)用 xTaskIncrementTick()函數(shù)查找是否有待進(jìn)行切換的任務(wù),如果有則應(yīng)該進(jìn)行任務(wù)切換if( xTaskIncrementTick() != pdFALSE ){xYieldPending = pdTRUE;}else{mtCOVERAGE_TEST_MARKER();}--uxPendedCounts;} while( uxPendedCounts > ( UBaseType_t ) 0U );uxPendedTicks = 0;}else{mtCOVERAGE_TEST_MARKER();}}if( xYieldPending != pdFALSE ){#if( configUSE_PREEMPTION != 0 ){//如果需要任務(wù)切換,則調(diào)用taskYIELD_IF_USING_PREEMPTION()函數(shù)發(fā)起一次任務(wù)切換。xAlreadyYielded = pdTRUE;}#endiftaskYIELD_IF_USING_PREEMPTION();}else{mtCOVERAGE_TEST_MARKER();}}}else{mtCOVERAGE_TEST_MARKER();}}taskEXIT_CRITICAL();return xAlreadyYielded;
}
xTaskResumeAll 函數(shù)的使用方法很簡單,但是要注意,調(diào)用了多少次vTaskSuspendAll()函數(shù)就必須同樣調(diào)用多少次 xTaskResumeAll()函數(shù)。
vTaskDelete()
vTaskDelete()用于刪除一個(gè)任務(wù)。當(dāng)一個(gè)任務(wù)刪除另外一個(gè)任務(wù)時(shí),形參為要?jiǎng)h除任務(wù)創(chuàng)建時(shí)返回的任務(wù)句柄,如果是刪除自身, 則形參為 NULL。 若要使用這個(gè)函數(shù),這個(gè)時(shí)候可以將宏 INCLUDE_vTaskDelete
設(shè)定為1。刪除的任務(wù)將從所有就緒,阻塞,掛起和事件列表中刪除。
void vTaskDelete( TaskHandle_t xTaskToDelete )
{TCB_t *pxTCB;taskENTER_CRITICAL();{/* 獲取任務(wù)控制塊,如果 xTaskToDelete 為 null則刪除任務(wù)自身 */pxTCB = prvGetTCBFromHandle( xTaskToDelete );/* 將任務(wù)從就緒列表中移除 */if( uxListRemove( &( pxTCB->xStateListItem ) ) == ( UBaseType_t ) 0 ){/*清除任務(wù)的就緒優(yōu)先級(jí)變量中的標(biāo)志位*/taskRESET_READY_PRIORITY( pxTCB->uxPriority );}else{mtCOVERAGE_TEST_MARKER();}/* 如果當(dāng)前任務(wù)在等待事件,那么將任務(wù)從事件列表中移除 */if( listLIST_ITEM_CONTAINER( &( pxTCB->xEventListItem ) ) != NULL ){( void ) uxListRemove( &( pxTCB->xEventListItem ) );}else{mtCOVERAGE_TEST_MARKER();}/* 增加uxTaskNumber也使內(nèi)核調(diào)試器可以檢測到任務(wù)列表需要重新生成。 */uxTaskNumber++;if( pxTCB == pxCurrentTCB ){/* 任務(wù)正在刪除自己。 這不能在任務(wù)本身內(nèi)完成,因?yàn)樾枰舷挛那袚Q到另一個(gè)任務(wù)。將任務(wù)放在結(jié)束列表中??臻e任務(wù)會(huì)檢查結(jié)束列表并釋放掉刪除的任務(wù)控制塊和已刪除任務(wù)的堆棧的任何內(nèi)存 */vListInsertEnd( &xTasksWaitingTermination, &( pxTCB->xStateListItem ) );/* 增加 uxDeletedTasksWaitingCleanUp 變量,記錄有多少個(gè)任務(wù)需要釋放內(nèi)存,以便空閑任務(wù)知道有一個(gè)已刪除的任務(wù),然后進(jìn)行內(nèi)存釋放,空閑任務(wù)會(huì)檢查結(jié)束列表 xTasksWaitingTermination*/++uxDeletedTasksWaitingCleanUp;/*任務(wù)刪除鉤子函數(shù) */portPRE_TASK_DELETE_HOOK( pxTCB, &xYieldPending );}else{// 當(dāng)前任務(wù)數(shù)減一,uxCurrentNumberOfTasks 是全局變量,用于記錄當(dāng)前的任務(wù)數(shù)量--uxCurrentNumberOfTasks;// 刪除任務(wù)控制塊prvDeleteTCB( pxTCB );/* 重置下一個(gè)任務(wù)的解除阻塞時(shí)間。重新計(jì)算一下還要多長時(shí)間執(zhí)行下一個(gè)任務(wù),如果下個(gè)任務(wù)的解鎖,剛好是被刪除的任務(wù),那么這就是不正確的,因?yàn)閯h除的任務(wù)對(duì)調(diào)度器而言是不可見的,所以調(diào)度器是無法對(duì)刪除的任務(wù)進(jìn)行調(diào)度,所以要重新從延時(shí)列表中獲取下一個(gè)要解除阻塞的任務(wù)。它是從延時(shí)列表的頭部來獲取的任務(wù) TCB,延時(shí)列表是按延時(shí)時(shí)間排序的 */prvResetNextTaskUnblockTime();}traceTASK_DELETE( pxTCB );}taskEXIT_CRITICAL();/* 如刪除的是當(dāng)前的任務(wù),則需要發(fā)起一次任務(wù)切換 */if( xSchedulerRunning != pdFALSE ){if( pxTCB == pxCurrentTCB ){configASSERT( uxSchedulerSuspended == 0 );portYIELD_WITHIN_API();}else{mtCOVERAGE_TEST_MARKER();}}
}
對(duì)于增加變量uxDeletedTasksWaitingCleanUp的值,該變量需要用于記錄有多少個(gè)任務(wù)需要釋放內(nèi)存,以便空閑任務(wù)知道有多少個(gè)已刪除的任務(wù)需要進(jìn)行內(nèi)存釋放,空閑任務(wù)會(huì)檢查結(jié)束列表xTasksWaitingTermination 并且釋放對(duì)應(yīng)刪除任務(wù)的內(nèi)存空間,主要操作函數(shù)在于**prvCheckTasksWaitingTermination()**來進(jìn)行如下的操作,該函數(shù)在prvIdleTask中調(diào)用:
static void prvCheckTasksWaitingTermination( void )
{/** 這個(gè)函數(shù)是被空閑任務(wù)調(diào)用的 prvIdleTask **/#if ( INCLUDE_vTaskDelete == 1 ){TCB_t *pxTCB;/* uxDeletedTasksWaitingCleanUp 這個(gè)變量的值用于記錄需要進(jìn)行內(nèi)存釋放的任務(wù)個(gè)數(shù),防止在空閑任務(wù)中過于頻繁地調(diào)用 vTaskSuspendAll(). */while( uxDeletedTasksWaitingCleanUp > ( UBaseType_t ) 0U ){taskENTER_CRITICAL();{//獲取對(duì)應(yīng)任務(wù)控制塊pxTCB = ( TCB_t * ) listGET_OWNER_OF_HEAD_ENTRY( ( &xTasksWaitingTermination ) );//將任務(wù)從狀態(tài)列表中刪除( void ) uxListRemove( &( pxTCB->xStateListItem ) );// 當(dāng)前任務(wù)個(gè)數(shù)減一--uxCurrentNumberOfTasks;// uxDeletedTasksWaitingCleanUp 的值減一,直到為 0 退出循環(huán)--uxDeletedTasksWaitingCleanUp;}taskEXIT_CRITICAL();//刪除堆棧//這個(gè)函數(shù)的作用是在任務(wù)刪除自身的時(shí)候才起作用,刪除其他任務(wù)的時(shí)候是直接在刪除函數(shù)中將其他任務(wù)的內(nèi)存釋放掉,不需要在空閑任務(wù)中釋放。prvDeleteTCB( pxTCB );}}#endif /* INCLUDE_vTaskDelete */
}
刪除任務(wù)時(shí),只會(huì)自動(dòng)釋放內(nèi)核本身分配給任務(wù)的內(nèi)存。應(yīng)用程序(而不是內(nèi)核)分配給任務(wù)的內(nèi)存或任何其他資源必須是刪除任務(wù)時(shí)由應(yīng)用程序顯式釋放,否則會(huì)導(dǎo)致內(nèi)存泄漏。
vTaskDelay()
使用此函數(shù)需要將宏INCLUDE_vTaskDelay
置為1,在任務(wù)中,每個(gè)任務(wù)需要是死循環(huán),并且是必須要有阻塞的情況,否則低優(yōu)先級(jí)的任務(wù)則無法執(zhí)行。此函數(shù)用于阻塞延時(shí),調(diào)用該函數(shù)后任務(wù)將進(jìn)入阻塞態(tài),讓出CPU資源。延時(shí)的時(shí)長由形參 xTicksToDelay 決定,單位為系統(tǒng)節(jié)拍周期。vTaskDelay()并不適用與周期性執(zhí)行任務(wù)的場合。此外,其它任務(wù)和中斷活動(dòng), 也會(huì)影響到 vTaskDelay()的調(diào)用(比如調(diào)用前高優(yōu)先級(jí)任務(wù)搶占了當(dāng)前任務(wù)),進(jìn)而影響到任務(wù)的下一次執(zhí)行的時(shí)間。(相對(duì)性延時(shí))
void vTaskDelay( const TickType_t xTicksToDelay )
{
BaseType_t xAlreadyYielded = pdFALSE;/* 延時(shí)時(shí)間要大于 0 個(gè) tick,否則會(huì)進(jìn)行強(qiáng)制切換任務(wù) */if( xTicksToDelay > ( TickType_t ) 0U ){configASSERT( uxSchedulerSuspended == 0 );// 掛起任務(wù)調(diào)度器vTaskSuspendAll();{traceTASK_DELAY();/* 將任務(wù)添加到延時(shí)列表*/prvAddCurrentTaskToDelayedList( xTicksToDelay, pdFALSE );}xAlreadyYielded = xTaskResumeAll();}else{mtCOVERAGE_TEST_MARKER();}/* 強(qiáng)制切換任務(wù),將 PendSV 的 bit28 置 1. */if( xAlreadyYielded == pdFALSE ){portYIELD_WITHIN_API();}else{mtCOVERAGE_TEST_MARKER();}
}
對(duì)于將任務(wù)添加到延時(shí)列表使用的函數(shù)為prvAddCurrentTaskToDelayedList
:
static void prvAddCurrentTaskToDelayedList( TickType_t xTicksToWait, const BaseType_t xCanBlockIndefinitely )
{TickType_t xTimeToWake;const TickType_t xConstTickCount = xTickCount;/* 在將任務(wù)添加到阻止列表之前,從就緒列表中刪除任務(wù),因?yàn)閮蓚€(gè)列表都使用相同的列表項(xiàng)。 */if( uxListRemove( &( pxCurrentTCB->xStateListItem ) ) == ( UBaseType_t ) 0 ){portRESET_READY_PRIORITY( pxCurrentTCB->uxPriority, uxTopReadyPriority );}else{mtCOVERAGE_TEST_MARKER();}#if ( INCLUDE_vTaskSuspend == 1 ){if( ( xTicksToWait == portMAX_DELAY ) && ( xCanBlockIndefinitely != pdFALSE ) ){/* 支持掛起,則當(dāng)前任務(wù)掛起,直接將任務(wù)添加到掛起列表,而不是延時(shí)列表 */vListInsertEnd( &xSuspendedTaskList, &( pxCurrentTCB->xStateListItem ) );}else{/* 計(jì)算喚醒任務(wù)時(shí)間 */xTimeToWake = xConstTickCount + xTicksToWait;/* 列表項(xiàng)按照喚醒時(shí)間順序插入. */listSET_LIST_ITEM_VALUE( &( pxCurrentTCB->xStateListItem ), xTimeToWake );if( xTimeToWake < xConstTickCount ){/* 喚醒時(shí)間如果溢出則加入到溢出列表中 */vListInsert( pxOverflowDelayedTaskList, &( pxCurrentTCB->xStateListItem ) );}else{/* 沒有溢出,添加到延時(shí)列表中 */vListInsert( pxDelayedTaskList, &( pxCurrentTCB->xStateListItem ) );/* 如果進(jìn)入阻塞狀態(tài)的任務(wù)被放置在被阻止任務(wù)列表的頭部,也就是下一個(gè)要喚醒的任務(wù)就是當(dāng)前任務(wù),那么就需要更新xNextTaskUnblockTime 的值 */if( xTimeToWake < xNextTaskUnblockTime ){xNextTaskUnblockTime = xTimeToWake;}else{mtCOVERAGE_TEST_MARKER();}}}}
}
任務(wù)的延時(shí)在實(shí)際中運(yùn)用特別多,因?yàn)樾枰獣和R粋€(gè)任務(wù),讓任務(wù)放棄 CPU,延時(shí)結(jié)束后再繼續(xù)運(yùn)行該任務(wù),如果任務(wù)中沒有阻塞的話,比該任務(wù)優(yōu)先級(jí)低的任務(wù)則無法得到CPU 的使用權(quán),就無法運(yùn)行。
vTaskDelayUntil()
這個(gè)函數(shù)是絕對(duì)延時(shí)函數(shù),這個(gè)絕對(duì)延時(shí)常用于較精確的周期運(yùn)行任務(wù),比如我有一個(gè)任務(wù),希望它以固定頻率定期執(zhí)行,而不受外部的影響,任務(wù)從上一次運(yùn)行開始到下一次運(yùn)行開始的時(shí)間間隔是絕對(duì)的,而不是相對(duì)的。需要設(shè)置宏INCLUDE_vTaskDelayUntil
。
與vTaskDelay()的區(qū)別在于:
vTaskDelay ()的延時(shí)是相對(duì)的,是不確定的,它的延時(shí)是等 vTaskDelay ()調(diào)用完畢后開始計(jì)算的。并且 vTaskDelay ()延時(shí)的時(shí)間到了之后,如果有高優(yōu)先級(jí)的任務(wù)或者中斷正在執(zhí)行,被延時(shí)阻塞的任務(wù)并不會(huì)馬上解除阻塞,所有每次執(zhí)行任務(wù)的周期并不完全確定。
而vTaskDelayUntil()延時(shí)是絕對(duì)的,適用于周期性執(zhí)行的任務(wù)。當(dāng)(*pxPreviousWakeTime + xTimeIncrement)時(shí)間到達(dá)后,vTaskDelayUntil()函數(shù)立刻返回,如果任務(wù)是最高優(yōu)先級(jí)的,那么任務(wù)會(huì)立馬解除阻塞。
void vTaskDelayUntil( TickType_t * const pxPreviousWakeTime, const TickType_t xTimeIncrement )
{TickType_t xTimeToWake;BaseType_t xAlreadyYielded, xShouldDelay = pdFALSE;configASSERT( pxPreviousWakeTime );configASSERT( ( xTimeIncrement > 0U ) );configASSERT( uxSchedulerSuspended == 0 );vTaskSuspendAll();{/* 獲取開始進(jìn)行延時(shí)的時(shí)間點(diǎn)*/const TickType_t xConstTickCount = xTickCount;/* 計(jì)算延時(shí)到達(dá)的時(shí)間,也就是喚醒任務(wù)的時(shí)間 *///周期循環(huán)時(shí)間xTimeToWake = *pxPreviousWakeTime + xTimeIncrement;/*pxPreviousWakeTime 中保存的是上次喚醒時(shí)間,喚醒后需要一定時(shí)間執(zhí)行任務(wù)主體代碼,如果上次喚醒時(shí)間大于當(dāng)前時(shí)間,說明節(jié)拍計(jì)數(shù)器溢出了*/if( xConstTickCount < *pxPreviousWakeTime ){/* 如果喚醒的時(shí)間小于上次喚醒時(shí)間,并且喚醒時(shí)間大于開始計(jì)時(shí)的時(shí)間,這樣子就是相當(dāng)于沒有溢出,也就是保了證周期性延時(shí)時(shí)間大于任務(wù)主體代碼的執(zhí)行時(shí)間 */if( ( xTimeToWake < *pxPreviousWakeTime ) && ( xTimeToWake > xConstTickCount ) ){xShouldDelay = pdTRUE;}else{mtCOVERAGE_TEST_MARKER();}}else{/* 只是喚醒時(shí)間溢出的情況或者都沒有溢出,保證了延時(shí)時(shí)間大于任務(wù)主體代碼的執(zhí)行時(shí)間. */if( ( xTimeToWake < *pxPreviousWakeTime ) || ( xTimeToWake > xConstTickCount ) ){xShouldDelay = pdTRUE;}else{mtCOVERAGE_TEST_MARKER();}}/* 更新上一次的喚醒時(shí)間 */*pxPreviousWakeTime = xTimeToWake;if( xShouldDelay != pdFALSE ){traceTASK_DELAY_UNTIL( xTimeToWake );/* prvAddCurrentTaskToDelayedList()函數(shù)需要的是阻塞時(shí)間而不是喚醒時(shí)間,因此需要減去當(dāng)前的滴答計(jì)數(shù) */prvAddCurrentTaskToDelayedList( xTimeToWake - xConstTickCount, pdFALSE );}else{mtCOVERAGE_TEST_MARKER();}}xAlreadyYielded = xTaskResumeAll();/* 強(qiáng)制執(zhí)行一次上下文切換 */if( xAlreadyYielded == pdFALSE ){portYIELD_WITHIN_API();}else{mtCOVERAGE_TEST_MARKER();}
}
其實(shí)兩個(gè)不同在于調(diào)用prvAddCurrentTaskToDelayedList函數(shù)中的形參不同,計(jì)算延時(shí)到達(dá)的時(shí)間,也就是喚醒任務(wù)的時(shí)間,由于變量xTickCount 與 xTimeToWake 可能會(huì)溢出,所以程序必須檢測各種溢出情況,并且要保證延時(shí)周期不得小于任務(wù)主體代碼執(zhí)行時(shí)間,才能保證絕對(duì)延時(shí)的正確性:
xTimeIncrement:任務(wù)周期時(shí)間。
pxPreviousWakeTime:上一次喚醒任務(wù)的時(shí)間點(diǎn)。
xTimeToWake:本次要喚醒任務(wù)的時(shí)間點(diǎn)。
xConstTickCount:進(jìn)入延時(shí)的時(shí)間點(diǎn)。
- pxPreviousWakeTime 中保存的是上次喚醒時(shí)間,喚醒后需要一定時(shí)間執(zhí)行任務(wù)主體代碼,如果上次喚醒時(shí)間大于當(dāng)前時(shí)間,說明節(jié)拍計(jì)數(shù)器溢出了。
- 如果本次任務(wù)的喚醒時(shí)間小于上次喚醒時(shí)間,但是大于開始進(jìn)入延時(shí)的時(shí)間,進(jìn)入延時(shí)的時(shí)間與任務(wù)喚醒時(shí)間都已經(jīng)溢出了,這樣子就可以看做沒有溢出,其實(shí)也就是保證了周期性延時(shí)時(shí)間大于任務(wù)主體代碼的執(zhí)行時(shí)間。
- 只是喚醒時(shí)間 xTimeToWake 溢出的情況,或者是 xTickCount 與xTimeToWake 都沒溢出的情況,都是符合要求的,因?yàn)槎急WC了周期性延時(shí)時(shí)間大于任務(wù)主體代碼的執(zhí)行時(shí)間。
只有任務(wù)喚醒時(shí)間溢出:
xTickCount 與 xTimeToWake 都沒溢出(正常情況):
可以看出無論是溢出還是沒有溢出,都要求在下次喚醒任務(wù)之前,當(dāng)前任務(wù)主體代碼必須被執(zhí)行完。也就是說任務(wù)執(zhí)行的時(shí)間必須小于任務(wù)周期時(shí)間 xTimeIncrement。每次產(chǎn)生系統(tǒng)節(jié)拍中斷,都會(huì)檢查這兩個(gè)延時(shí)列表,查看延時(shí)的任務(wù)是否到期,如果時(shí)間到,則將任務(wù)從延時(shí)列表中刪除,重新加入就緒列表,任務(wù)從阻塞態(tài)變成就緒態(tài),如果此時(shí)的任務(wù)優(yōu)先級(jí)是最高的,則會(huì)觸發(fā)一次上下文切換。絕對(duì)延時(shí)函數(shù)使用如下:
在使用的時(shí)候要將延時(shí)時(shí)間轉(zhuǎn)化為系統(tǒng)節(jié)拍,在任務(wù)主體之前要調(diào)用延時(shí)函數(shù)。
任務(wù)會(huì)先調(diào)用 vTaskDelayUntil()使任務(wù)進(jìn)入阻塞態(tài),等到時(shí)間到了就從阻塞中解除,然后執(zhí)行主體代碼,任務(wù)主體代碼執(zhí)行完畢。會(huì)繼續(xù)調(diào)用 vTaskDelayUntil()使任務(wù)進(jìn)入阻塞態(tài),然后就是循環(huán)這樣子執(zhí)行。即使任務(wù)在執(zhí)行過程中發(fā)生中斷,那么也不會(huì)影響這個(gè)任務(wù)的運(yùn)行周期,僅僅是縮短了阻塞的時(shí)間而已,到了要喚醒的時(shí)間依舊會(huì)將任務(wù)喚醒。
任務(wù)設(shè)計(jì)要點(diǎn)
FreeRTOS中程序運(yùn)行的上下文包括:
- 中斷服務(wù)函數(shù)
- 普通任務(wù)
- 空閑任務(wù)
中斷服務(wù)函數(shù):
中斷服務(wù)函數(shù)是一種需要特別注意的上下文環(huán)境,它運(yùn)行在非任務(wù)的執(zhí)行環(huán)境下(一般為芯片的一種特殊運(yùn)行模式(也被稱作特權(quán)模式)),在這個(gè)上下文環(huán)境中不能使用掛起當(dāng)前任務(wù)的操作,不允許調(diào)用任何會(huì)阻塞運(yùn)行的 API 函數(shù)接口,另外需要注意的是,中斷服務(wù)程序最好保持精簡短小,快進(jìn)快出,一般在中斷服務(wù)函數(shù)中只做標(biāo)記事件的發(fā)生,然后通知任務(wù),讓對(duì)應(yīng)任務(wù)去執(zhí)行相關(guān)處理,因?yàn)橹袛喾?wù)函數(shù)的優(yōu)先級(jí)高于任何優(yōu)先級(jí)的任務(wù),如果中斷處理時(shí)間過長,將會(huì)導(dǎo)致整個(gè)系統(tǒng)的任務(wù)無法正常運(yùn)行。
普通任務(wù):
做為一個(gè)優(yōu)先級(jí)明確的實(shí)時(shí)系統(tǒng),如果一個(gè)任務(wù)中的程序出現(xiàn)了死循環(huán)操作(此處的死循環(huán)是指沒有阻塞機(jī)制的任務(wù)循環(huán)體),那么比這個(gè)任務(wù)優(yōu)先級(jí)低的任務(wù)都將無法執(zhí)行,當(dāng)然也包括了空閑任務(wù),因?yàn)樗姥h(huán)的時(shí)候,任務(wù)不會(huì)主動(dòng)讓出 CPU,低優(yōu)先級(jí)的任務(wù)是不可能得到CPU 的使用權(quán)的,而高優(yōu)先級(jí)的任務(wù)就可以搶占 CPU。這個(gè)情況在實(shí)時(shí)操作系統(tǒng)中是必須注意的一點(diǎn),所以在任務(wù)中不允許出現(xiàn)死循環(huán)。如果一個(gè)任務(wù)只有就緒態(tài)而無阻塞態(tài),勢必會(huì)影響到其他低優(yōu)先級(jí)任務(wù)的執(zhí)行,所以在進(jìn)行任務(wù)設(shè)計(jì)時(shí),就應(yīng)該保證任務(wù)在不活躍的時(shí)候,任務(wù)可以進(jìn)入阻塞態(tài)以交出 CPU 使用權(quán),這就需要我們自己明確知道什么情況下讓任務(wù)進(jìn)入阻塞態(tài),保證低優(yōu)先級(jí)任務(wù)可以正常運(yùn)行。
空閑任務(wù):
空閑任務(wù)(idle 任務(wù))是 FreeRTOS 系統(tǒng)中沒有其他工作進(jìn)行時(shí)自動(dòng)進(jìn)入的系統(tǒng)任務(wù)。FreeRTOS 為了保證這一點(diǎn),當(dāng)調(diào)用 **vTaskStartScheduler()**時(shí),調(diào)度器會(huì)自動(dòng)創(chuàng)建一個(gè)空閑任務(wù),空閑任務(wù)是一個(gè)非常短小的循環(huán)。
用戶可以通過空閑任務(wù)鉤子方式,在空閑任務(wù)上鉤入自己的功能函數(shù)。通常這個(gè)空閑任務(wù)鉤子能夠完成一些額外的特殊功能,例如系統(tǒng)運(yùn)行狀態(tài)的指示,系統(tǒng)省電模式等。
除了空閑任務(wù)鉤子,FreeRTOS 系統(tǒng)還把空閑任務(wù)用于一些其他的功能,比如當(dāng)系統(tǒng)刪除一個(gè)任務(wù)或一個(gè)動(dòng)態(tài)任務(wù)運(yùn)行結(jié)束時(shí),在執(zhí)行刪除任務(wù)的時(shí)候,并不會(huì)釋放任務(wù)的內(nèi)存空間,只會(huì)將任務(wù)添加到結(jié)束列表中,真正的系統(tǒng)資源回收工作在空閑任務(wù)完成,空閑任務(wù)是唯一一個(gè)不允許出現(xiàn)阻塞情況的任務(wù),因?yàn)?FreeRTOS 需要保證系統(tǒng)永遠(yuǎn)都有一個(gè)可運(yùn)行的任務(wù)。
對(duì)于空閑任務(wù)鉤子上掛接的空閑鉤子函數(shù),它應(yīng)該滿足以下的條件:
- 永遠(yuǎn)不會(huì)被掛起
- 不應(yīng)該陷入死循環(huán),需要留出部分時(shí)間用于系統(tǒng)處理系統(tǒng)資源回收
任務(wù)執(zhí)行的時(shí)間
任務(wù)的執(zhí)行時(shí)間一般是指兩個(gè)方面,一是任務(wù)從開始到結(jié)束的時(shí)間,二是任務(wù)的周期。一般來說處理時(shí)間更短的任務(wù)優(yōu)先級(jí)應(yīng)設(shè)置更高一些。
0x05 消息隊(duì)列
隊(duì)列又稱消息隊(duì)列,是一種常用于任務(wù)間通信的數(shù)據(jù)結(jié)構(gòu),隊(duì)列可以在任務(wù)與任務(wù)間、中斷和任務(wù)間傳遞信息,實(shí)現(xiàn)了任務(wù)接收來自其他任務(wù)或中斷的不固定長度的消息,任務(wù)能夠從隊(duì)列里面讀取消息,當(dāng)隊(duì)列中的消息是空時(shí),讀取消息的任務(wù)將被阻塞,用戶還可以指定阻塞的任務(wù)時(shí)間 xTicksToWait,在這段時(shí)間中,如果隊(duì)列為空,該任務(wù)將保持阻塞狀態(tài)以等待隊(duì)列數(shù)據(jù)有效。
當(dāng)隊(duì)列中有新消息時(shí),被阻塞的任務(wù)會(huì)被喚醒并處理新消息;當(dāng)?shù)却臅r(shí)間超過了指定的阻塞時(shí)間,即使隊(duì)列中尚無有效數(shù)據(jù),任務(wù)也會(huì)自動(dòng)從阻塞態(tài)轉(zhuǎn)為就緒態(tài)。消息隊(duì)列是一種異步的通信方式。
任務(wù)先得到的是最先進(jìn)入消息隊(duì)列的消息,即先進(jìn)先出原則(FIFO),但是也支持后進(jìn)先出原則(LIFO)。
運(yùn)作原理
創(chuàng)建消息隊(duì)列時(shí) FreeRTOS 會(huì)先給消息隊(duì)列分配一塊內(nèi)存空間,這塊內(nèi)存的大小等于消息隊(duì)列控制塊大小加上(單個(gè)消息空間大小與消息隊(duì)列長度的乘積),接著再初始化消息隊(duì)列,此時(shí)消息隊(duì)列為空。
FreeRTOS 的消息隊(duì)列控制塊由多個(gè)元素組成,當(dāng)消息隊(duì)列被創(chuàng)建時(shí),系統(tǒng)會(huì)為控制塊分配對(duì)應(yīng)的內(nèi)存空間,用于保存消息隊(duì)列的一些信息如消息的存儲(chǔ)位置,頭指針 pcHead、尾指針 pcTail、消息大小 uxItemSize 以及隊(duì)列長度 uxLength 等。
同時(shí)每個(gè)消息隊(duì)列都與消息空間在同一段連續(xù)的內(nèi)存空間中,在創(chuàng)建成功的時(shí)候,這些內(nèi)存就被占用了,只有刪除了消息隊(duì)列的時(shí)候,這段內(nèi)存才會(huì)被釋放掉,創(chuàng)建成功的時(shí)候就已經(jīng)分配好每個(gè)消息空間與消息隊(duì)列的容量,無法更改,每個(gè)消息空間可以存放不大于消息大小 uxItemSize 的任意類型的數(shù)據(jù),所有消息隊(duì)列中的消息空間總數(shù)即是消息隊(duì)列的長度,這個(gè)長度可在消息隊(duì)列創(chuàng)建時(shí)指定。
任務(wù)或者中斷服務(wù)程序都可以給消息隊(duì)列發(fā)送消息,當(dāng)發(fā)送消息時(shí),如果隊(duì)列未滿或者允許覆蓋入隊(duì),FreeRTOS 會(huì)將消息拷貝到消息隊(duì)列隊(duì)尾,否則,會(huì)根據(jù)用戶指定的阻塞超時(shí)時(shí)間進(jìn)行阻塞,在這段時(shí)間中,如果隊(duì)列一直不允許入隊(duì),該任務(wù)將保持阻塞狀態(tài)以等待隊(duì)列允許入隊(duì)。(一直阻塞直到有消息到來)
當(dāng)其它任務(wù)從其等待的隊(duì)列中讀取入了數(shù)據(jù)(隊(duì)列未滿),該任務(wù)將自動(dòng)由阻塞態(tài)轉(zhuǎn)移為就緒態(tài)。當(dāng)?shù)却臅r(shí)間超過了指定的阻塞時(shí)間,即使隊(duì)列中還不允許入隊(duì),任務(wù)也會(huì)自動(dòng)從阻塞態(tài)轉(zhuǎn)移為就緒態(tài),此時(shí)發(fā)送消息的任務(wù)或者中斷程序會(huì)收到一個(gè)錯(cuò)誤碼errQUEUE_FULL。
緊急消息,發(fā)送的位置是消息隊(duì)列隊(duì)頭而非隊(duì)尾。
當(dāng)某個(gè)任務(wù)試圖讀一個(gè)隊(duì)列時(shí),其可以指定一個(gè)阻塞超時(shí)時(shí)間。在這段時(shí)間中,如果隊(duì)列為空,該任務(wù)將保持阻塞狀態(tài)以等待隊(duì)列數(shù)據(jù)有效。當(dāng)其它任務(wù)或中斷服務(wù)程序往其等待的隊(duì)列中寫入了數(shù)據(jù),該任務(wù)將自動(dòng)由阻塞態(tài)轉(zhuǎn)移為就緒態(tài)。當(dāng)?shù)却臅r(shí)間超過了指定的阻塞時(shí)間,即使隊(duì)列中尚無有效數(shù)據(jù),任務(wù)也會(huì)自動(dòng)從阻塞態(tài)轉(zhuǎn)移為就緒態(tài)。
阻塞機(jī)制
- 只有在任務(wù)中發(fā)送消息才允許進(jìn)行阻塞狀態(tài),而在中斷中發(fā)送消息不允許帶有阻塞機(jī)制的,需要調(diào)用在中斷中發(fā)送消息的 API 函數(shù)接口,因?yàn)榘l(fā)送消息的上下文環(huán)境是在中斷中,不允許有阻塞的情況。
- 隊(duì)列中無可用消息空間時(shí),說明消息隊(duì)列已滿,此時(shí),系統(tǒng)會(huì)根據(jù)用戶指定的阻塞超時(shí)時(shí)間將任務(wù)阻塞,在指定的超時(shí)時(shí)間內(nèi)如果還不能完成入隊(duì)操作,發(fā)送消息的任務(wù)或者中斷服務(wù)程序會(huì)收到一個(gè)錯(cuò)誤碼 errQUEUE_FULL,然后解除阻塞狀態(tài)。
- 假如有多個(gè)任務(wù)阻塞在一個(gè)消息隊(duì)列中,那么這些阻塞的任務(wù)將按照任務(wù)優(yōu)先級(jí)進(jìn)行排序,優(yōu)先級(jí)高的任務(wù)將優(yōu)先獲得隊(duì)列的訪問權(quán)。
消息隊(duì)列控制塊
typedef struct QueueDefinition
{int8_t *pcHead; /*pcHead 指向隊(duì)列消息存儲(chǔ)區(qū)起始位置,即第一個(gè)消息空間。 */int8_t *pcTail; /*< pcTail 指向隊(duì)列消息存儲(chǔ)區(qū)結(jié)束位置地址 */int8_t *pcWriteTo; /*pcWriteTo 指向隊(duì)列消息存儲(chǔ)區(qū)下一個(gè)可用消息空間 */union /* 使用聯(lián)合體用來確保兩個(gè)互斥的結(jié)構(gòu)體成員不會(huì)同時(shí)出現(xiàn) */{int8_t *pcReadFrom; /*當(dāng)結(jié)構(gòu)體用于隊(duì)列時(shí),pcReadFrom 指向出隊(duì)消息空間的最后一個(gè),就是讀取消息時(shí)候是從 pcReadFrom 指向的空間讀取消息內(nèi)容*/UBaseType_t uxRecursiveCallCount;/*用于計(jì)數(shù),記錄遞歸互斥量被“調(diào)用”的次數(shù)。 */} u;List_t xTasksWaitingToSend; /*是一個(gè)發(fā)送消息阻塞列表,用于保存阻塞在此隊(duì)列的任務(wù),任務(wù)按照優(yōu)先級(jí)進(jìn)行排序,由于隊(duì)列已滿,想要發(fā)送消息的任務(wù)無法發(fā)送消息。*/List_t xTasksWaitingToReceive; /*是一個(gè)獲取消息阻塞列表,用于保存阻塞在此隊(duì)列的任務(wù),任務(wù)按照優(yōu)先級(jí)進(jìn)行排序,由于隊(duì)列是空的,想要獲取消息的任務(wù)無法獲取到消息。 */volatile UBaseType_t uxMessagesWaiting;/*用于記錄當(dāng)前消息隊(duì)列的消息個(gè)數(shù),如果消息隊(duì)列被用于信號(hào)量的時(shí)候,這個(gè)值就表示有效信號(hào)量個(gè)數(shù)。*/UBaseType_t uxLength; /*表示隊(duì)列的長度,也就是能存放多少消息。*/UBaseType_t uxItemSize; /*表示單個(gè)消息的大小。 *///這兩個(gè)成員變量為 queueUNLOCKED 時(shí),表示隊(duì)列未上鎖;當(dāng)這兩個(gè)成員變量為queueLOCKED_UNMODIFIED 時(shí),表示隊(duì)列上鎖。volatile int8_t cRxLock; /*隊(duì)列上鎖后,儲(chǔ)存從隊(duì)列收到的列表項(xiàng)數(shù)目,也就是出隊(duì)的數(shù)量,如果隊(duì)列沒有上鎖,設(shè)置為 queueUNLOCKED。*/volatile int8_t cTxLock; /*隊(duì)列上鎖后,儲(chǔ)存發(fā)送到隊(duì)列的列表項(xiàng)數(shù)目,也就是入隊(duì)的數(shù)量,如果隊(duì)列沒有上鎖,設(shè)置為 queueUNLOCKED。 */#if( ( configSUPPORT_STATIC_ALLOCATION == 1 ) && ( configSUPPORT_DYNAMIC_ALLOCATION == 1 ) )uint8_t ucStaticallyAllocated; /*< Set to pdTRUE if the memory used by the queue was statically allocated to ensure no attempt is made to free the memory. */#endif#if ( configUSE_QUEUE_SETS == 1 )struct QueueDefinition *pxQueueSetContainer;#endif#if ( configUSE_TRACE_FACILITY == 1 )UBaseType_t uxQueueNumber;uint8_t ucQueueType;#endif} xQUEUE;typedef xQUEUE Queue_t;
消息隊(duì)列常用函數(shù)
流程:創(chuàng)建消息隊(duì)列、寫隊(duì)列操作、讀隊(duì)列操作、刪除隊(duì)列。
xQueueCreate()
xQueueCreate()用于創(chuàng)建一個(gè)新的隊(duì)列并返回可用于訪問這個(gè)隊(duì)列的隊(duì)列句柄。隊(duì)列句柄其實(shí)就是一個(gè)指向隊(duì)列數(shù)據(jù)結(jié)構(gòu)類型的指針。
使用xQueueCreate()創(chuàng)建隊(duì)列時(shí),使用的是動(dòng)態(tài)內(nèi)存分配,所以要想使用該函數(shù)必須在FreeRTOSConfig.h 中把 configSUPPORT_DYNAMIC_ALLOCATION 定義為 1 來使能,這是個(gè)用于使能動(dòng)態(tài)內(nèi)存分配的宏,通常情況下,在 FreeRTOS 中,凡是創(chuàng)建任務(wù),隊(duì)列,信號(hào)量和互斥量等內(nèi)核對(duì)象都需要使用動(dòng)態(tài)內(nèi)存分配,所以這個(gè)宏默認(rèn)在 FreeRTOS.h 頭文件中已經(jīng)使能(即定義為 1)。
如果想使用靜態(tài)內(nèi)存,則可以使用 xQueueCreateStatic() 函數(shù)來創(chuàng)建一個(gè)隊(duì)列。使用靜態(tài)創(chuàng)建消息隊(duì)列函數(shù)創(chuàng)建隊(duì)列時(shí)需要的形參更多,需要的內(nèi)存由編譯的時(shí)候預(yù)先分配好,一般很少使用這種方法。
#if( configSUPPORT_DYNAMIC_ALLOCATION == 1 )#define xQueueCreate( uxQueueLength, uxItemSize ) xQueueGenericCreate( ( uxQueueLength ), ( uxItemSize ), ( queueQUEUE_TYPE_BASE ) )
#endif
xQueueGenericCreate()
#if( configSUPPORT_DYNAMIC_ALLOCATION == 1 )QueueHandle_t xQueueGenericCreate( const UBaseType_t uxQueueLength, const UBaseType_t uxItemSize, const uint8_t ucQueueType ){Queue_t *pxNewQueue;size_t xQueueSizeInBytes;uint8_t *pucQueueStorage;configASSERT( uxQueueLength > ( UBaseType_t ) 0 );if( uxItemSize == ( UBaseType_t ) 0 ){/* 消息空間大小為 0 */xQueueSizeInBytes = ( size_t ) 0;}else{/* 分配足夠消息存儲(chǔ)空間,空間的大小為隊(duì)列長度*單個(gè)消息大小 */xQueueSizeInBytes = ( size_t ) ( uxQueueLength * uxItemSize ); }// 向系統(tǒng)申請(qǐng)內(nèi)存,內(nèi)存大小為消息隊(duì)列控制塊大小+消息存儲(chǔ)空間大小pxNewQueue = ( Queue_t * ) pvPortMalloc( sizeof( Queue_t ) + xQueueSizeInBytes );if( pxNewQueue != NULL ){/* 計(jì)算出消息存儲(chǔ)空間的起始地址 */pucQueueStorage = ( ( uint8_t * ) pxNewQueue ) + sizeof( Queue_t );#if( configSUPPORT_STATIC_ALLOCATION == 1 ){/* Queues can be created either statically or dynamically, sonote this task was created dynamically in case it is laterdeleted. */pxNewQueue->ucStaticallyAllocated = pdFALSE;}#endif /* configSUPPORT_STATIC_ALLOCATION */prvInitialiseNewQueue( uxQueueLength, uxItemSize, pucQueueStorage, ucQueueType, pxNewQueue );}else{traceQUEUE_CREATE_FAILED( ucQueueType );}return pxNewQueue;}#endif /* configSUPPORT_STATIC_ALLOCATION */
prvInitialiseNewQueue()
static void prvInitialiseNewQueue( const UBaseType_t uxQueueLength, //消息隊(duì)列長度。const UBaseType_t uxItemSize, //單個(gè)消息大小。uint8_t *pucQueueStorage, //存儲(chǔ)消息起始地址。const uint8_t ucQueueType, //消息隊(duì)列類型Queue_t *pxNewQueue ) //消息隊(duì)列控制塊
{/* 如果configUSE_TRACE_FACILITY未設(shè)置為1,則刪除編譯器關(guān)于未使用參數(shù)的警告。 */( void ) ucQueueType;if( uxItemSize == ( UBaseType_t ) 0 ){/* 沒有為消息存儲(chǔ)分配內(nèi)存,但是 pcHead 指針不能設(shè)置為 NULL,因?yàn)殛?duì)列用作互斥量時(shí),pcHead 要設(shè)置成 NULL。這里只是將 pcHead 指向一個(gè)已知的區(qū)域*/pxNewQueue->pcHead = ( int8_t * ) pxNewQueue;}else{/*設(shè)置 pcHead 指向存儲(chǔ)消息的起始地址 */pxNewQueue->pcHead = ( int8_t * ) pucQueueStorage;}/* 初始化消息隊(duì)列控制塊的其他成員 */pxNewQueue->uxLength = uxQueueLength;pxNewQueue->uxItemSize = uxItemSize;/*重置消息隊(duì)列*/( void ) xQueueGenericReset( pxNewQueue, pdTRUE );#if ( configUSE_TRACE_FACILITY == 1 ){pxNewQueue->ucQueueType = ucQueueType;}#endif /* configUSE_TRACE_FACILITY */#if( configUSE_QUEUE_SETS == 1 ){pxNewQueue->pxQueueSetContainer = NULL;}#endif /* configUSE_QUEUE_SETS */traceQUEUE_CREATE( pxNewQueue );
}
-
ucQueueType:
xQueueGenericReset()
BaseType_t xQueueGenericReset( QueueHandle_t xQueue, BaseType_t xNewQueue )
{Queue_t * const pxQueue = ( Queue_t * ) xQueue;configASSERT( pxQueue );//進(jìn)入臨界段taskENTER_CRITICAL();{//重置消息隊(duì)列的成員變量,pcTail 指向存儲(chǔ)消息內(nèi)存空間的結(jié)束地址。pxQueue->pcTail = pxQueue->pcHead + ( pxQueue->uxLength * pxQueue->uxItemSize );//當(dāng)前消息隊(duì)列中的消息個(gè)數(shù) uxMessagesWaiting 為 0。pxQueue->uxMessagesWaiting = ( UBaseType_t ) 0U;//pcWriteTo 指向隊(duì)列消息存儲(chǔ)區(qū)下一個(gè)可用消息空間,因?yàn)槭侵刂孟㈥?duì)列,就指向消息隊(duì)列的第一個(gè)消息空間,也就是 pcHead 指向的空間。pxQueue->pcWriteTo = pxQueue->pcHead;//pcReadFrom 指向消息隊(duì)列最后一個(gè)消息空間。pxQueue->u.pcReadFrom = pxQueue->pcHead + ( ( pxQueue->uxLength - ( UBaseType_t ) 1U ) * pxQueue->uxItemSize );//消息隊(duì)列沒有上鎖,設(shè)置為 queueUNLOCKED。pxQueue->cRxLock = queueUNLOCKED;pxQueue->cTxLock = queueUNLOCKED;if( xNewQueue == pdFALSE ){/* 如果不是新建一個(gè)消息隊(duì)列,那么之前的消息隊(duì)列可能阻塞了一些任務(wù),需要將其解除阻塞。如果有發(fā)送消息任務(wù)被阻塞,那么需要將它恢復(fù),而如果任務(wù)是因?yàn)樽x取消息而阻塞,那么重置之后的消息隊(duì)列也是空的,則無需被恢復(fù)。 */if( listLIST_IS_EMPTY( &( pxQueue->xTasksWaitingToSend ) ) == pdFALSE ){if( xTaskRemoveFromEventList( &( pxQueue->xTasksWaitingToSend ) ) != pdFALSE ){queueYIELD_IF_USING_PREEMPTION();}else{mtCOVERAGE_TEST_MARKER();}}else{mtCOVERAGE_TEST_MARKER();}}else{/* 如果是新創(chuàng)建一個(gè)消息隊(duì)列,則需要將 xTasksWaitingToSend 列表與 xTasksWaitingToReceive 列表初始化 */vListInitialise( &( pxQueue->xTasksWaitingToSend ) );vListInitialise( &( pxQueue->xTasksWaitingToReceive ) );}}//退出臨界段taskEXIT_CRITICAL();/* A value is returned for calling semantic consistency with previousversions. */return pdPASS;
}
在創(chuàng)建消息隊(duì)列的時(shí)候,是需要用戶自己定義消息隊(duì)列的句柄的,但是注意了,定義了隊(duì)列的句柄并不等于創(chuàng)建了隊(duì)列,創(chuàng)建隊(duì)列必須是調(diào)用消息隊(duì)列創(chuàng)建函數(shù)進(jìn)行創(chuàng)建(可以是靜態(tài)也可以是動(dòng)態(tài)創(chuàng)建),否則,以后根據(jù)隊(duì)列句柄使用消息隊(duì)列的其它函數(shù)的時(shí)候會(huì)發(fā)生錯(cuò)誤,創(chuàng)建完成會(huì)返回消息隊(duì)列的句柄,用戶通過句柄就可使用消息隊(duì)列進(jìn)行發(fā)送與讀取消息隊(duì)列的操作,如果返回的是 NULL 則表示創(chuàng)建失敗。
xQueueCreateStatic()
此函數(shù)為消息隊(duì)列靜態(tài)創(chuàng)建函數(shù),隊(duì)列就是一個(gè)數(shù)據(jù)結(jié)構(gòu),用于任務(wù)間的數(shù)據(jù)的傳遞。每創(chuàng)建一個(gè)新的隊(duì)列都需要為其分 配 RAM , 一 部 分 用 于 存 儲(chǔ) 隊(duì) 列 的 狀 態(tài) , 剩 下 的 作 為 隊(duì) 列 的 存 儲(chǔ) 區(qū) 。 使 用xQueueCreateStatic()創(chuàng)建隊(duì)列時(shí),使用的是靜態(tài)內(nèi)存分配,所以要想使用該函數(shù)必須在FreeRTOSConfig.h 中把 configSUPPORT_STATIC_ALLOCATION 定義為 1 來使能。
#if( configSUPPORT_STATIC_ALLOCATION == 1 )#define xQueueCreateStatic( uxQueueLength, uxItemSize, pucQueueStorage, pxQueueBuffer ) xQueueGenericCreateStatic( ( uxQueueLength ), ( uxItemSize ), ( pucQueueStorage ), ( pxQueueBuffer ), ( queueQUEUE_TYPE_BASE ) )
#endif /* configSUPPORT_STATIC_ALLOCATION */
vQueueDelete()
隊(duì)列刪除函數(shù)是根據(jù)消息隊(duì)列句柄直接刪除的,刪除之后這個(gè)消息隊(duì)列的所有信息都會(huì)被系統(tǒng)回收清空,而且不能再次使用這個(gè)消息隊(duì)列了,但是需要注意的是,如果某個(gè)消息隊(duì)列沒有被創(chuàng)建,那也是無法被刪除的。xQueue 是 vQueueDelete()函數(shù)的形參,是消息隊(duì)列句柄,表示的是要?jiǎng)h除哪個(gè)想隊(duì)列。
void vQueueDelete( QueueHandle_t xQueue )
{Queue_t * const pxQueue = ( Queue_t * ) xQueue;// 斷言configASSERT( pxQueue );traceQUEUE_DELETE( pxQueue );#if ( configQUEUE_REGISTRY_SIZE > 0 ){// 將消息隊(duì)列從注冊(cè)表中刪除vQueueUnregisterQueue( pxQueue );}#endif#if( ( configSUPPORT_DYNAMIC_ALLOCATION == 1 ) && ( configSUPPORT_STATIC_ALLOCATION == 0 ) ){/* 因?yàn)橛玫南㈥?duì)列是動(dòng)態(tài)分配內(nèi)存的,所以需要調(diào)用vPortFree 來釋放消息隊(duì)列的內(nèi)存*/vPortFree( pxQueue );}#elif( ( configSUPPORT_DYNAMIC_ALLOCATION == 1 ) && ( configSUPPORT_STATIC_ALLOCATION == 1 ) ){/* 隊(duì)列可以是靜態(tài)分配的,也可以是動(dòng)態(tài)分配的,因此在嘗試釋放內(nèi)存之前請(qǐng)進(jìn)行檢查。 */if( pxQueue->ucStaticallyAllocated == ( uint8_t ) pdFALSE ){vPortFree( pxQueue );}else{mtCOVERAGE_TEST_MARKER();}}#else{/* 隊(duì)列必須是靜態(tài)分配的,因此不會(huì)被刪除。避免編譯器對(duì)未使用的參數(shù)發(fā)出警告。 */( void ) pxQueue;}#endif /* configSUPPORT_DYNAMIC_ALLOCATION */
}
需要注意的是調(diào)用刪除消息隊(duì)列函數(shù)前,系統(tǒng)應(yīng)存在 xQueueCreate()或 xQueueCreateStatic()函數(shù)創(chuàng)建的消息隊(duì)列。此外vQueueDelete()也可用于刪除信號(hào)量。如果刪除消息隊(duì)列時(shí),有任務(wù)正在等待消息,則不應(yīng)該進(jìn)行刪除操作(官方說的是不允許進(jìn)行刪除操作,但是源碼并沒有禁止刪除的操作,使用的時(shí)候注意一下就行了)。
向消息隊(duì)列發(fā)送消息
任務(wù)或者中斷服務(wù)程序都可以給消息隊(duì)列發(fā)送消息,當(dāng)發(fā)送消息時(shí),如果隊(duì)列未滿或者允許覆蓋入隊(duì),FreeRTOS 會(huì)將消息拷貝到消息隊(duì)列隊(duì)尾,否則,會(huì)根據(jù)用戶指定的阻塞超時(shí)時(shí)間進(jìn)行阻塞,在這段時(shí)間中,如果隊(duì)列一直不允許入隊(duì),該任務(wù)將保持阻塞狀態(tài)以等待隊(duì)列允許入隊(duì)。當(dāng)其它任務(wù)從其等待的隊(duì)列中讀取入了數(shù)據(jù)(隊(duì)列未滿),該任務(wù)將自動(dòng)由阻塞態(tài)轉(zhuǎn)為就緒態(tài)。當(dāng)任務(wù)等待的時(shí)間超過了指定的阻塞時(shí)間,即使隊(duì)列中還不允許入隊(duì),任務(wù)也會(huì)自動(dòng)從阻塞態(tài)轉(zhuǎn)移為就緒態(tài),此時(shí)發(fā)送消息的任務(wù)或者中斷程序會(huì)收到一個(gè)錯(cuò)誤碼errQUEUE_FULL。
發(fā)送緊急消息則是加入到隊(duì)列隊(duì)頭。
xQueueSend()
#define xQueueSend( xQueue, pvItemToQueue, xTicksToWait ) xQueueGenericSend( ( xQueue ), ( pvItemToQueue ), ( xTicksToWait ), queueSEND_TO_BACK )
調(diào)用函數(shù) xQueueGenericSend()
,該 宏 是 為 了 向 后 兼 容 沒 有 包 含 xQueueSendToFront()
和xQueueSendToBack()
這 兩 個(gè) 宏 的 FreeRTOS 版 本 。 xQueueSend()
等 同 于xQueueSendToBack()
。
xQueueSend()用于向隊(duì)列尾部發(fā)送一個(gè)隊(duì)列消息,消息以拷貝的形式入隊(duì),而不是以引用的形式。該函數(shù)絕對(duì)不能在中斷服務(wù)程序里面被調(diào)用,中斷中必須使用帶有中斷保護(hù)功能的 xQueueSendFromISR()
來代替。
xQueueSendToBack()
#define xQueueSendToBack( xQueue, pvItemToQueue, xTicksToWait ) xQueueGenericSend( ( xQueue ), ( pvItemToQueue ), ( xTicksToWait ), queueSEND_TO_BACK )
xQueueSendFromISR()
#define xQueueSendFromISR( xQueue, pvItemToQueue, pxHigherPriorityTaskWoken ) xQueueGenericSendFromISR( ( xQueue ), ( pvItemToQueue ), ( pxHigherPriorityTaskWoken ), queueSEND_TO_BACK )
該宏是 xQueueSend()的中斷保護(hù)版本,用于在中斷服務(wù)程序中向隊(duì)列尾部發(fā)送一個(gè)隊(duì)列消息,等價(jià)于 xQueueSendToBackFromISR()。
xQueueSendToBackFromISR()
#define xQueueSendToBackFromISR( xQueue, pvItemToQueue, pxHigherPriorityTaskWoken ) xQueueGenericSendFromISR( ( xQueue ), ( pvItemToQueue ), ( pxHigherPriorityTaskWoken ), queueSEND_TO_BACK )
xQueueSendToFront()
#define xQueueSendToFront( xQueue, pvItemToQueue, xTicksToWait ) xQueueGenericSend( ( xQueue ), ( pvItemToQueue ), ( xTicksToWait ), queueSEND_TO_FRONT )
宏 展 開 也 是 調(diào) 用 函 數(shù) xQueueGenericSend() 。xQueueSendToFront()用于向隊(duì)列隊(duì)首發(fā)送一個(gè)消息。消息以拷貝的形式入隊(duì),而不是以引用的形式。該函數(shù)絕不能在中斷服務(wù)程序里面被調(diào)用,而是必須使用帶有中斷保護(hù)功能的xQueueSendToFrontFromISR ()來代替。使用方式同 xQueueSend()。
xQueueSendToFrontFromISR()
#define xQueueSendToFrontFromISR( xQueue, pvItemToQueue, pxHigherPriorityTaskWoken ) xQueueGenericSendFromISR( ( xQueue ), ( pvItemToQueue ), ( pxHigherPriorityTaskWoken ), queueSEND_TO_FRONT )
該宏是 xQueueSendToFront()的中斷保護(hù)版本,用于在中斷服務(wù)程序中向消息隊(duì)列隊(duì)首發(fā)送一個(gè)消息。,使用方式與 xQueueSendFromISR()函數(shù)一致。
xQueueGenericSend()
BaseType_t xQueueGenericSend( QueueHandle_t xQueue, //消息隊(duì)列句柄const void * const pvItemToQueue, //指針,指向要發(fā)送的消息TickType_t xTicksToWait, //指定阻塞超時(shí)時(shí)間const BaseType_t xCopyPosition ) //發(fā)送數(shù)據(jù)到消息隊(duì)列的位置
{BaseType_t xEntryTimeSet = pdFALSE, xYieldRequired;TimeOut_t xTimeOut;Queue_t * const pxQueue = ( Queue_t * ) xQueue;//斷言configASSERT( pxQueue );configASSERT( !( ( pvItemToQueue == NULL ) && ( pxQueue->uxItemSize != ( UBaseType_t ) 0U ) ) );configASSERT( !( ( xCopyPosition == queueOVERWRITE ) && ( pxQueue->uxLength != 1 ) ) );#if ( ( INCLUDE_xTaskGetSchedulerState == 1 ) || ( configUSE_TIMERS == 1 ) ){configASSERT( !( ( xTaskGetSchedulerState() == taskSCHEDULER_SUSPENDED ) && ( xTicksToWait != 0 ) ) );}#endiffor( ;; ){taskENTER_CRITICAL();{/* 隊(duì)列未滿*/if( ( pxQueue->uxMessagesWaiting < pxQueue->uxLength ) || ( xCopyPosition == queueOVERWRITE ) ){traceQUEUE_SEND( pxQueue );xYieldRequired = prvCopyDataToQueue( pxQueue, pvItemToQueue, xCopyPosition );/* 如果有任務(wù)在等待獲取此消息隊(duì)列 */if( listLIST_IS_EMPTY( &( pxQueue->xTasksWaitingToReceive ) ) == pdFALSE ){//將任務(wù)從阻塞中恢復(fù)if( xTaskRemoveFromEventList( &( pxQueue->xTasksWaitingToReceive ) ) != pdFALSE ){/* 如果恢復(fù)的任務(wù)優(yōu)先級(jí)比當(dāng)前運(yùn)行任務(wù)優(yōu)先級(jí)還高,那么需要進(jìn)行一次任務(wù)切換 */queueYIELD_IF_USING_PREEMPTION();}else{mtCOVERAGE_TEST_MARKER();}}else if( xYieldRequired != pdFALSE ){/* 如果沒有等待的任務(wù),拷貝成功也需要任務(wù)切換 */queueYIELD_IF_USING_PREEMPTION();}else{mtCOVERAGE_TEST_MARKER();}}#endif /* configUSE_QUEUE_SETS */taskEXIT_CRITICAL();return pdPASS;}// 隊(duì)列已滿else{if( xTicksToWait == ( TickType_t ) 0 ){/* 如果用戶不指定阻塞超時(shí)時(shí)間,退出 */taskEXIT_CRITICAL();traceQUEUE_SEND_FAILED( pxQueue );return errQUEUE_FULL;}else if( xEntryTimeSet == pdFALSE ){/* 初始化阻塞超時(shí)結(jié)構(gòu)體變量,初始化進(jìn)入阻塞的時(shí)間 xTickCount 和溢出次數(shù) xNumOfOverflows */vTaskInternalSetTimeOutState( &xTimeOut );xEntryTimeSet = pdTRUE;}else{/* Entry time was already set. */mtCOVERAGE_TEST_MARKER();}}}taskEXIT_CRITICAL();//因?yàn)榻酉聛淼牟僮飨到y(tǒng)不允許其他任務(wù)訪問隊(duì)列,簡單粗暴掛起調(diào)度器就不會(huì)進(jìn)行任務(wù)切換,但是掛起調(diào)度器并不會(huì)禁止中斷的發(fā)生,所以還需給隊(duì)列上鎖/* 掛起調(diào)度器 */vTaskSuspendAll();/* 隊(duì)列上鎖 */prvLockQueue( pxQueue );/* 檢查超時(shí)時(shí)間是否已經(jīng)過去了. */if( xTaskCheckForTimeOut( &xTimeOut, &xTicksToWait ) == pdFALSE ){/* 如果隊(duì)列還是滿的 */if( prvIsQueueFull( pxQueue ) != pdFALSE ){traceBLOCKING_ON_QUEUE_SEND( pxQueue );// 將當(dāng)前任務(wù)添加到隊(duì)列的等待發(fā)送列表中以及阻塞延時(shí)列表,延時(shí)時(shí)間為用戶指定的超時(shí)時(shí)間 xTicksToWaitvTaskPlaceOnEventList( &( pxQueue->xTasksWaitingToSend ), xTicksToWait );/* 隊(duì)列解鎖 */prvUnlockQueue( pxQueue );/* 恢復(fù)調(diào)度器 */if( xTaskResumeAll() == pdFALSE ){portYIELD_WITHIN_API();}}else{/* 隊(duì)列有空閑消息空間,允許入隊(duì) */prvUnlockQueue( pxQueue );( void ) xTaskResumeAll();}}else{/* 超時(shí)時(shí)間已過,退出*/prvUnlockQueue( pxQueue );( void ) xTaskResumeAll();traceQUEUE_SEND_FAILED( pxQueue );return errQUEUE_FULL;}}
}
- xCopyPosition:具有三個(gè)位置:queueSEND_TO_BACK:發(fā)送到隊(duì)尾;queueSEND_TO_FRONT:發(fā)送到隊(duì)頭;queueOVERWRITE:以覆蓋的方式發(fā)送。
從消息隊(duì)列的入隊(duì)操作我們可以看出:如果阻塞時(shí)間不為 0,則任務(wù)會(huì)因?yàn)榈却腙?duì)而進(jìn)入阻塞,在將任務(wù)設(shè)置為阻塞的過程中,系統(tǒng)不希望有其它任務(wù)和中斷操作這個(gè)隊(duì)列的 xTasksWaitingToReceive 列表和 xTasksWaitingToSend 列表,因?yàn)榭赡芤鹌渌蝿?wù)解除阻塞,這可能會(huì)發(fā)生優(yōu)先級(jí)翻轉(zhuǎn)。比如任務(wù) A 的優(yōu)先級(jí)低于當(dāng)前任務(wù),但是在當(dāng)前任務(wù)進(jìn)入阻塞的過程中,任務(wù) A 卻因?yàn)槠渌蚪獬枞?#xff0c;這顯然是要絕對(duì)禁止的。因此FreeRTOS 使用掛起調(diào)度器禁止其它任務(wù)操作隊(duì)列,因?yàn)閽炱鹫{(diào)度器意味著任務(wù)不能切換并且不準(zhǔn)調(diào)用可能引起任務(wù)切換的 API 函數(shù)。但掛起調(diào)度器并不會(huì)禁止中斷,中斷服務(wù)函數(shù)仍然可以操作隊(duì)列事件列表,可能會(huì)解除任務(wù)阻塞、可能會(huì)進(jìn)行上下文切換,這也是不允許的。于是,解決辦法是不但掛起調(diào)度器,還要給隊(duì)列上鎖,禁止任何中斷來操作隊(duì)列。
xQueueGenericSendFromISR()
這個(gè)函數(shù)跟 xQueueGenericSend() 函數(shù)很像,只不過是 執(zhí)行的上下文環(huán)境是不一樣的,xQueueGenericSendFromISR()函數(shù)只能用于中斷中執(zhí)行,是不帶阻塞機(jī)制的。
BaseType_t xQueueGenericSendFromISR( QueueHandle_t xQueue, //消息隊(duì)列句柄const void * const pvItemToQueue, //指針,指向要發(fā)送的消息BaseType_t * const pxHigherPriorityTaskWoken, //pxHigherPriorityTaskWoken 稱為一個(gè)可選參數(shù),并可以設(shè)置為 NULL,判斷其是否要上下文切換const BaseType_t xCopyPosition ) //消息隊(duì)列位置
{BaseType_t xReturn;UBaseType_t uxSavedInterruptStatus;Queue_t * const pxQueue = ( Queue_t * ) xQueue;configASSERT( pxQueue );configASSERT( !( ( pvItemToQueue == NULL ) && ( pxQueue->uxItemSize != ( UBaseType_t ) 0U ) ) );configASSERT( !( ( xCopyPosition == queueOVERWRITE ) && ( pxQueue->uxLength != 1 ) ) );portASSERT_IF_INTERRUPT_PRIORITY_INVALID();uxSavedInterruptStatus = portSET_INTERRUPT_MASK_FROM_ISR();{//隊(duì)列未滿if( ( pxQueue->uxMessagesWaiting < pxQueue->uxLength ) || ( xCopyPosition == queueOVERWRITE ) ){const int8_t cTxLock = pxQueue->cTxLock;traceQUEUE_SEND_FROM_ISR( pxQueue );/* 完成消息拷貝 */( void ) prvCopyDataToQueue( pxQueue, pvItemToQueue, xCopyPosition );/* 判斷隊(duì)列是否上鎖 */if( cTxLock == queueUNLOCKED ){{//如果有任務(wù)再等待獲取此消息隊(duì)列if( listLIST_IS_EMPTY( &( pxQueue->xTasksWaitingToReceive ) ) == pdFALSE ){/* 將任務(wù)從阻塞中恢復(fù) */if( xTaskRemoveFromEventList( &( pxQueue->xTasksWaitingToReceive ) ) != pdFALSE ){/* 解除阻塞的任務(wù)優(yōu)先級(jí)比當(dāng)前任務(wù)高,記錄上下文切換請(qǐng)求,等返回中斷服務(wù)程序后,就進(jìn)行上下文切換*/if( pxHigherPriorityTaskWoken != NULL ){*pxHigherPriorityTaskWoken = pdTRUE;}else{mtCOVERAGE_TEST_MARKER();}}else{mtCOVERAGE_TEST_MARKER();}}else{mtCOVERAGE_TEST_MARKER();}}#endif /* configUSE_QUEUE_SETS */}else{/*隊(duì)列上鎖,記錄上鎖次數(shù),等到任務(wù)解除隊(duì)列鎖時(shí),使用這個(gè)計(jì)錄數(shù)就可以知道有多少數(shù)據(jù)入隊(duì) */pxQueue->cTxLock = ( int8_t ) ( cTxLock + 1 );}xReturn = pdPASS;}else{// 隊(duì)列是滿的,因?yàn)?API 執(zhí)行的上下文環(huán)境是中斷,所以不能阻塞,直接返回隊(duì)列已滿錯(cuò)誤代碼 errQUEUE_FULLtraceQUEUE_SEND_FROM_ISR_FAILED( pxQueue );xReturn = errQUEUE_FULL;}}portCLEAR_INTERRUPT_MASK_FROM_ISR( uxSavedInterruptStatus );return xReturn;
}
xQueueGenericSendFromISR()函數(shù)沒有阻塞機(jī)制,只能用于中斷中發(fā)送消息,代碼簡單了很多,當(dāng)成功入隊(duì)后,如果有因?yàn)榈却鲫?duì)而阻塞的任務(wù),系統(tǒng)會(huì)將該任務(wù)解除阻塞,要注意的是,解除了任務(wù)并不是會(huì)馬上運(yùn)行的,只是任務(wù)會(huì)被掛到就緒列表中。
在執(zhí)行解除阻塞操作之前,會(huì)判斷隊(duì)列是否上鎖。如果沒有上鎖,則可以解除被阻塞的任務(wù),然后根據(jù)任務(wù)優(yōu)先級(jí)情況來決定是否需要進(jìn)行任務(wù)切換;如果隊(duì)列已經(jīng)上鎖,則不能解除被阻塞的任務(wù),只能是記錄 xTxLock 的值,表示隊(duì)列上鎖期間消息入隊(duì)的個(gè)數(shù),也用來記錄可以解除阻塞任務(wù)的個(gè)數(shù),在隊(duì)列解鎖中會(huì)將任務(wù)解除阻塞。
向消息隊(duì)列讀取消息函數(shù)
當(dāng)任務(wù)試圖讀隊(duì)列中的消息時(shí),可以指定一個(gè)阻塞超時(shí)時(shí)間,當(dāng)且僅當(dāng)消息隊(duì)列中有消息的時(shí)候,任務(wù)才能讀取到消息。
在這段時(shí)間中,如果隊(duì)列為空,該任務(wù)將保持阻塞狀態(tài)以等待隊(duì)列數(shù)據(jù)有效。當(dāng)其它任務(wù)或中斷服務(wù)程序往其等待的隊(duì)列中寫入了數(shù)據(jù),該任務(wù)將自動(dòng)由阻塞態(tài)轉(zhuǎn)為就緒態(tài)。當(dāng)任務(wù)等待的時(shí)間超過了指定的阻塞時(shí)間,即使隊(duì)列中尚無有效數(shù)據(jù),任務(wù)也會(huì)自動(dòng)從阻塞態(tài)轉(zhuǎn)移為就緒態(tài)。
xQueueReceive()
該函數(shù)不可以于中斷中使用。用于從一個(gè)隊(duì)列中接收消息并把消息從隊(duì)列中刪除。
BaseType_t xQueueReceive( QueueHandle_t xQueue, //隊(duì)列句柄。void * const pvBuffer, //指針,指向接收到要保存的數(shù)據(jù)TickType_t xTicksToWait ) //隊(duì)列為空時(shí)阻塞超時(shí)的最大時(shí)間
{BaseType_t xEntryTimeSet = pdFALSE;TimeOut_t xTimeOut;Queue_t * const pxQueue = ( Queue_t * ) xQueue;for( ;; ){taskENTER_CRITICAL();{const UBaseType_t uxMessagesWaiting = pxQueue->uxMessagesWaiting;/* 看看隊(duì)列中有沒有消息 */if( uxMessagesWaiting > ( UBaseType_t ) 0 ){/* 拷貝消息到用戶指定存放區(qū)域 pvBuffer */prvCopyDataFromQueue( pxQueue, pvBuffer );//讀取消息并且消息出隊(duì)traceQUEUE_RECEIVE( pxQueue );//獲取了消息,當(dāng)前消息隊(duì)列的消息個(gè)數(shù)需要減一pxQueue->uxMessagesWaiting = uxMessagesWaiting - ( UBaseType_t ) 1;/* 判斷一下消息隊(duì)列中是否有等待發(fā)送消息的任務(wù)*/if( listLIST_IS_EMPTY( &( pxQueue->xTasksWaitingToSend ) ) == pdFALSE ){/* 將任務(wù)從阻塞中恢復(fù) */if( xTaskRemoveFromEventList( &( pxQueue->xTasksWaitingToSend ) ) != pdFALSE ){/* 如果被恢復(fù)的任務(wù)優(yōu)先級(jí)比當(dāng)前任務(wù)高,會(huì)進(jìn)行一次任務(wù)切換 */queueYIELD_IF_USING_PREEMPTION();}else{mtCOVERAGE_TEST_MARKER();}}else{mtCOVERAGE_TEST_MARKER();}taskEXIT_CRITICAL();return pdPASS;}else{/* 消息隊(duì)列中沒有消息可讀 */if( xTicksToWait == ( TickType_t ) 0 ){/* 不等待,直接返回 */taskEXIT_CRITICAL();traceQUEUE_RECEIVE_FAILED( pxQueue );return errQUEUE_EMPTY;}else if( xEntryTimeSet == pdFALSE ){/* 初始化阻塞超時(shí)結(jié)構(gòu)體變量,初始化進(jìn)入阻塞的時(shí)間 xTickCount 和溢出次數(shù) xNumOfOverflows */vTaskInternalSetTimeOutState( &xTimeOut );xEntryTimeSet = pdTRUE;}else{/* Entry time was already set. */mtCOVERAGE_TEST_MARKER();}}}taskEXIT_CRITICAL();vTaskSuspendAll();prvLockQueue( pxQueue );/* 檢查超時(shí)時(shí)間是否已經(jīng)過去了*/if( xTaskCheckForTimeOut( &xTimeOut, &xTicksToWait ) == pdFALSE ){/* 如果隊(duì)列還是空的 */if( prvIsQueueEmpty( pxQueue ) != pdFALSE ){traceBLOCKING_ON_QUEUE_RECEIVE( pxQueue );// 將當(dāng)前任務(wù)添加到隊(duì)列的等待接收列表中,以及阻塞延時(shí)列表,阻塞時(shí)間為用戶指定的超時(shí)時(shí)間 xTicksToWaitvTaskPlaceOnEventList( &( pxQueue->xTasksWaitingToReceive ), xTicksToWait );prvUnlockQueue( pxQueue );if( xTaskResumeAll() == pdFALSE ){/* 如果有任務(wù)優(yōu)先級(jí)比當(dāng)前任務(wù)高,會(huì)進(jìn)行一次任務(wù)切換 */portYIELD_WITHIN_API();}else{mtCOVERAGE_TEST_MARKER();}}else{/* 如果隊(duì)列有消息了,就再試一次獲取消息*/prvUnlockQueue( pxQueue );( void ) xTaskResumeAll();}}else{/* 超時(shí)時(shí)間已過,退出*/prvUnlockQueue( pxQueue );( void ) xTaskResumeAll();// 如果隊(duì)列還是空的,返回錯(cuò)誤代碼 errQUEUE_EMPTif( prvIsQueueEmpty( pxQueue ) != pdFALSE ){traceQUEUE_RECEIVE_FAILED( pxQueue );return errQUEUE_EMPTY;}else{mtCOVERAGE_TEST_MARKER();}}}
}
xQueuePeek()
如果接收了消息不想刪除隊(duì)列中的內(nèi)容,則調(diào)用這個(gè)函數(shù)。實(shí)現(xiàn)方法與xQueueReceive()一樣。
xQueueReceiveFromISR()、xQueuePeekFromISR()
xQueueReceiveFromISR()是 xQueueReceive ()的中斷版本,用于在中斷服務(wù)程序中接收一個(gè)隊(duì)列消息并把消息從隊(duì)列中刪除;xQueuePeekFromISR()是 xQueuePeek()的中斷版本,用于在中斷中從一個(gè)隊(duì)列中接收消息,但并不會(huì)把消息從隊(duì)列中移除。說白了這兩個(gè)函數(shù)只能用于中斷,是不帶有阻塞機(jī)制的,并且是在中斷中可以安全調(diào)用。
消息隊(duì)列使用注意
- 使用 xQueueSend()、xQueueSendFromISR()、xQueueReceive()等這些函數(shù)之前應(yīng)先創(chuàng)建需消息隊(duì)列,并根據(jù)隊(duì)列句柄進(jìn)行操作。
- 隊(duì)列讀取采用的是先進(jìn)先出(FIFO)模式,會(huì)先讀取先存儲(chǔ)在隊(duì)列中的數(shù)據(jù)。當(dāng)然也 FreeRTOS 也支持后進(jìn)先出(LIFO)模式,那么讀取的時(shí)候就會(huì)讀取到后進(jìn)隊(duì)列的數(shù)據(jù)。
- 在獲取隊(duì)列中的消息時(shí)候,我們必須要定義一個(gè)存儲(chǔ)讀取數(shù)據(jù)的地方,并且該數(shù)據(jù)區(qū)域大小不小于消息大小,否則,很可能引發(fā)地址非法的錯(cuò)誤。
- 無論是發(fā)送或者是接收消息都是以拷貝的方式進(jìn)行,如果消息過于龐大,可以將消息的地址作為消息進(jìn)行發(fā)送、接收。
- 隊(duì)列是具有自己獨(dú)立權(quán)限的內(nèi)核對(duì)象,并不屬于任何任務(wù)。
對(duì)消息隊(duì)列進(jìn)行讀寫實(shí)現(xiàn)
需要注意的是接收的優(yōu)先級(jí)需要比發(fā)送的優(yōu)先級(jí)高。這樣才可以做到收發(fā)順序執(zhí)行。否則會(huì)出現(xiàn)發(fā)送一直在往隊(duì)列中寫數(shù)據(jù),直到隊(duì)列滿了阻塞了,才輪到接收來讀取一次。有一個(gè)地方需要做修改:
#define osMessageQDef(name, queue_sz, type) \
const osMessageQDef_t os_messageQ_def_##name = \
{ (queue_sz), sizeof(type), NULL, NULL }
在RTOS中,queue_sz代表隊(duì)列深度,也就是我們這個(gè)隊(duì)列可以存放多少個(gè)Send進(jìn)來的的數(shù)據(jù),而每次進(jìn)來的數(shù)據(jù)長度在這里定義了sizeof(type),我們把sizeof去掉即可,這樣子的話就不會(huì)限制我們?cè)陉?duì)列中傳輸?shù)臄?shù)據(jù)長度。
實(shí)現(xiàn):
osMessageQDef(TestQueue, 1, 24);TestQueueHandle = osMessageCreate(osMessageQ(TestQueue), NULL);
void ReceiveTask(void const * argument)
{/* USER CODE BEGIN ReceiveTask *//* Infinite loop */BaseType_t xReturn = pdTRUE;char Receive_data[24];int i=0;for(;;){memset(Receive_data , 0 , 24);xReturn = xQueueReceive( TestQueueHandle, &Receive_data,portMAX_DELAY);if(xReturn==pdTRUE){printf("The receive task is: ");printf("%s\r\n",Receive_data);}}
/* USER CODE END ReceiveTask */
}void SendTask(void const * argument)
{/* USER CODE BEGIN SendTask */BaseType_t xReturn = pdPASS;char send_data[] = "Hello!";/* Infinite loop */for(;;){xReturn = xQueueSend( TestQueueHandle, &send_data,0 ); if(xReturn==pdPASS)printf("Send Success\r\n");vTaskDelay(500);}/* USER CODE END SendTask */
}
0x06 信號(hào)量
概念
信號(hào)量(Semaphore)是一種實(shí)現(xiàn)任務(wù)間通信的機(jī)制,可以實(shí)現(xiàn)任務(wù)之間同步或臨界資源的互斥訪問,常用于協(xié)助一組相互競爭的任務(wù)來訪問臨界資源。
抽象的來講,信號(hào)量是一個(gè)非負(fù)整數(shù),所有獲取它的任務(wù)都會(huì)將該整數(shù)減一(獲取它當(dāng)然是為了使用資源),當(dāng)該整數(shù)值為零時(shí),所有試圖獲取它的任務(wù)都將處于阻塞狀態(tài)。通常一個(gè)信號(hào)量的計(jì)數(shù)值用于對(duì)應(yīng)有效的資源數(shù),表示剩下的可被占用的互斥資源數(shù)。其值的含義分兩種情況:
-
0:表示沒有積累下來的釋放信號(hào)量操作,且有可能有在此信號(hào)量上阻塞的任務(wù)。
-
正值:表示有一個(gè)或多個(gè)釋放信號(hào)量操作。
二值信號(hào)量
二值信號(hào)量既可以用于臨界資源訪問也可以用于同步功能。
二值信號(hào)量與互斥信號(hào)量具有如下差別:互斥量有優(yōu)先級(jí)繼承機(jī)制,二值信號(hào)量則沒有這個(gè)機(jī)制。這使得二值信號(hào)量更偏向應(yīng)用于同步功能(任務(wù)與任務(wù)間的同步或任務(wù)和中斷間同步),而互斥量更偏向應(yīng)用于臨界資源的訪問。
可以將二值信號(hào)量看作只有一個(gè)消息的隊(duì)列,因此這個(gè)隊(duì)列只能為空或滿(因此稱為二 值),我們?cè)谶\(yùn)用的時(shí)候只需要知道隊(duì)列中是否有消息即可,而無需關(guān)注消息是什么。二值信號(hào)量是有0和1兩種狀態(tài),信號(hào)值為0的時(shí)候代表資源被獲取,信號(hào)量為1時(shí)代表信號(hào)量被釋放。
計(jì)數(shù)信號(hào)量
在實(shí)際的使用中,我們常將計(jì)數(shù)信號(hào)量用于事件計(jì)數(shù)與資源管理。
每當(dāng)某個(gè)事件發(fā)生時(shí),任務(wù)或者中斷將釋放一個(gè)信號(hào)量(信號(hào)量計(jì)數(shù)值加 1),當(dāng)處理被事件時(shí)(一般在任務(wù)中處理),處理任務(wù)會(huì)取走該信號(hào)量(信號(hào)量計(jì)數(shù)值減 1),信號(hào)量的計(jì)數(shù)值則表示還有多少個(gè)事件沒被處理。
也可以使用計(jì)數(shù)信號(hào)量進(jìn)行資源管理,信號(hào)量的計(jì)數(shù)值表示系統(tǒng)中可用的資源數(shù)目,任務(wù)必須先獲取到信號(hào)量才能獲取資源訪問權(quán),當(dāng)信號(hào)量的計(jì)數(shù)值為零時(shí)表示系統(tǒng)沒有可用的資源,但是要注意,在使用完資源的時(shí)候必須歸還信號(hào)量,否則當(dāng)計(jì)數(shù)值為 0的時(shí)候任務(wù)就無法訪問該資源了。
互斥信號(hào)量
互斥信號(hào)量其實(shí)是特殊的二值信號(hào)量,由于其特有的優(yōu)先級(jí)繼承機(jī)制從而使它更適用于簡單互鎖,也就是保護(hù)臨界資源。
用作互斥時(shí),信號(hào)量創(chuàng)建后可用信號(hào)量個(gè)數(shù)應(yīng)該是滿的,任務(wù)在需要使用臨界資源時(shí),(臨界資源是指任何時(shí)刻只能被一個(gè)任務(wù)訪問的資源),先獲取互斥信號(hào)量,使其變空,這樣其他任務(wù)需要使用臨界資源時(shí)就會(huì)因?yàn)闊o法獲取信號(hào)量而進(jìn)入阻塞,從而保證了臨界資源的安全。
在操作系統(tǒng)中,我們使用信號(hào)量的很多時(shí)候是為了給臨界資源建立一個(gè)標(biāo)志,信號(hào)量表示了該臨界資源被占用情況,有效地保護(hù)了臨界資源。
遞歸信號(hào)量
可以重復(fù)獲取調(diào)用的信號(hào)量,是對(duì)于已經(jīng)獲取遞歸互斥量的任務(wù)可以重復(fù)獲取該遞歸互斥量,該任務(wù)擁有遞歸信號(hào)量的所有權(quán)。
任務(wù)成功獲取幾次遞歸互斥量,就要返還幾次,在此之前遞歸互斥量都處于無效狀態(tài),其他任務(wù)無法獲取,只有持有遞歸信號(hào)量的任務(wù)才能獲取與釋放。
使用信號(hào)量的目的是,不需要CPU不斷地去查詢當(dāng)前狀態(tài)并且不斷去執(zhí)行重復(fù)的狀態(tài),這樣會(huì)占用CPU的資源,提高CPU的執(zhí)行效率。
二值信號(hào)量運(yùn)作機(jī)制
創(chuàng)建信號(hào)量時(shí),系統(tǒng)會(huì)為創(chuàng)建的信號(hào)量對(duì)象分配內(nèi)存,并把可用信號(hào)量初始化為用戶自定義的個(gè)數(shù),二值信號(hào)量的最大可用信號(hào)量個(gè)數(shù)為 1。
二值信號(hào)量獲取,任何任務(wù)都可以從創(chuàng)建的二值信號(hào)量資源中獲取一個(gè)二值信號(hào)量,獲取成功則返回正確,否則任務(wù)會(huì)根據(jù)用戶指定的阻塞超時(shí)時(shí)間來等待其它任務(wù)/中斷釋放信號(hào)量。在等待這段時(shí)間,系統(tǒng)將任務(wù)變成阻塞態(tài),任務(wù)將被掛到該信號(hào)量的阻塞等待列表中。
假如某個(gè)時(shí)間中斷/任務(wù)釋放了信號(hào)量,由于獲取無效信號(hào)量而進(jìn)入阻塞態(tài)的任務(wù)將獲得信號(hào)量并且恢復(fù)為就緒態(tài):
計(jì)數(shù)信號(hào)量運(yùn)作機(jī)制
計(jì)數(shù)信號(hào)量可以用于資源管理,允許多個(gè)任務(wù)獲取信號(hào)量訪問共享資源,但會(huì)限制任務(wù)的最大數(shù)目。訪問的任務(wù)數(shù)達(dá)到可支持的最大數(shù)目時(shí),會(huì)阻塞其他試圖獲取該信號(hào)量的任務(wù),直到有任務(wù)釋放了信號(hào)量。
信號(hào)量控制塊
信號(hào)量 API 函數(shù)實(shí)際上都是宏,它使用現(xiàn)有的隊(duì)列機(jī)制,這些宏定義在 semphr.h
文件中,如果使用信號(hào)量或者互斥量,需要包含 semphr.h
頭文件。
volatile UBaseType_t uxMessagesWaiting;
UBaseType_t uxLength;
UBaseType_t uxItemSize;
如果控制塊結(jié)構(gòu)體是用于消息隊(duì)列:uxMessagesWaiting 用來記錄當(dāng)前消息隊(duì)列的消息個(gè)數(shù);如果控制塊結(jié)構(gòu)體被用于信號(hào)量的時(shí)候,這個(gè)值就表示有效信號(hào)量個(gè)數(shù),有以下兩種情況:
- 如果信號(hào)量是二值信號(hào)量、互斥信號(hào)量,這個(gè)值是 1 則表示有可用信號(hào)量,如果是 0 則表示沒有可用信號(hào)量。
- 如果是計(jì)數(shù)信號(hào)量,這個(gè)值表示可用的信號(hào)量個(gè)數(shù),在創(chuàng)建計(jì)數(shù)信號(hào)量的時(shí)候會(huì)被初始化一個(gè)可用信號(hào)量個(gè)數(shù) uxInitialCount,最大不允許超過創(chuàng)建信號(hào)量的初始值 uxMaxCount。
如果控制塊結(jié)構(gòu)體是用于消息隊(duì)列:uxLength 表示隊(duì)列的長度,也就是能存放多少消息;如果控制塊結(jié)構(gòu)體被用于信號(hào)量的時(shí)候,uxLength 表示最大的信號(hào)量可用個(gè)數(shù),會(huì)有以下兩種情況:
- 如果信號(hào)量是二值信號(hào)量、互斥信號(hào)量,uxLength 最大為 1,因?yàn)樾盘?hào)量要么是有效的,要么是無效的。
- 如果是計(jì)數(shù)信號(hào)量,這個(gè)值表示最大的信號(hào)量個(gè)數(shù),在創(chuàng)建計(jì)數(shù)信號(hào)量的時(shí)候?qū)⒂捎脩糁付ㄟ@個(gè)值 uxMaxCount。
如果控制塊結(jié)構(gòu)體是用于消息隊(duì)列:uxItemSize 表示單個(gè)消息的大小;如果控制塊結(jié)構(gòu)體被用于信號(hào)量的時(shí)候,則無需存儲(chǔ)空間,為 0 即可。
信號(hào)量函數(shù)接口
創(chuàng)建信號(hào)量函數(shù)
xSemaphoreCreateBinary()
xSemaphoreCreateBinary()用于創(chuàng)建一個(gè)二值信號(hào)量,并返回一個(gè)句柄。其實(shí)二值信號(hào)量和互斥量都共同使用一個(gè)類型 SemaphoreHandle_t 的句柄,該句柄的原型是一個(gè) void 型 的 指 針。 使 用 該 函數(shù) 創(chuàng) 建 的 二值信號(hào)量是空的 , 在 使 用函 數(shù)**xSemaphoreTake()**獲取之前必須先調(diào)用函數(shù) **xSemaphoreGive()**釋放后才可以獲取。使用前需要將宏configSUPPORT_DYNAMIC_ALLOCATION 置為1,開啟動(dòng)態(tài)內(nèi)存分配。
#if( configSUPPORT_DYNAMIC_ALLOCATION == 1 )#define xSemaphoreCreateBinary() xQueueGenericCreate( ( UBaseType_t ) 1, //創(chuàng)建的隊(duì)列長度為1,表示信號(hào)量的最大可用個(gè)數(shù)semSEMAPHORE_QUEUE_ITEM_LENGTH, //創(chuàng)建的消息空間(隊(duì)列項(xiàng))大小為0queueQUEUE_TYPE_BINARY_SEMAPHORE ) //創(chuàng)建消息隊(duì)列的類型
#endif
queueQUEUE_TYPE_BINARY_SEMAPHORE可選類型:
#define queueQUEUE_TYPE_BASE ( ( uint8_t ) 0U )
#define queueQUEUE_TYPE_SET ( ( uint8_t ) 0U )
#define queueQUEUE_TYPE_MUTEX ( ( uint8_t ) 1U )
#define queueQUEUE_TYPE_COUNTING_SEMAPHORE ( ( uint8_t ) 2U )
#define queueQUEUE_TYPE_BINARY_SEMAPHORE ( ( uint8_t ) 3U )
#define queueQUEUE_TYPE_RECURSIVE_MUTEX ( ( uint8_t ) 4U )
創(chuàng)建一個(gè)沒有消息存儲(chǔ)空間的隊(duì)列,信號(hào)量用什么表示?其實(shí)二值信號(hào)量的釋放和獲取都是通過操作隊(duì)列結(jié)控制塊構(gòu)體成員 uxMessageWaiting 來實(shí)現(xiàn)的,它表示信號(hào)量中當(dāng)前可用的信號(hào)量個(gè)數(shù)。
在信號(hào)量創(chuàng)建之后,變量 uxMessageWaiting 的值為 0,這說明當(dāng)前信號(hào)量處于無效狀態(tài),此時(shí)的信號(hào)量是無法被獲取的,在獲取信號(hào)之前,應(yīng)先釋放一個(gè)信號(hào)量。
xSemaphoreCreateCounting()
用于創(chuàng)建一個(gè)計(jì)數(shù)信號(hào)量,使用前需要將宏configSUPPORT_DYNAMIC_ALLOCATION
定義為1,其 實(shí) 計(jì) 數(shù) 信 號(hào) 量 跟 二 值 信 號(hào) 量 的 創(chuàng) 建 過 程 都 差 不 多 , 其 實(shí) 也 是 間 接 調(diào) 用xQueueGenericCreate()函數(shù)進(jìn)行創(chuàng)建:
#if( configSUPPORT_DYNAMIC_ALLOCATION == 1 )#define xSemaphoreCreateCounting( uxMaxCount, uxInitialCount ) xQueueCreateCountingSemaphore( ( uxMaxCount ), ( uxInitialCount ) )
#endif
xQueueCreateCountingSemaphore()
/*-----------------------------------------------------------*/#if( ( configUSE_COUNTING_SEMAPHORES == 1 ) && ( configSUPPORT_DYNAMIC_ALLOCATION == 1 ) )QueueHandle_t xQueueCreateCountingSemaphore( const UBaseType_t uxMaxCount, const UBaseType_t uxInitialCount ){QueueHandle_t xHandle;configASSERT( uxMaxCount != 0 );configASSERT( uxInitialCount <= uxMaxCount );//實(shí)則也是調(diào)用函數(shù)xQueueGenericCreatexHandle = xQueueGenericCreate( uxMaxCount, //信號(hào)量最大個(gè)數(shù)queueSEMAPHORE_QUEUE_ITEM_LENGTH, //每個(gè)消息空間的大小的宏 0queueQUEUE_TYPE_COUNTING_SEMAPHORE ); //類型if( xHandle != NULL ){( ( Queue_t * ) xHandle )->uxMessagesWaiting = uxInitialCount; //初始為用戶指定的可用信號(hào)量個(gè)數(shù)traceCREATE_COUNTING_SEMAPHORE();}else{traceCREATE_COUNTING_SEMAPHORE_FAILED();}return xHandle;}#endif /* ( ( configUSE_COUNTING_SEMAPHORES == 1 ) && ( configSUPPORT_DYNAMIC_ALLOCATION == 1 ) ) */
/*-----------------------------------------------------------*/
刪除信號(hào)量函數(shù)
vSemaphoreDelete()
用于信號(hào)量刪除,包括二值信號(hào)量,計(jì)數(shù)信號(hào)量,互斥量和遞歸互斥量。如果有任務(wù)阻塞在該信號(hào)量上,那么不要?jiǎng)h除該信號(hào)量。
#define vSemaphoreDelete( xSemaphore ) vQueueDelete( ( QueueHandle_t ) ( xSemaphore ) )
需要傳入信號(hào)量句柄。刪除信號(hào)量過程其實(shí)就是刪除消息隊(duì)列過程,因?yàn)樾盘?hào)量其實(shí)就是消息隊(duì)列,只不過是無法存儲(chǔ)消息的隊(duì)列而已。
信號(hào)量釋放函數(shù)
與消息隊(duì)列的操作一樣,信號(hào)量的釋放可以在任務(wù)、中斷中使用,所以需要有不一樣的 API 函數(shù)在不一樣的上下文環(huán)境中調(diào)用。
使得函數(shù)信號(hào)量變得有效:
- 在創(chuàng)建的時(shí)候進(jìn)行初始化,將它可用的信號(hào)量個(gè)數(shù)設(shè)置為一個(gè)初始值。
- 在使用時(shí),需要釋放信號(hào)量,并且注意釋放的次數(shù)是否符合信號(hào)量的區(qū)間。
xSemaphoreGive()
xSemaphoreGive()是一個(gè)用于釋放信號(hào)量的宏,真正的實(shí)現(xiàn)過程是調(diào)用消息隊(duì)列通用發(fā)送函數(shù),釋放的信號(hào)量對(duì)象必須是已經(jīng)被創(chuàng)建的,可以用于二值信號(hào)量、計(jì)數(shù)信號(hào)量、互斥量的釋放,但不能釋放由函數(shù)xSemaphoreCreateRecursiveMutex()創(chuàng)建的遞歸互斥量。
#define xSemaphoreGive( xSemaphore )
xQueueGenericSend( ( QueueHandle_t ) ( xSemaphore ), NULL, semGIVE_BLOCK_TIME, queueSEND_TO_BACK )
從該宏定義可以看出釋放信號(hào)量實(shí)際上是一次入隊(duì)操作,并且是不允許入隊(duì)阻塞,因?yàn)樽枞麜r(shí)間為 semGIVE_BLOCK_TIME,該宏的值為 0。通過消息隊(duì)列入隊(duì)過程分析,我們可以將釋放一個(gè)信號(hào)量的過程簡化:如果信號(hào)量未滿,控制塊結(jié)構(gòu)體成員 uxMessageWaiting 就會(huì)加 1,然后判斷是否有阻塞的任務(wù),如果有的話就會(huì)恢復(fù)阻塞的任務(wù),然后返回成功信息(pdPASS);如果信號(hào)量已滿,則返回錯(cuò)誤代碼(err_QUEUE_FULL)。
xSemaphoreGiveFromISR()
用于釋放一個(gè)信號(hào)量,帶中斷保護(hù)。被釋放的信號(hào)量可以是二進(jìn)制信號(hào)量和計(jì)數(shù)信號(hào)量。和普通版本的釋放信號(hào)量 API 函數(shù)有些許不同,它不能釋放互斥量,這是因?yàn)榛コ饬坎豢梢栽谥袛嘀惺褂?#xff0c;互斥量的優(yōu)先級(jí)繼承機(jī)制只能在任務(wù)中起作用,而在中斷中毫無意義。
#define xSemaphoreGiveFromISR( xSemaphore, pxHigherPriorityTaskWoken ) xQueueGiveFromISR( ( QueueHandle_t ) ( xSemaphore ), ( pxHigherPriorityTaskWoken ) )
如果可用信號(hào)量未滿,控制塊結(jié)構(gòu)體成員 uxMessageWaiting 就會(huì)加 1,然后判斷是否有阻塞的任務(wù),如果有的話就會(huì)恢復(fù)阻塞的任務(wù),然后返回成功信息(pdPASS),如果恢復(fù)的任務(wù)優(yōu)先級(jí)比當(dāng)前任務(wù)優(yōu)先級(jí)高,那么在退出中斷要進(jìn)行任務(wù)切換一次;如果信號(hào)量滿,則返回錯(cuò)誤代碼(err_QUEUE_FULL),表示信號(hào)量滿。
一個(gè)或者多個(gè)任務(wù)有可能阻塞在同一個(gè)信號(hào)量上,調(diào)用函數(shù) xSemaphoreGiveFromISR()可能會(huì)喚醒阻塞在該信號(hào)量上的任務(wù),如果被喚醒的任務(wù)的優(yōu)先級(jí)大于當(dāng)前任務(wù)的優(yōu)先級(jí),那么形參 pxHigherPriorityTaskWoken 就會(huì)被設(shè)置為 pdTRUE,然后在中斷退出前執(zhí)行一次上下文切換portYIELD_FROM_ISR
。
信號(hào)量獲取函數(shù)
與消息隊(duì)列的操作一樣,信號(hào)量的獲取可以在任務(wù)、中斷(中斷中使用并不常見)中使用,所以需要有不一樣的 API 函數(shù)在不一樣的上下文環(huán)境中調(diào)用。如果某個(gè)信號(hào)量中當(dāng)前擁有 1 個(gè)可用的信號(hào)量的話,被獲取一次就變得無效了,那么此時(shí)另外一個(gè)任務(wù)獲取該信號(hào)量的時(shí)候,就會(huì)無法獲取成功,該任務(wù)便會(huì)進(jìn)入阻塞態(tài),阻塞時(shí)間由用戶指定。
xSemaphoreTake()
#define xSemaphoreTake( xSemaphore, xBlockTime ) xQueueSemaphoreTake( ( xSemaphore ), //信號(hào)量句柄( xBlockTime ) ) //等待信號(hào)量可用的最大超時(shí)時(shí)間,單位為 tick(即系統(tǒng)節(jié)拍周期)。如果宏 INCLUDE_vTaskSuspend 定義為 1 且形參 xTicksToWait 設(shè)置為portMAX_DELAY ,則任務(wù)將一直阻塞在該信號(hào)量上(即沒有超時(shí)時(shí)間)。
xSemaphoreTake()函數(shù)用于獲取信號(hào)量,不帶中斷保護(hù)。獲取的信號(hào)量對(duì)象可以是二值信號(hào)量、計(jì)數(shù)信號(hào)量和互斥量,但是遞歸互斥量并不能使用這個(gè) API 函數(shù)獲取。其實(shí)獲取信號(hào)量是一個(gè)宏,真正調(diào)用的函數(shù)是 xQueueSemaphoreTake()。
從該宏定義可以看出釋放信號(hào)量實(shí)際上是一次消息出隊(duì)操作,阻塞時(shí)間由用戶指定xBlockTime,當(dāng)有任務(wù)試圖獲取信號(hào)量的時(shí)候,當(dāng)且僅當(dāng)信號(hào)量有效的時(shí)候,任務(wù)才能讀獲取到信號(hào)量。如果信號(hào)量無效,在用戶指定的阻塞超時(shí)時(shí)間中,該任務(wù)將保持阻塞狀態(tài)以等待信號(hào)量有效。當(dāng)其它任務(wù)或中斷釋放了有效的信號(hào)量,該任務(wù)將自動(dòng)由阻塞態(tài)轉(zhuǎn)移為就緒態(tài)。當(dāng)任務(wù)等待的時(shí)間超過了指定的阻塞時(shí)間,即使信號(hào)量中還是沒有可用信號(hào)量,任務(wù)也會(huì)自動(dòng)從阻塞態(tài)轉(zhuǎn)移為就緒態(tài)。
如果有可用信號(hào)量,控制塊結(jié)構(gòu)體成員 uxMessageWaiting 就會(huì)減 1,然后返回獲取成功信息(pdPASS);如果信號(hào)量無效并且阻塞時(shí)間為 0,則返回錯(cuò)誤代碼(errQUEUE_EMPTY);如果信號(hào)量無效并且用戶指定了阻塞時(shí)間,則任務(wù)會(huì)因?yàn)榈却盘?hào)量而進(jìn)入阻塞狀態(tài),任務(wù)會(huì)被掛接到延時(shí)列表中。
xSemaphoreTakeFromISR()
#define xSemaphoreTakeFromISR( xSemaphore, pxHigherPriorityTaskWoken ) xQueueReceiveFromISR( ( QueueHandle_t ) ( xSemaphore ), NULL, ( pxHigherPriorityTaskWoken ) )
xSemaphoreTakeFromISR()是函數(shù) xSemaphoreTake()的中斷版本,用于獲取信號(hào)量,是一個(gè)不帶阻塞機(jī)制獲取信號(hào)量的函數(shù),獲取對(duì)象必須由是已經(jīng)創(chuàng)建的信號(hào)量,信號(hào)量類型可以是二值信號(hào)量和計(jì)數(shù)信號(hào)量,它與 xSemaphoreTake()函數(shù)不同,它不能用于獲取互斥量,因?yàn)榛コ饬坎豢梢栽谥袛嘀惺褂?#xff0c;并且互斥量特有的優(yōu)先級(jí)繼承機(jī)制只能在任務(wù)中起作用,而在中斷中毫無意義。
信號(hào)量實(shí)現(xiàn)
實(shí)現(xiàn)一個(gè)釋放信號(hào)量,另一個(gè)線程獲取信號(hào)量后再接著執(zhí)行。
創(chuàng)建一個(gè)二值信號(hào)量:
osSemaphoreDef(BinarySem);
BinarySem_Handle = osSemaphoreCreate(osSemaphore(BinarySem),1);/* definition and creation of Receive */
osThreadDef(Receive, ReceiveTask, osPriorityLow, 0, 128);
ReceiveHandle = osThreadCreate(osThread(Receive), NULL);/* definition and creation of Send */
osThreadDef(Send, SendTask, osPriorityIdle, 0, 128);
SendHandle = osThreadCreate(osThread(Send), NULL);
osSemaphoreCreate第二個(gè)參數(shù)代表的是信號(hào)量的類型,1為二值信號(hào)量,0則可能是計(jì)數(shù)信號(hào)量。任務(wù)函數(shù)則如下:
void ReceiveTask(void const * argument)
{/* USER CODE BEGIN ReceiveTask *//* Infinite loop */BaseType_t xReturn = pdTRUE;for(;;){vTaskDelay(5000);osSemaphoreRelease(BinarySem_Handle);int t1 = osKernelSysTick();printf("the release is %d\r\n",t1);}/* USER CODE END ReceiveTask */
}void SendTask(void const * argument)
{/* USER CODE BEGIN SendTask */BaseType_t xReturn = pdPASS;for(;;){osSemaphoreWait(BinarySem_Handle,portMAX_DELAY);int t1 = osKernelSysTick();printf("the wait is %d\r\n",t1);}/* USER CODE END SendTask */
}
使用計(jì)數(shù)信號(hào)量時(shí),需要將宏configUSE_COUNTING_SEMAPHORES置位。
osSemaphoreDef(CountSem);
CountSem_Handle = osSemaphoreCreate(osSemaphore(CountSem),10);
之后將osSemaphoreRelease(BinarySem_Handle);去掉,即可看到send任務(wù)只運(yùn)行了十次。