FreeRTOS篇章之任務管理

寫在前面:
本文章旨在總結備份、方便以後查詢,由於是個人總結,如有不對,歡迎指正;另外,內容大部分來自網絡、書籍、和各類手冊,如若侵權請告知,馬上刪帖致歉。

 

目錄

一、任務狀態

二、任務優先級

三、任務創建

四、任務刪除

五、代碼搭建


 

一、任務狀態

任務目前存在四種狀態,分爲:運行、就緒、阻塞、掛起;

  1. Running — 運行態 
    這是任務在執行的時候的狀態,處在運行態意味着任務獲得 CPU的使用權,對於單核 CPU,此時不存在其他運行態的任務。

  2. Ready — 就緒態 
    處在就緒態意味着這個任務是可以執行的,比如某個事件發生、隊列數據到來、所請求的資源有效等;但是,因爲此時有一個相同優先級或者更高優先級的任務正在運行,此時任務無法執行而處於就緒態。

  3. Blocked — 阻塞態 
    如果一個任務正在等待一個時間到來或者外部的事件到來,比如一個調用 vTaskDelay()函數的任務,在延時時間到來之前,任務將處在阻塞狀態。當然,任務在等待隊列、信號量、事件組、通知、信號量事件時都會處在阻塞態。通常,這些等待都會設置一個超時 timeout時間,當等待超時是將任務從阻塞態退出,防止任務被無限掛起。處在阻塞態的任務不佔用任何 CPU時間。

  4. Suspended — 掛起態 
    掛起態無法進入運行態,同時不像阻塞態有超時時間,掛起態無超時時間,除非調用 vTaskResume()推出掛起態。相應的調用 vTaskSuspend()將一個任務掛起。掛起態可以描述成“凍結”;除非“解凍”,否則無法再有機會被執行到。

它們的狀態關係圖:

 

二、任務優先級

每個任務在創建的時候都要指定一個從 0到 configMAX_PRIORITIES - 1的優先級,與其他 RTOS不同之處在於,優先級數值越大,優先級越低。configMAX_PRIORITIES 定義在 freeRTOSConfig.h頭文件中。

configMAX_PRIORITIES 的值可以是任意值,也就是說 FreeRTOS的任務數量不受限制。事實上,因爲 FreeRTOS允許任務共享同一個優先級,configMAX_PRIORITIES 的大小不會限制到任務數量。但是對於實際應用來說,因爲 FreeRTOS中即使未用到的優先級,任然會佔用一定的內存,因此優先級的最大數值,應與實際應用貼近。

在 freeRTOSConfig.h頭文件中,定義了 configUSE_PORT_OPTIMISED_TASK_SELECTION,擁有某些存在任務選擇優化機制的架構,此時最大優先級不能超過 32。

FreeRTOS中的空閒任務 IDEL Task總是最低優先級,因此它的優先級爲 0。

FreeRTOS的調度器總是確保當前得到 CPU時間的任務的優先級是最高的,換句話說,就緒態中高優先級的任務,總是會優先進入運行態。

對於共用同一優先級的任務,如果 configUSE_TIME_SLICING未定義或者定義爲“1”,則 FreeRTOS將使用時間片輪詢的機制去調度這些任務。

跟多具體的配置信息可以去看 FreeRTOS篇章之 FreeRTOSConfig.h分析

 

三、任務創建

xTaskCreate() API 函數原型實現:

