freertos移植教程
準備
在移植之前,我們首先要獲取到FreeRTOS的官方的源碼包。這里我們提供兩個下載鏈接:
一個是官網(wǎng):http://www.freertos.org/
另外一個是代碼托管網(wǎng)站:https://sourceforge.net/projects/freertos/files/FreeRTOS/
這里我們演示如何在代碼托管網(wǎng)站里面下載。打開網(wǎng)站鏈接之后,我們選擇FreeRTOS的最新版本V9.0.0(2016年),盡管現(xiàn)在FreeRTOS的版本已經(jīng)更新到V10.0.1了,但是我們還是選擇V9.0.0,因為內(nèi)核很穩(wěn)定,并且網(wǎng)上資料很多,因為V10.0.0版本之后是亞馬遜收購了FreeRTOS之后才出來的版本,主要添加了一些云端組件,我們本書所講的FreeRTOS是實時內(nèi)核,采用V9.0.0版本足以。
簡單介紹FreeRTOS
FreeRTOS包含Demo例程和內(nèi)核源碼(比較重要,我們就需要提取該目錄下的大部分文件)。
Source文件夾里面包含的是FreeRTOS內(nèi)核的源代碼,我們移植FreeRTOS的時候就需要這部分源代碼;
Demo 文件夾里面包含了FreeRTOS官方為各個單片機移植好的工程代碼,F(xiàn)reeRTOS為了推廣自己,會給各種半導(dǎo)體廠商的評估板寫好完整的工程程序,這些程序就放在Demo這個目錄下,這部分Demo非常有參考價值。
Source文件夾
這里我們再重點分析下FreeRTOS/ Source文件夾下的文件,①和③包含的是FreeRTOS的通用的頭文件和C文件,這兩部分的文件試用于各種編譯器和處理器,是通用的。需要移植的頭文件和C文件放在②portblle這個文件夾。
portblle文件夾,是與編譯器相關(guān)的文件夾,在不同的編譯器中使用不同的支持文件。①中的KEIL就是我們就是我們使用的編譯器,其實KEIL里面的內(nèi)容跟RVDS里面的內(nèi)容一樣,所以我們只需要③RVDS文件夾里面的內(nèi)容即可,里面包含了各種處理器相關(guān)的文件夾,從文件夾的名字我們就非常熟悉了,我們學(xué)習的STM32有M0、M3、M4等各種系列,F(xiàn)reeRTOS是一個軟件,單片機是一個硬件,F(xiàn)reeRTOS要想運行在一個單片機上面,它們就必須關(guān)聯(lián)在一起。MemMang文件夾下存放的是跟內(nèi)存管理相關(guān)的源文件。
移植過程
提取源碼
1.首先在我們的STM32裸機工程模板根目錄下新建一個文件夾,命名為“FreeRTOS”,并且在FreeRTOS文件夾下新建兩個空文件夾,分別命名為“src”與“port”,src文件夾用于保存FreeRTOS中的核心源文件,也就是我們常說的‘.c文件’,port文件夾用于保存內(nèi)存管理以及處理器架構(gòu)相關(guān)代碼,這些代碼FreeRTOS官方已經(jīng)提供給我們的,直接使用即可,在前面已經(jīng)說了,F(xiàn)reeRTOS是軟件,我們的開發(fā)版是硬件,軟硬件必須有橋梁來連接,這些與處理器架構(gòu)相關(guān)的代碼,可以稱之為RTOS硬件接口層,它們位于FreeRTOS/Source/Portable文件夾下。
2.打開FreeRTOS V9.0.0源碼,在“FreeRTOSv9.0.0\FreeRTOS\Source”目錄下找到所有的‘.c文件’,將它們拷貝到我們新建的src文件夾中,
3.打開FreeRTOS V9.0.0源碼,在“FreeRTOSv9.0.0\FreeRTOS\Source\portable”目錄下找到“MemMang”文件夾與“RVDS”文件夾,將它們拷貝到我們新建的port文件夾中
4.打開FreeRTOS V9.0.0源碼,在“FreeRTOSv9.0.0\ FreeRTOS\Source”目錄下找到“include”文件夾,它是我們需要用到FreeRTOS的一些頭文件,將它直接拷貝到我們新建的FreeRTOS文件夾中,完成這一步之后就可以看到我們新建的FreeRTOS文件夾已經(jīng)有3個文件夾,這3個文件夾就包含F(xiàn)reeRTOS的核心文件,至此,F(xiàn)reeRTOS的源碼就提取完成。
添加到工程
添加FreeRTOSConfig.h文件
FreeRTOSConfig.h文件是FreeRTOS的工程配置文件,因為FreeRTOS是可以裁剪的實時操作內(nèi)核,應(yīng)用于不同的處理器平臺,用戶可以通過修改這個FreeRTOS內(nèi)核的配置頭文件來裁剪FreeRTOS的功能,所以我們把它拷貝一份放在user這個文件夾下面。
打開FreeRTOSv9.0.0源碼,在“FreeRTOSv9.0.0\FreeRTOS\Demo”文件夾下面找到“CORTEX_STM32F103_Keil”這個文件夾,雙擊打開,在其根目錄下找到這個“FreeRTOSConfig.h”文件,然后拷貝到我們工程的user文件夾下即可,等下我們需要對這個文件進行修改。
創(chuàng)建工程分組
接下來我們在mdk里面新建FreeRTOS/src和FreeRTOS/port兩個組文件夾,其中FreeRTOS/src用于存放src文件夾的內(nèi)容,F(xiàn)reeRTOS/port用于存放port\MemMang文件夾 與port\RVDS\ARM_CM3文件夾的內(nèi)容。
然后我們將工程文件中FreeRTOS的內(nèi)容添加到工程中去,按照已經(jīng)新建的分組添加我們的FreeRTOS工程源碼。
在FreeRTOS/port分組中添加MemMang文件夾中的文件只需選擇其中一個即可,我們選擇“heap_4.c”,這是FreeRTOS的一個內(nèi)存管理源碼文件。
添加完成后:
** 添加頭文件路徑**
FreeRTOS的源碼已經(jīng)添加到開發(fā)環(huán)境的組文件夾下面,編譯的時候需要為這些源文件指定頭文件的路徑,不然編譯會報錯。FreeRTOS的源碼里面只有FreeRTOS\include和FreeRTOS\port\RVDS\ARM_CM3這兩個文件夾下面有頭文件,只需要將這兩個頭文件的路徑在開發(fā)環(huán)境里面指定即可。同時我們還將FreeRTOSConfig.h這個頭文件拷貝到了工程根目錄下的user文件夾下,所以user的路徑也要加到開發(fā)環(huán)境里面。
修改FreeRTOSConfig.h
FreeRTOSConfig.h是直接從demo文件夾下面拷貝過來的,該頭文件對裁剪整個FreeRTOS所需的功能的宏均做了定義,有些宏定義被使能,有些宏定義被失能,一開始我們只需要配置最簡單的功能即可。要想隨心所欲的配置FreeRTOS的功能,我們必須對這些宏定義的功能有所掌握,下面我們先簡單的介紹下這些宏定義的含義,然后再對這些宏定義進行修改。
#ifndef FREERTOS_CONFIG_H
#define FREERTOS_CONFIG_H
#include “stm32f10x.h”
#include “bsp_usart.h”
//針對不同的編譯器調(diào)用不同的stdint.h文件
#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ǔ)配置配置選項
*********************************************************************/
/* 置1:RTOS使用搶占式調(diào)度器;置0:RTOS使用協(xié)作式調(diào)度器(時間片)
*
* 注:在多任務(wù)管理機制上,操作系統(tǒng)可以分為搶占式和協(xié)作式兩種。
* 協(xié)作式操作系統(tǒng)是任務(wù)主動釋放CPU后,切換到下一個任務(wù)。
* 任務(wù)切換的時機完全取決于正在運行的任務(wù)。
*/
#define configUSE_PREEMPTION 1
//1使能時間片調(diào)度(默認式使能的)
#define configUSE_TIME_SLICING 1
/* 某些運行FreeRTOS的硬件有兩種方法選擇下一個要執(zhí)行的任務(wù):
* 通用方法和特定于硬件的方法(以下簡稱“特殊方法”)。
*
* 通用方法:
* 1.configUSE_PORT_OPTIMISED_TASK_SELECTION 為 0 或者硬件不支持這種特殊方法。
* 2.可以用于所有FreeRTOS支持的硬件
* 3.完全用C實現(xiàn),效率略低于特殊方法。
* 4.不強制要求限制最大可用優(yōu)先級數(shù)目
* 特殊方法:
* 1.必須將configUSE_PORT_OPTIMISED_TASK_SELECTION設(shè)置為1。
* 2.依賴一個或多個特定架構(gòu)的匯編指令(一般是類似計算前導(dǎo)零[CLZ]指令)。
* 3.比通用方法更高效
* 4.一般強制限定最大可用優(yōu)先級數(shù)目為32
* 一般是硬件計算前導(dǎo)零指令,如果所使用的,MCU沒有這些硬件指令的話此宏應(yīng)該設(shè)置為0!
*/
#define configUSE_PORT_OPTIMISED_TASK_SELECTION 1
/* 置1:使能低功耗tickless模式;置0:保持系統(tǒng)節(jié)拍(tick)中斷一直運行
* 假設(shè)開啟低功耗的話可能會導(dǎo)致下載出現(xiàn)問題,因為程序在睡眠中,可用以下辦法解決
*
* 下載方法:
* 1.將開發(fā)版正常連接好
* 2.按住復(fù)位按鍵,點擊下載瞬間松開復(fù)位按鍵
*
* 1.通過跳線帽將 BOOT 0 接高電平(3.3V)
* 2.重新上電,下載
*
* 1.使用FlyMcu擦除一下芯片,然后進行下載
* STMISP -》 清除芯片(z)
*/
#define configUSE_TICKLESS_IDLE 0
/*
* 寫入實際的CPU內(nèi)核時鐘頻率,也就是CPU指令執(zhí)行頻率,通常稱為Fclk
* Fclk為供給CPU內(nèi)核的時鐘信號,我們所說的cpu主頻為 XX MHz,
* 就是指的這個時鐘信號,相應(yīng)的,1/Fclk即為cpu時鐘周期;
*/
#define configCPU_CLOCK_HZ (SystemCoreClock)
//RTOS系統(tǒng)節(jié)拍中斷的頻率。即一秒中斷的次數(shù),每次中斷RTOS都會進行任務(wù)調(diào)度
#define configTICK_RATE_HZ (( TickType_t )1000)
//可使用的最大優(yōu)先級
#define configMAX_PRIORITIES (32)
//空閑任務(wù)使用的堆棧大小
#define configMINIMAL_STACK_SIZE ((unsigned short)128)
//任務(wù)名字字符串長度
#define configMAX_TASK_NAME_LEN (16)
//系統(tǒng)節(jié)拍計數(shù)器變量數(shù)據(jù)類型,1表示為16位無符號整形,0表示為32位無符號整形
#define configUSE_16_BIT_TICKS 0
//空閑任務(wù)放棄CPU使用權(quán)給其他同優(yōu)先級的用戶任務(wù)
#define configIDLE_SHOULD_YIELD 1
//啟用隊列
#define configUSE_QUEUE_SETS 1
//開啟任務(wù)通知功能,默認開啟
#define configUSE_TASK_NOTIFICATIONS 1
//使用互斥信號量
#define configUSE_MUTEXES 1
//使用遞歸互斥信號量
#define configUSE_RECURSIVE_MUTEXES 1
//為1時使用計數(shù)信號量
#define configUSE_COUNTING_SEMAPHORES 1
/* 設(shè)置可以注冊的信號量和消息隊列個數(shù) */
#define configQUEUE_REGISTRY_SIZE 10
#define configUSE_APPLICATION_TASK_TAG 0
/*****************************************************************
FreeRTOS與內(nèi)存申請有關(guān)配置選項
*****************************************************************/
//支持動態(tài)內(nèi)存申請
#define configSUPPORT_DYNAMIC_ALLOCATION 1
//支持靜態(tài)內(nèi)存
#define configSUPPORT_STATIC_ALLOCATION 0
//系統(tǒng)所有總的堆大小
#define configTOTAL_HEAP_SIZE ((size_t)(36*1024))
/***************************************************************
FreeRTOS與鉤子函數(shù)有關(guān)的配置選項
**************************************************************/
/* 置1:使用空閑鉤子(Idle Hook類似于回調(diào)函數(shù));置0:忽略空閑鉤子
*
* 空閑任務(wù)鉤子是一個函數(shù),這個函數(shù)由用戶來實現(xiàn),
* FreeRTOS規(guī)定了函數(shù)的名字和參數(shù):void vApplicationIdleHook(void ),
* 這個函數(shù)在每個空閑任務(wù)周期都會被調(diào)用
* 對于已經(jīng)刪除的RTOS任務(wù),空閑任務(wù)可以釋放分配給它們的堆棧內(nèi)存。
* 因此必須保證空閑任務(wù)可以被CPU執(zhí)行
* 使用空閑鉤子函數(shù)設(shè)置CPU進入省電模式是很常見的
* 不可以調(diào)用會引起空閑任務(wù)阻塞的API函數(shù)
*/
#define configUSE_IDLE_HOOK 0
/* 置1:使用時間片鉤子(Tick Hook);置0:忽略時間片鉤子
*
*
* 時間片鉤子是一個函數(shù),這個函數(shù)由用戶來實現(xiàn),
* FreeRTOS規(guī)定了函數(shù)的名字和參數(shù):void vApplicationTickHook(void )
* 時間片中斷可以周期性的調(diào)用
* 函數(shù)必須非常短小,不能大量使用堆棧,
* 不能調(diào)用以”FromISR“ 或 ”FROM_ISR”結(jié)尾的API函數(shù)
*/
/*xTaskIncrementTick函數(shù)是在xPortSysTickHandler中斷函數(shù)中被調(diào)用的。因此,vApplicationTickHook()函數(shù)執(zhí)行的時間必須很短才行*/
#define configUSE_TICK_HOOK 0
//使用內(nèi)存申請失敗鉤子函數(shù)
#define configUSE_MALLOC_FAILED_HOOK 0
/*
* 大于0時啟用堆棧溢出檢測功能,如果使用此功能
* 用戶必須提供一個棧溢出鉤子函數(shù),如果使用的話
* 此值可以為1或者2,因為有兩種棧溢出檢測方法 */
#define configCHECK_FOR_STACK_OVERFLOW 0
/********************************************************************
FreeRTOS與運行時間和任務(wù)狀態(tài)收集有關(guān)的配置選項
**********************************************************************/
//啟用運行時間統(tǒng)計功能
#define configGENERATE_RUN_TIME_STATS 0
//啟用可視化跟蹤調(diào)試
#define configUSE_TRACE_FACILITY 0
/* 與宏configUSE_TRACE_FACILITY同時為1時會編譯下面3個函數(shù)
* prvWriteNameToBuffer()
* vTaskList(),
* vTaskGetRunTimeStats()
*/
#define configUSE_STATS_FORMATTING_FUNCTIONS 1
/********************************************************************
FreeRTOS與協(xié)程有關(guān)的配置選項
*********************************************************************/
//啟用協(xié)程,啟用協(xié)程以后必須添加文件croutine.c
#define configUSE_CO_ROUTINES 0
//協(xié)程的有效優(yōu)先級數(shù)目
#define configMAX_CO_ROUTINE_PRIORITIES ( 2 )
/***********************************************************************
FreeRTOS與軟件定時器有關(guān)的配置選項
**********************************************************************/
//啟用軟件定時器
#define configUSE_TIMERS 1
//軟件定時器優(yōu)先級
#define configTIMER_TASK_PRIORITY (configMAX_PRIORITIES-1)
//軟件定時器隊列長度
#define configTIMER_QUEUE_LENGTH 10
//軟件定時器任務(wù)堆棧大小
#define configTIMER_TASK_STACK_DEPTH (configMINIMAL_STACK_SIZE*2)
/************************************************************
FreeRTOS可選函數(shù)配置選項
************************************************************/
#define INCLUDE_xTaskGetSchedulerState 1
#define INCLUDE_vTaskPrioritySet 1
#define INCLUDE_uxTaskPriorityGet 1
#define INCLUDE_vTaskDelete 1
#define INCLUDE_vTaskCleanUpResources 1
#define INCLUDE_vTaskSuspend 1
#define INCLUDE_vTaskDelayUntil 1
#define INCLUDE_vTaskDelay 1
#define INCLUDE_eTaskGetState 1
#define INCLUDE_xTimerPendFunctionCall 1
//#define INCLUDE_xTaskGetCurrentTaskHandle 1
//#define INCLUDE_uxTaskGetStackHighWaterMark 0
//#define INCLUDE_xTaskGetIdleTaskHandle 0
/******************************************************************
FreeRTOS與中斷有關(guān)的配置選項
******************************************************************/
#ifdef __NVIC_PRIO_BITS
#define configPRIO_BITS __NVIC_PRIO_BITS
#else
#define configPRIO_BITS 4
#endif
//中斷最低優(yōu)先級
#define configLIBRARY_LOWEST_INTERRUPT_PRIORITY 15
//系統(tǒng)可管理的最高中斷優(yōu)先級
#define configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY 5
#define configKERNEL_INTERRUPT_PRIORITY ( configLIBRARY_LOWEST_INTERRUPT_PRIORITY 《《 (8 - configPRIO_BITS) ) /* 240 */
#define configMAX_SYSCALL_INTERRUPT_PRIORITY ( configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY 《《 (8 - configPRIO_BITS) )
/****************************************************************
FreeRTOS與中斷服務(wù)函數(shù)有關(guān)的配置選項
****************************************************************/
#define xPortPendSVHandler PendSV_Handler
#define vPortSVCHandler SVC_Handler
/* 以下為使用Percepio Tracealyzer需要的東西,不需要時將 configUSE_TRACE_FACILITY 定義為 0 */
#if ( configUSE_TRACE_FACILITY == 1 )
#include “trcRecorder.h”
#define INCLUDE_xTaskGetCurrentTaskHandle 1 // 啟用一個可選函數(shù)(該函數(shù)被 Trace源碼使用,默認該值為0 表示不用)
#endif
#endif /* FREERTOS_CONFIG_H */
修改stm32f10x_it.c
SysTick中斷服務(wù)函數(shù)是一個非常重要的函數(shù),F(xiàn)reeRTOS所有跟時間相關(guān)的事情都在里面處理,SysTick就是FreeRTOS的一個心跳時鐘,驅(qū)動著FreeRTOS的運行,就像人的心跳一樣,假如沒有心跳,我們就相當于“死了”,同樣的,F(xiàn)reeRTOS沒有了心跳,那么它就會卡死在某個地方,不能進行任務(wù)調(diào)度,不能運行任何的東西,因此我們需要實現(xiàn)一個FreeRTOS的心跳時鐘,F(xiàn)reeRTOS幫我們實現(xiàn)了SysTick的啟動的配置:在port.c文件中已經(jīng)實現(xiàn)vPortSetupTimerInterrupt()函數(shù),并且FreeRTOS通用的SysTick中斷服務(wù)函數(shù)也實現(xiàn)了:在port.c文件中已經(jīng)實現(xiàn)xPortSysTickHandler()函數(shù),所以移植的時候只需要我們在stm32f10x_it.c文件中實現(xiàn)我們對應(yīng)(STM32)平臺上的SysTick_Handler()函數(shù)即可。FreeRTOS為開發(fā)者考慮得特別多,PendSV_Handler()與SVC_Handler()這兩個很重要的函數(shù)都幫我們實現(xiàn)了,在在port.c文件中已經(jīng)實現(xiàn)xPortPendSVHandler()與vPortSVCHandler()函數(shù),防止我們自己實現(xiàn)不了,那么在stm32f10x_it.c中就需要我們注釋掉PendSV_Handler()與SVC_Handler()這兩個函數(shù)了。
//void SVC_Handler(void)
//{
//}
//void PendSV_Handler(void)
//{
//}
extern void xPortSysTickHandler(void);
//systick中斷服務(wù)函數(shù)
void SysTick_Handler(void)
{
#if (INCLUDE_xTaskGetSchedulerState == 1 )
if (xTaskGetSchedulerState() != taskSCHEDULER_NOT_STARTED)
{
#endif /* INCLUDE_xTaskGetSchedulerState */
xPortSysTickHandler();
#if (INCLUDE_xTaskGetSchedulerState == 1 )
}
#endif /* INCLUDE_xTaskGetSchedulerState */
}
創(chuàng)建任務(wù)
這里,我們創(chuàng)建一個單任務(wù),任務(wù)使用的棧和任務(wù)控制塊是在創(chuàng)建任務(wù)的時候FreeRTOS動態(tài)分配的。
任務(wù)必須是一個死循環(huán),否則任務(wù)將通過LR返回,如果LR指向了非法的內(nèi)存就會產(chǎn)生HardFault_Handler,而FreeRTOS指向一個死循環(huán),那么任務(wù)返回之后就在死循環(huán)中執(zhí)行,這樣子的任務(wù)是不安全的,所以避免這種情況,任務(wù)一般都是死循環(huán)并且無返回值的。
并且每個任務(wù)循環(huán)主體中應(yīng)該有阻塞任務(wù)的函數(shù),否則就會餓死比它優(yōu)先級更低的任務(wù)?。?!
/* FreeRTOS頭文件 */
#include “FreeRTOS.h”
#include “task.h”
/* 開發(fā)板硬件bsp頭文件 */
#include “bsp_led.h”
static void AppTaskCreate(void);/* AppTask任務(wù) */
/* 創(chuàng)建任務(wù)句柄 */
static TaskHandle_t AppTask_Handle = NULL;
int main(void)
{
BaseType_t xReturn = pdPASS;/* 定義一個創(chuàng)建信息返回值,默認為pdPASS */
/* 開發(fā)板硬件初始化 */
BSP_Init();
/* 創(chuàng)建AppTaskCreate任務(wù) */
xReturn = xTaskCreate((TaskFunction_t )AppTask, /* 任務(wù)入口函數(shù) */
(const char* )“AppTask”,/* 任務(wù)名字 */
(uint16_t )512, /* 任務(wù)棧大小 */
?。╲oid* )NULL,/* 任務(wù)入口函數(shù)參數(shù) */
?。║BaseType_t )1, /* 任務(wù)的優(yōu)先級 */
(TaskHandle_t* )&AppTask_Handle);/* 任務(wù)控制塊指針 */
/* 啟動任務(wù)調(diào)度 */
if(pdPASS == xReturn)
vTaskStartScheduler(); /* 啟動任務(wù),開啟調(diào)度 */
else
return -1;
while(1); /* 正常不會執(zhí)行到這里 */
}
static void AppTask(void* parameter)
{
while (1)
{
LED1_ON;
vTaskDelay(500); /* 延時500個tick */
LED1_OFF;
vTaskDelay(500); /* 延時500個tick */
}
}
freertos任務(wù)調(diào)度原理
本章教程為大家將介紹 FreeRTOS 操作系統(tǒng)支持的任務(wù)調(diào)度方式:搶占式,時間片和合作式,這部分算是 FreeRTOS 操作系統(tǒng)的核心了。 對于初學(xué)者來說,要一下子就能夠理解這些比較困難,需要多花些時間把這些基本概念搞清楚,然后閱讀下源碼,深入理解實現(xiàn)方法。
關(guān)于合作式調(diào)度器的特別說明
FreeRTOS 支持的調(diào)度方式
FreeRTOS 操作系統(tǒng)支持三種調(diào)度方式:搶占式調(diào)度,時間片調(diào)度和合作式調(diào)度。 實際應(yīng)用主要是搶占式調(diào)度和時間片調(diào)度,合作式調(diào)度用到的很少。搶占式調(diào)度每個任務(wù)都有不同的優(yōu)先級,任務(wù)會一直運行直到被高優(yōu)先級任務(wù)搶占或者遇到阻塞式的 API 函數(shù),比如 vTaskDelay。時間片調(diào)度每個任務(wù)都有相同的優(yōu)先級,任務(wù)會運行固定的時間片個數(shù)或者遇到阻塞式的 API 函數(shù),比如vTaskDelay,才會執(zhí)行同優(yōu)先級任務(wù)之間的任務(wù)切換。
什么是調(diào)度器
簡單的說,調(diào)度器就是使用相關(guān)的調(diào)度算法來決定當前需要執(zhí)行的任務(wù)。所有的調(diào)度器有一個共同的特性:
調(diào)度器可以區(qū)分就緒態(tài)任務(wù)和掛起任務(wù)(由于延遲,信號量等待,郵箱等待,事件組等待等原因而使得任務(wù)被掛起)。
調(diào)度器可以選擇就緒態(tài)中的一個任務(wù),然后激活它(通過執(zhí)行這個任務(wù))。 當前正在執(zhí)行的任務(wù)是運行態(tài)的任務(wù)。
不同調(diào)度器之間最大的區(qū)別就是如何分配就緒態(tài)任務(wù)間的完成時間。
嵌入式實時操作系統(tǒng)的核心就是調(diào)度器和任務(wù)切換,調(diào)度器的核心就是調(diào)度算法。任務(wù)切換的實現(xiàn)在不同的嵌入式實時操作系統(tǒng)中區(qū)別不大,基本相同的硬件內(nèi)核架構(gòu),任務(wù)切換也是相似的。調(diào)度算法就有些區(qū)別了。下面我們主要了解一下?lián)屨际秸{(diào)度器和時間片調(diào)度器。
搶占式調(diào)度器基本概念
在實際的應(yīng)用中,不同的任務(wù)需要不同的響應(yīng)時間。例如,我們在一個應(yīng)用中需要使用電機,鍵盤和LCD 顯示。電機比鍵盤和 LCD 需要更快速的響應(yīng),如果我們使用合作式調(diào)度器或者時間片調(diào)度,那么電機將無法得到及時的響應(yīng),這時搶占式調(diào)度是必須的。
如果使用了搶占式調(diào)度,最高優(yōu)先級的任務(wù)一旦就緒,總能得到 CPU 的控制權(quán)。 比如,當一個運行著的任務(wù)被其它高優(yōu)先級的任務(wù)搶占,當前任務(wù)的 CPU 使用權(quán)就被剝奪了,或者說被掛起了,那個高優(yōu)先級的任務(wù)立刻得到了 CPU 的控制權(quán)并運行。 又比如,如果中斷服務(wù)程序使一個高優(yōu)先級的任務(wù)進入就緒態(tài),中斷完成時,被中斷的低優(yōu)先級任務(wù)被掛起,優(yōu)先級高的那個任務(wù)開始運行。
使用搶占式調(diào)度器,使得最高優(yōu)先級的任務(wù)什么時候可以得到 CPU 的控制權(quán)并運行是可知的,同時使得任務(wù)級響應(yīng)時間得以最優(yōu)化。
總的來說,學(xué)習搶占式調(diào)度要掌握的最關(guān)鍵一點是:每個任務(wù)都被分配了不同的優(yōu)先級,搶占式調(diào)度器會獲得就緒列表中優(yōu)先級最高的任務(wù),并運行這個任務(wù)。
FreeRTOS 搶占式調(diào)度器的實現(xiàn)
如果用戶在 FreeRTOS 的配置文件 FreeRTOSConfig.h 中禁止使用時間片調(diào)度, 那么每個任務(wù)必須配置不同的優(yōu)先級。當 FreeRTOS 多任務(wù)啟動執(zhí)行后,基本會按照如下的方式去執(zhí)行:
首先執(zhí)行的最高優(yōu)先級的任務(wù) Task1, Task1 會一直運行直到遇到系統(tǒng)阻塞式的 API 函數(shù),比如延遲,事件標志等待,信號量等待,Task1 任務(wù)會被掛起,也就是釋放 CPU 的執(zhí)行權(quán),讓低優(yōu)先級的任務(wù)得到執(zhí)行。
FreeRTOS 操作系統(tǒng)繼續(xù)執(zhí)行任務(wù)就緒列表中下一個最高優(yōu)先級的任務(wù) Task2,Task2 執(zhí)行過程中有兩種情況:
Task1由于延遲時間到, 接收到信號量消息等方面的原因, 使得 Task1從掛起狀態(tài)恢復(fù)到就緒態(tài),在搶占式調(diào)度器的作用下,Task2 的執(zhí)行會被 Task1 搶占。
Task2 會一直運行直到遇到系統(tǒng)阻塞式的 API 函數(shù),比如延遲,事件標志等待,信號量等待, Task2任務(wù)會被掛起,繼而執(zhí)行就緒列表中下一個最高優(yōu)先級的任務(wù)。
如果用戶創(chuàng)建了多個任務(wù)并且采用搶占式調(diào)度器的話,基本都是按照上面兩條來執(zhí)行。 根據(jù)搶占式調(diào)度器,當前的任務(wù)要么被高優(yōu)先級任務(wù)搶占,要么通過調(diào)用阻塞式 API 來釋放 CPU 使用權(quán)讓低優(yōu)先級任務(wù)執(zhí)行,沒有用戶任務(wù)執(zhí)行時就執(zhí)行空閑任務(wù)。
運行條件:
這里僅對搶占式調(diào)度進行說明。
創(chuàng)建 3 個任務(wù) Task1,Task2 和 Task3。
Task1 的優(yōu)先級為 1,Task2 的優(yōu)先級為 2,Task3 的優(yōu)先級為 3。 FreeRTOS 操作系統(tǒng)是設(shè)置的數(shù)值越小任務(wù)優(yōu)先級越低,故 Task3 的優(yōu)先級最高,Task1 的優(yōu)先級最低。
此框圖是 FreeRTOS 操作系統(tǒng)運行過程中的一部分。
運行過程描述如下:
此時任務(wù) Task1 在運行中,運行過程中由于 Task2 就緒,在搶占式調(diào)度器的作用下任務(wù) Task2 搶占Task1 的執(zhí)行。 Task2 進入到運行態(tài),Task1 由運行態(tài)進入到就緒態(tài)。
任務(wù) Task2 在運行中,運行過程中由于 Task3 就緒,在搶占式調(diào)度器的作用下任務(wù) Task3 搶占 Task2的執(zhí)行。 Task3 進入到運行態(tài),Task2 由運行態(tài)進入到就緒態(tài)。
任務(wù) Task3 運行過程中調(diào)用了阻塞式 API 函數(shù),比如 vTaskDelay,任務(wù) Task3 被掛起,在搶占式調(diào)度器的作用下查找到下一個要執(zhí)行的最高優(yōu)先級任務(wù)是 Task2,任務(wù) Task2 由就緒態(tài)進入到運行態(tài)。
任務(wù) Task2 在運行中,運行過程中由于 Task3 再次就緒,在搶占式調(diào)度器的作用下任務(wù) Task3 搶占Task2 的執(zhí)行。 Task3 進入到運行態(tài),Task2 由運行態(tài)進入到就緒態(tài)。
上面就是一個簡單的不同優(yōu)先級任務(wù)通過搶占式調(diào)度進行任務(wù)調(diào)度和任務(wù)切換的過程。
時間片調(diào)度器基本概念
在小型的嵌入式 RTOS 中,最常用的的時間片調(diào)度算法就是 Round-robin 調(diào)度算法。這種調(diào)度算法可以用于搶占式或者合作式的多任務(wù)中。另外,時間片調(diào)度適合用于不要求任務(wù)實時響應(yīng)的情況。
實現(xiàn) Round-robin 調(diào)度算法需要給同優(yōu)先級的任務(wù)分配一個專門的列表,用于記錄當前就緒的任務(wù),并為每個任務(wù)分配一個時間片(也就是需要運行的時間長度,時間片用完了就進行任務(wù)切換)。
FreeRTOS 時間片調(diào)度器的實現(xiàn)在 FreeRTOS 操作系統(tǒng)中只有同優(yōu)先級任務(wù)才會使用時間片調(diào)度,另外還需要用戶在FreeRTOSConfig.h 文件中使能宏定義:
#define configUSE_TIME_SLICING 1默認情況下,此宏定義已經(jīng)在 FreeRTOS.h 文件里面使能了,用戶可以不用在 FreeRTOSConfig.h 文件中再單獨使能。
下面我們通過如下的框圖來說明一下時間片調(diào)度在 FreeRTOS 中的運行過程,讓大家有一個形象的認識。
運行條件:
這里僅對時間片調(diào)度進行說明。
創(chuàng)建 4 個同優(yōu)先級任務(wù) Task1,Task2,Task3 和 Task4。
每個任務(wù)分配的時間片大小是 5 個系統(tǒng)時鐘節(jié)拍。
運行過程描述如下:
先運行任務(wù) Task1,運行夠 5 個系統(tǒng)時鐘節(jié)拍后,通過時間片調(diào)度切換到任務(wù) Task2。
任務(wù) Task2 運行夠 5 個系統(tǒng)時鐘節(jié)拍后,通過時間片調(diào)度切換到任務(wù) Task3。
任務(wù) Task3 在運行期間調(diào)用了阻塞式 API 函數(shù),調(diào)用函數(shù)時,雖然 5 個系統(tǒng)時鐘節(jié)拍的時間片大小還沒有用完,此時依然會通過時間片調(diào)度切換到下一個任務(wù) Task4。 (注意,沒有用完的時間片不會再使用,下次任務(wù) Task3 得到執(zhí)行還是按照 5 個系統(tǒng)時鐘節(jié)拍運行)
任務(wù) Task4 運行夠 5 個系統(tǒng)時鐘節(jié)拍后,通過時間片調(diào)度切換到任務(wù) Task1。
上面就是一個簡單的同優(yōu)先級任務(wù)通過時間片調(diào)度進行任務(wù)調(diào)度和任務(wù)切換的過程。
Summary:
時間片調(diào)度和搶占式調(diào)度我們一般都是開啟了的,這樣優(yōu)先級相同時,使用時間片調(diào)度,優(yōu)先級不同時,使用搶占式調(diào)度。默認情況下,在freertos.h中使能了時間片調(diào)度:
而搶占式調(diào)度需要我們用戶自己開啟,一般在freertosconfig.h中使能:
小編推薦閱讀:
評論