#if( configSUPPORT_DYNAMIC_ALLOCATION == 1 )

	BaseType_t xTaskCreate(	TaskFunction_t pxTaskCode,
				const char * const pcName,
				const uint16_t usStackDepth,
				void * const pvParameters,
				UBaseType_t uxPriority,
				TaskHandle_t * const pxCreatedTask ) /*lint !e971 Unqualified char types are allowed for strings and single characters only. */
	{
	TCB_t *pxNewTCB;
	BaseType_t xReturn;

		/* If the stack grows down then allocate the stack then the TCB so the stack
		does not grow into the TCB.  Likewise if the stack grows up then allocate
		the TCB then the stack. */
		#if( portSTACK_GROWTH > 0 )
		{
			/* Allocate space for the TCB.  Where the memory comes from depends on
			the implementation of the port malloc function and whether or not static
			allocation is being used. */
			pxNewTCB = ( TCB_t * ) pvPortMalloc( sizeof( TCB_t ) );

			if( pxNewTCB != NULL )
			{
				/* Allocate space for the stack used by the task being created.
				The base of the stack memory stored in the TCB so the task can
				be deleted later if required. */
				pxNewTCB->pxStack = ( StackType_t * ) pvPortMalloc( ( ( ( size_t ) usStackDepth ) * sizeof( StackType_t ) ) ); /*lint !e961 MISRA exception as the casts are only redundant for some ports. */

				if( pxNewTCB->pxStack == NULL )
				{
					/* Could not allocate the stack.  Delete the allocated TCB. */
					vPortFree( pxNewTCB );
					pxNewTCB = NULL;
				}
			}
		}
		#else /* portSTACK_GROWTH */
		{
		StackType_t *pxStack;

			/* Allocate space for the stack used by the task being created. */
			pxStack = ( StackType_t * ) pvPortMalloc( ( ( ( size_t ) usStackDepth ) * sizeof( StackType_t ) ) ); /*lint !e961 MISRA exception as the casts are only redundant for some ports. */

			if( pxStack != NULL )
			{
				/* Allocate space for the TCB. */
				pxNewTCB = ( TCB_t * ) pvPortMalloc( sizeof( TCB_t ) ); /*lint !e961 MISRA exception as the casts are only redundant for some paths. */

				if( pxNewTCB != NULL )
				{
					/* Store the stack location in the TCB. */
					pxNewTCB->pxStack = pxStack;
				}
				else
				{
					/* The stack cannot be used as the TCB was not created.  Free
					it again. */
					vPortFree( pxStack );
				}
			}
			else
			{
				pxNewTCB = NULL;
			}
		}
		#endif /* portSTACK_GROWTH */

		if( pxNewTCB != NULL )
		{
			#if( tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE != 0 )
			{
				/* Tasks can be created statically or dynamically, so note this
				task was created dynamically in case it is later deleted. */
				pxNewTCB->ucStaticallyAllocated = tskDYNAMICALLY_ALLOCATED_STACK_AND_TCB;
			}
			#endif /* configSUPPORT_STATIC_ALLOCATION */

			prvInitialiseNewTask( pxTaskCode, pcName, ( uint32_t ) usStackDepth, pvParameters, uxPriority, pxCreatedTask, pxNewTCB, NULL );
			prvAddNewTaskToReadyList( pxNewTCB );
			xReturn = pdPASS;
		}
		else
		{
			xReturn = errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY;
		}

		return xReturn;
	}

#endif /* configSUPPORT_DYNAMIC_ALLOCATION */

可以看到,該函數是受宏 configSUPPORT_DYNAMIC_ALLOCATION控制的,該宏位於 FreeRTOSConfig.h文件中

傳入參數:

  • pvTaskCode:指向任務函數入口的指針,是一個永不退出的 C函數,實現常通常是一個死循環
  • pcName:具有描述性的任務名。這個參數不會被 FreeRTOS 使用,其只是單純地用於輔助調試;通過定義常量 config_MAX_TASK_NAME_LEN 來定義任務名的最大長度(包括 ’ \0 ’ 結束符),該宏位於 FreeRTOSConfig.h文件中
  • usStackDepth:當任務創建時,用於告訴內核爲它分配多大的棧空間,這個值指定的是棧空間可以保存多少個字(word),而不是多少個字節(byte)
  • pvParameters:任務函數接受一個指向 void的指針(void *),即是傳遞到任務中的值
  • uxPriority:用於指定任務執行的優先級。優先級的取值範圍可以從最低優先級 0到最高優先級(configMAX_PRIORITIES – 1);configMAX_PRIORITIES 是一個由用戶定義的常量,該宏位於 FreeRTOSConfig.h文件中
  • pxCreatedTask:用於傳出任務的句柄。這個句柄將在 API調用中對該創建出來的任務進行引用,比如改變任務優先級,或者刪除任務;如果應用程序中不會用到這個任務的句柄,則 pxCreatedTask可以被設爲 NULL

返回參數(有兩個可能的返回值):

  • pdTRUE:表明任務創建成功
  • errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY:由於內存堆空間不足,FreeRTOS 無法分配足夠的空間來保存任務結構數據和任務棧,而無法創建任務

 

四、任務刪除

vTaskDelete() API 函數原型實現:

#if ( INCLUDE_vTaskDelete == 1 )

	void vTaskDelete( TaskHandle_t xTaskToDelete )
	{
	TCB_t *pxTCB;

		taskENTER_CRITICAL();
		{
			/* If null is passed in here then it is the calling task that is
			being deleted. */
			pxTCB = prvGetTCBFromHandle( xTaskToDelete );

			/* Remove task from the ready list. */
			if( uxListRemove( &( pxTCB->xStateListItem ) ) == ( UBaseType_t ) 0 )
			{
				taskRESET_READY_PRIORITY( pxTCB->uxPriority );
			}
			else
			{
				mtCOVERAGE_TEST_MARKER();
			}

			/* Is the task waiting on an event also? */
			if( listLIST_ITEM_CONTAINER( &( pxTCB->xEventListItem ) ) != NULL )
			{
				( void ) uxListRemove( &( pxTCB->xEventListItem ) );
			}
			else
			{
				mtCOVERAGE_TEST_MARKER();
			}

			/* Increment the uxTaskNumber also so kernel aware debuggers can
			detect that the task lists need re-generating.  This is done before
			portPRE_TASK_DELETE_HOOK() as in the Windows port that macro will
			not return. */
			uxTaskNumber++;

			if( pxTCB == pxCurrentTCB )
			{
				/* A task is deleting itself.  This cannot complete within the
				task itself, as a context switch to another task is required.
				Place the task in the termination list.  The idle task will
				check the termination list and free up any memory allocated by
				the scheduler for the TCB and stack of the deleted task. */
				vListInsertEnd( &xTasksWaitingTermination, &( pxTCB->xStateListItem ) );

				/* Increment the ucTasksDeleted variable so the idle task knows
				there is a task that has been deleted and that it should therefore
				check the xTasksWaitingTermination list. */
				++uxDeletedTasksWaitingCleanUp;

				/* The pre-delete hook is primarily for the Windows simulator,
				in which Windows specific clean up operations are performed,
				after which it is not possible to yield away from this task -
				hence xYieldPending is used to latch that a context switch is
				required. */
				portPRE_TASK_DELETE_HOOK( pxTCB, &xYieldPending );
			}
			else
			{
				--uxCurrentNumberOfTasks;
				prvDeleteTCB( pxTCB );

				/* Reset the next expected unblock time in case it referred to
				the task that has just been deleted. */
				prvResetNextTaskUnblockTime();
			}

			traceTASK_DELETE( pxTCB );
		}
		taskEXIT_CRITICAL();

		/* Force a reschedule if it is the currently running task that has just
		been deleted. */
		if( xSchedulerRunning != pdFALSE )
		{
			if( pxTCB == pxCurrentTCB )
			{
				configASSERT( uxSchedulerSuspended == 0 );
				portYIELD_WITHIN_API();
			}
			else
			{
				mtCOVERAGE_TEST_MARKER();
			}
		}
	}

#endif /* INCLUDE_vTaskDelete */

同樣的,該函數則是受宏 INCLUDE_vTaskDelete控制的,該宏位於 FreeRTOSConfig.h文件中

傳入參數:

  • pxTaskToDelete:被刪除任務的句柄(目標任務)。若傳入 NULL,則是刪除當前執行的任務(即刪除自己)

 

五、代碼搭建

任務(task)是在 FreeRTOS中執行的基本單位,每個 task都是由一個 C函數所組成,意思是你需要先定義一個 C 的函數,然後再用 xTaskCreate() 這個 API 來建立一個 task,這個 C 函數有幾個特點,它的返回值必須是 void,其中通常是存於無限循環中(while、for),所有關於這個 task 的工作都會在這個無限循環中進行,而且這個函數不會有 return,FreeRTOS 不允許 task 自行結束(使用 return 或執行到函數的最後一行)

Task 被建立出來後,它會配置有自己的堆棧空間和 stack variable(就是 function 中定義的變量)

main.c

/*
    FreeRTOS V9.0.0 - Copyright (C) 2016 Real Time Engineers Ltd.
    All rights reserved

    VISIT http://www.FreeRTOS.org TO ENSURE YOU ARE USING THE LATEST VERSION.

    This file is part of the FreeRTOS distribution.

    FreeRTOS is free software; you can redistribute it and/or modify it under
    the terms of the GNU General Public License (version 2) as published by the
    Free Software Foundation >>>> AND MODIFIED BY <<<< the FreeRTOS exception.

    ***************************************************************************
    >>!   NOTE: The modification to the GPL is included to allow you to     !<<
    >>!   distribute a combined work that includes FreeRTOS without being   !<<
    >>!   obliged to provide the source code for proprietary components     !<<
    >>!   outside of the FreeRTOS kernel.                                   !<<
    ***************************************************************************

    FreeRTOS is distributed in the hope that it will be useful, but WITHOUT ANY
    WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
    FOR A PARTICULAR PURPOSE.  Full license text is available on the following
    link: http://www.freertos.org/a00114.html

    ***************************************************************************
     *                                                                       *
     *    FreeRTOS provides completely free yet professionally developed,    *
     *    robust, strictly quality controlled, supported, and cross          *
     *    platform software that is more than just the market leader, it     *
     *    is the industry's de facto standard.                               *
     *                                                                       *
     *    Help yourself get started quickly while simultaneously helping     *
     *    to support the FreeRTOS project by purchasing a FreeRTOS           *
     *    tutorial book, reference manual, or both:                          *
     *    http://www.FreeRTOS.org/Documentation                              *
     *                                                                       *
    ***************************************************************************

    http://www.FreeRTOS.org/FAQHelp.html - Having a problem?  Start by reading
    the FAQ page "My application does not run, what could be wrong?".  Have you
    defined configASSERT()?

    http://www.FreeRTOS.org/support - In return for receiving this top quality
    embedded software for free we request you assist our global community by
    participating in the support forum.

    http://www.FreeRTOS.org/training - Investing in training allows your team to
    be as productive as possible as early as possible.  Now you can receive
    FreeRTOS training directly from Richard Barry, CEO of Real Time Engineers
    Ltd, and the world's leading authority on the world's leading RTOS.

    http://www.FreeRTOS.org/plus - A selection of FreeRTOS ecosystem products,
    including FreeRTOS+Trace - an indispensable productivity tool, a DOS
    compatible FAT file system, and our tiny thread aware UDP/IP stack.

    http://www.FreeRTOS.org/labs - Where new FreeRTOS products go to incubate.
    Come and try FreeRTOS+TCP, our new open source TCP/IP stack for FreeRTOS.

    http://www.OpenRTOS.com - Real Time Engineers ltd. license FreeRTOS to High
    Integrity Systems ltd. to sell under the OpenRTOS brand.  Low cost OpenRTOS
    licenses offer ticketed support, indemnification and commercial middleware.

    http://www.SafeRTOS.com - High Integrity Systems also provide a safety
    engineered and independently SIL3 certified version for use in safety and
    mission critical applications that require provable dependability.

    1 tab == 4 spaces!
*/


/* Standard includes. */
#include <stdio.h>

/* Scheduler includes. */
#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"

/* Library includes. */
#include "stm32f10x_it.h"

/* Private app includes. */
#include "bsp_uart.h"
#include "bsp_time.h"
#include "bsp_gpio.h"


/*----------------------------- End -----------------------------*/

/*
 * User Private Task.
 */
static void prvUser_Task( void *pvParameters );

/*
 * Configure the clocks, GPIO and other peripherals as required by the demo.
 */
static void prvSetupHardware( void );

/*----------------------------- End -----------------------------*/


/************************************************
函數名稱 : main
功    能 : 主函數入口
參    數 : 無
返 回 值 : 無
*************************************************/
int main( void )
{
#ifdef DEBUG
  debug();
#endif

	prvSetupHardware();

	/* Start the tasks defined within this file/specific to this demo. */
	xTaskCreate( prvUser_Task, "prvUser_Task", configMINIMAL_STACK_SIZE, NULL, tskIDLE_PRIORITY, NULL );

	/* Start the scheduler. */
	vTaskStartScheduler();

	/* Will only get here if there was not enough heap space to create the
	idle task. */
	return 0;
}
/*----------------------------- End -----------------------------*/

static void prvUser_Task( void *pvParameters )
{
    /* User-defined private tasks */

    while(1){
        /* do something here */
    }
	
    /* 
    * 如果你的 task 就是需要離開 loop 並結束
    * 需要用 vTaskDelete 來刪除自己而非使用 return 或自然結束(執行到最後一行)
    * 這個參數的 NULL 值是表示自己 
    */
    vTaskDelete(NULL);		// 刪除自己
}

static void prvSetupHardware( void )
{
	/* Start with the clocks in their expected state. */
	RCC_DeInit();

	/* Enable HSE (high speed external clock). */
	RCC_HSEConfig( RCC_HSE_ON );

	/* Wait till HSE is ready. */
	while( RCC_GetFlagStatus( RCC_FLAG_HSERDY ) == RESET )
	{
	}

	/* 2 wait states required on the flash. */
	*( ( unsigned long * ) 0x40022000 ) = 0x02;

	/* HCLK = SYSCLK */
	RCC_HCLKConfig( RCC_SYSCLK_Div1 );

	/* PCLK2 = HCLK */
	RCC_PCLK2Config( RCC_HCLK_Div1 );

	/* PCLK1 = HCLK/2 */
	RCC_PCLK1Config( RCC_HCLK_Div2 );

	/* PLLCLK = 8MHz * 9 = 72 MHz. */
	RCC_PLLConfig( RCC_PLLSource_HSE_Div1, RCC_PLLMul_9 );

	/* Enable PLL. */
	RCC_PLLCmd( ENABLE );

	/* Wait till PLL is ready. */
	while(RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET)
	{
	}

	/* Select PLL as system clock source. */
	RCC_SYSCLKConfig( RCC_SYSCLKSource_PLLCLK );

	/* Wait till PLL is used as system clock source. */
	while( RCC_GetSYSCLKSource() != 0x08 )
	{
	}

	/* Configure HCLK clock as SysTick clock source. */
	SysTick_CLKSourceConfig( SysTick_CLKSource_HCLK );
	
	/*
	 * STM32中斷優先級分組爲 4,即 4bit都用來表示搶佔優先級,範圍爲:0~15
	 * 優先級分組只需要分組一次即可,以後如果有其他的任務需要用到中斷,
	 * 都統一用這個優先級分組,千萬不要再分組,切忌。
	 */
	NVIC_PriorityGroupConfig( NVIC_PriorityGroup_4 );
	
	/* Other peripheral configuration */
	vSetupTimer();
	vSetupUSART();
	vSetupParPort();
}
/*----------------------------- End -----------------------------*/

#ifdef  DEBUG
/* Keep the linker happy. */
void assert_failed( unsigned char* pcFile, unsigned long ulLine )
{
	for( ;; )
	{
	}
}
#endif


/*---------------------------- END OF FILE ----------------------------*/


首先,prvSetupHardware()函數是配置時鐘,並且初始化硬件配置(即 vSetupTimer(); vSetupUSART(); vSetupParPort();的實現,其實就是封裝了一下之前 stm32筆記中的 time、uart、gpio初始化配置)

Other peripheral configuration

/************************************************
函數名稱 : vSetupTimer
功    能 : Timer初始化接口
參    數 : 無
返 回 值 : 無
*************************************************/
void vSetupTimer( void )
{
	Timer2_Config();
}

/************************************************
函數名稱 : vSetupUSART
功    能 : UART初始化接口
參    數 : 無
返 回 值 : 無
*************************************************/
void vSetupUSART( void )
{
	UART1_Config();
//	UART2_Config();
}

/************************************************
函數名稱 : vSetupParPort
功    能 : 基礎 IO初始化接口
參    數 : 無
返 回 值 : 無
*************************************************/
void vSetupParPort( void )
{
	LED_Config();
	Key_Config();
}

然後到後面的 main函數的實現調用,調用 prvSetupHardware初始化所有跟硬件相關的配置,創建一個 prvUser_Task任務,用於管理所有後面創建的任務(其實就是爲了方便管理),因爲這裏面沒有創建其他函數,所以放了個 while循環在裏面,最後再調用 vTaskDelete(NULL);把自己刪除;

這裏可能有個疑問,明明有個 while死循環,執行不到 vTaskDelete()函數,沒什麼意義啊,雖然道理是這樣,但是最好在每個任務的結尾放上 vTaskDelete()函數,以確保安全(特殊情況除外);還有就是,因爲在 prvUser_Task任務中有個死循環,那在 while之後的程序應該都執行不了啊,包括 prvUser_Task任務後面的 vTaskStartScheduler();,如果是按正常的思維是這樣的;但是,這裏是在使用 RTOS,調用 xTaskCreate創建任務並不表示就已經執行了,只是告訴內核,我創建了這麼一個任務,真正使系統開始執行是在調用了 vTaskStartScheduler();啓動調度器之後

 

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章