目录
- 1.区分
- 1.1 相似之处:
- 1.2 区别
- 2.任务的创建和删除
- 2.1 任务创建
- 2.1.1 动态创建
- `pxTaskCode` (任务函数指针)
- `pcName` (任务名称)
- `usStackDepth` (栈深度)
- `pvParameters` (任务参数)
- `uxPriority` (任务优先级)
- `pxCreatedTask` (任务句柄)
- 2.1.2 静态创建
- 2.1.3 最后一个参数
- 2.1.4 示例程序
- 2.2 任务删除
- 2.2.1 三种删除
- 2.2.2 涉及优先级
- 3.任务优先级
- 3.1 实验1
- 3.2 实验2:修改任务优先级
基础的freertos程序文件:📎04_freertos_example_base_uart.zip
1.区分
在FreeRTOS中,**任务(task)和线程(thread)的概念是相似的,但它们之间有一些微小的区别。**一般情况下将任务理解成线程也是可以的
1.1 相似之处:
- 任务和线程的功能:无论是任务还是线程,它们都是操作系统调度执行的基本单位。它们都代表了一个独立的执行流,执行一定的代码,拥有自己的执行上下文(例如程序计数器、堆栈、寄存器等)。
- 多任务/多线程的目的:任务和线程的主要目的是实现并发执行,使得多个代码块或功能模块能够“并行”处理,提高系统的响应性和效率。
- 上下文切换:任务和线程都需要支持上下文切换,也就是说操作系统需要在不同的任务或线程之间进行切换,保存当前任务的状态并加载另一个任务或线程的状态。
1.2 区别
FreeRTOS中的任务(Task)
在FreeRTOS中,任务是FreeRTOS调度的基本单位。FreeRTOS中的任务通常与线程在其他操作系统中的概念相似,但由于FreeRTOS是一个实时操作系统(RTOS),它的任务管理与一般操作系统中的线程管理有一些不同之处。
- 任务调度:FreeRTOS中的任务由FreeRTOS内核调度,而不像一般操作系统中的线程那样由操作系统调度器调度。
- 任务创建:FreeRTOS通过
xTaskCreate()
来创建任务,每个任务都有自己的栈,执行的是一个具体的任务函数。任务的调度基于优先级,可以是抢占式的,也可以是协作式的。 - 优先级:FreeRTOS任务具有优先级,任务可以设置不同的优先级。高优先级的任务会抢占低优先级的任务。
- 资源占用:FreeRTOS任务通常占用较少的资源,相比线程,它的实现和切换开销较小,因此适合在资源受限的嵌入式系统中使用。
传统操作系统中的线程(Thread):
在线程的传统操作系统(如Windows、Linux)中,线程是一个操作系统级别的概念。线程与进程相比,进程是独立的资源分配单位,而线程是在进程内执行的最小单位。
- 线程调度:传统操作系统的线程由操作系统的内核调度,线程可以有自己的内存空间(例如,堆栈和局部变量),并且与其他线程共享父进程的资源。
- 线程的资源和开销:线程通常占用较多的资源,因为它们在操作系统的调度中有更多的管理和控制,例如多核处理器的使用、内存分配等。线程切换的开销通常比FreeRTOS任务要大,因为操作系统需要管理更多的系统资源。
- 线程的多核支持:传统的操作系统支持在多核系统中调度线程,它们可能会在不同的CPU核心上并行执行。
特性 | FreeRTOS任务(Task) | 传统操作系统线程(Thread) |
---|---|---|
资源占用 | 占用较少的资源,通常是精简版的线程实现 | 占用更多资源,包含更复杂的管理和调度机制 |
调度方式 | 基于优先级调度,抢占式或协作式调度 | 基于操作系统调度,通常是抢占式调度 |
优先级管理 | 支持任务优先级,任务优先级影响调度 | 线程可以设置优先级,但通常由操作系统调度决定 |
栈和内存管理 | 每个任务有独立的栈,内存管理简单 | 每个线程有自己的栈,并可能涉及复杂的内存管理机制 |
调度开销 | 相对较小,适用于资源受限的嵌入式系统 | 较大,涉及到进程内存、CPU时间的调度等 |
使用场景 | 适用于嵌入式系统,特别是实时操作要求的场合 | 适用于桌面操作系统、服务器和大规模的多线程应用 |
**FreeRTOS中的任务(Task)**可以理解为一种特殊的线程,优化了资源管理并更适合嵌入式和实时操作系统。任务的创建和调度相对轻量级,任务优先级对调度有重要影响,且任务间的切换开销较小。
**传统操作系统中的线程(Thread)**通常是更重的概念,包含更多的资源管理和调度机制,适用于多核操作系统和大规模并发应用。
尽管它们在许多方面有相似性,任务和线程的区别主要在于它们的实现细节、资源占用、调度机制等,FreeRTOS中的任务更倾向于嵌入式和实时性要求高的场景,而线程则更常见于桌面操作系统和大型应用程序中。
2.任务的创建和删除
2.1 任务创建
每个任务的创建都需要分配一个栈的空间的给它,并且每个任务的栈空间是独一无二的,不能重复。freeRtos中有两种方式创建任务:
- 动态创建,指任务的栈的空间由系统随机分配
- 静态创建,指任务的栈的空间可以由自己指定,比如创建一个全局静态数组作为栈分配给该任务,只不过数组的容量需要和指定的栈的深度对应
2.1.1 动态创建
BaseType_t xTaskCreate( TaskFunction_t pxTaskCode, // 任务函数const char * const pcName, // 任务名字const configSTACK_DEPTH_TYPE usStackDepth, // 栈的深度void * const pvParameters, // 传递给任务的参数UBaseType_t uxPriority, // 任务优先级TaskHandle_t * const pxCreatedTask // 任务句柄
);
xTaskCreate
函数的作用是创建一个任务,任务的定义包括任务代码、任务名称、栈大小、传递给任务的参数、优先级和任务句柄。这个函数是 FreeRTOS 创建和管理任务的核心接口。任务的参数根据任务的需求和系统的资源来配置,正确设置这些参数非常重要,以确保任务的正确运行和内存的合理利用。参数介绍如下:
pxTaskCode
(任务函数指针)
TaskFunction_t
(通常定义为void (*)(void *pvParameters)
)。- 这是一个指向任务函数的指针,任务函数是执行任务的主体代码。你需要为每个任务创建一个函数,这个函数会一直运行,除非任务被删除或挂起。
- 任务函数通常是一个无限循环,在函数中可以执行你需要完成的任务。如果任务有结束时需要做的事情,任务函数必须调用
vTaskDelete(NULL)
来删除自己。
void myTask(void *pvParameters) {while(1) {// 执行任务代码}
}
pcName
(任务名称)
const char * const
,是任务的名称(字符串类型)。- 这是任务的名字,它对调试非常有用。FreeRTOS 内部不会使用这个名字,只是为了开发人员调试和识别任务。
- 名字的长度由
configMAX_TASK_NAME_LEN
决定,通常是在FreeRTOSConfig.h
中配置的。如果没有特别要求,通常保持默认值即可。
xTaskCreate(myTask, "MyTask", 128, NULL, 1, NULL);
- 在这个例子中,任务的名字是
"MyTask"
。
usStackDepth
(栈深度)
-
configSTACK_DEPTH_TYPE
,通常是uint16_t
类型。 -
为每个任务分配的栈大小。任务栈用于存储任务的局部变量、函数调用等。这里的
usStackDepth
表示栈的深度(单位是字)。例如,如果传入100
,表示栈大小为 100 字(通常是 400 字节,如果word
是 4 字节。) ---- 查看了该函数内部的调用,发现貌似栈的基本单位是uint_32,也就是word默认的就是4字节: -
pxNewTCB->pxStack = ( StackType_t * ) pvPortMallocStack( ( ( ( size_t ) usStackDepth ) * sizeof( StackType_t ) ) );
其中StackType_t:typedef portSTACK_TYPE StackType_t;
,而portSTACK_TYPE :#define portSTACK_TYPE uint32_t
,为4字节
-
注意:栈的大小要根据任务的需求来设置,太小可能导致栈溢出,太大则浪费内存。栈的大小有时需要通过经验或分析工具来确定。FreeRTOS 本身没有提供非常精确的栈计算方法,但可以通过调试工具分析栈的使用情况。
xTaskCreate(myTask, "MyTask", 128, NULL, 1, NULL); // 128 字节的栈
pvParameters
(任务参数)
void *
(指向任何类型的指针)。- 这个参数会被传递给任务的任务函数
pxTaskCode
。如果任务不需要任何参数,可以传递NULL
。它使得任务可以接收传递给它的额外数据(如结构体或指针等)。
void myTask(void *pvParameters) {// 任务执行时,可以通过 pvParameters 获取到传递的参数
}xTaskCreate(myTask, "MyTask", 128, (void *)parameter, 1, NULL);
在这个例子中,pvParameters
是一个传递给任务的指针,可以让任务访问外部传递的数据。
uxPriority
(任务优先级)
UBaseType_t
,通常是uint32_t
或unsigned int
类型。- 定义任务的优先级。FreeRTOS 中的任务是基于优先级调度的,优先级值越小,优先级越低。有效优先级范围是
0
到configMAX_PRIORITIES - 1
,其中configMAX_PRIORITIES
是在FreeRTOSConfig.h
中定义的最大优先级数。
xTaskCreate(myTask, "MyTask", 128, NULL, 2, NULL); // 任务优先级为 2
如果传入的优先级超过了 configMAX_PRIORITIES - 1
,FreeRTOS 会自动将其调整为 configMAX_PRIORITIES - 1
。
pxCreatedTask
(任务句柄)
TaskHandle_t *
,这是一个指向任务句柄的指针。 在FreeRTOS中,每个任务在创建时都会分配一个任务句柄。这个句柄是一个指向任务控制块(TCB)的指针。- 如果想在任务创建后保存任务的句柄(用于之后操作任务,比如修改任务优先级、删除任务等),可以传入这个参数。任务句柄是一个标识符,实现在任务间进行交互或控制任务。
- 如果不需要这个句柄,可以将其设为
NULL
,这样就不保存任务句柄。
TaskHandle_t xTaskHandle;
xTaskCreate(myTask, "MyTask", 128, NULL, 1, &xTaskHandle); // 保存任务句柄
之后,可以使用 xTaskHandle
来控制任务,例如改变任务优先级:
vTaskPrioritySet(xTaskHandle, 2); // 修改任务优先级
2.1.2 静态创建
TaskHandle_t xTaskCreateStatic( TaskFunction_t pxTaskCode,const char * const pcName, /*lint !e971 Unqualified char types are allowed for strings and single characters only. */const uint32_t ulStackDepth,void * const pvParameters,UBaseType_t uxPriority,StackType_t * const puxStackBuffer,StaticTask_t * const pxTaskBuffer )
参6的StackType_t * const puxStackBuffer
,可以指定一个静态数组传入作为所创建任务的栈,需要注意数组类型实际上是uint_32
要想使用该文件还要去配置FreeRtos中的文件:
实现函数
2.1.3 最后一个参数
xTaskCreate`的最后一个参数:`TaskHandle_t * const pxCreatedTask
xTaskCreateStatic`的最后一个参数:` StaticTask_t * const pxTaskBuffer
其实这两个参数最后都是指向一个TCB_t
结构体:
typedef tskTCB TCB_t;// 其中tskTCB定义如下:
typedef struct tskTaskControlBlock /* The old naming convention is used to prevent breaking kernel aware debuggers. */
{volatile StackType_t * pxTopOfStack; /*< Points to the location of the last item placed on the tasks stack. THIS MUST BE THE FIRST MEMBER OF THE TCB STRUCT. */#if ( portUSING_MPU_WRAPPERS == 1 )xMPU_SETTINGS xMPUSettings; /*< The MPU settings are defined as part of the port layer. THIS MUST BE THE SECOND MEMBER OF THE TCB STRUCT. */#endifListItem_t xStateListItem; /*< The list that the state list item of a task is reference from denotes the state of that task (Ready, Blocked, Suspended ). */ListItem_t xEventListItem; /*< Used to reference a task from an event list. */UBaseType_t uxPriority; /*< The priority of the task. 0 is the lowest priority. */StackType_t * pxStack; /*< Points to the start of the stack. */char pcTaskName[ configMAX_TASK_NAME_LEN ]; /*< Descriptive name given to the task when created. Facilitates debugging only. */ /*lint !e971 Unqualified char types are allowed for strings and single characters only. */#if ( ( portSTACK_GROWTH > 0 ) || ( configRECORD_STACK_HIGH_ADDRESS == 1 ) )StackType_t * pxEndOfStack; /*< Points to the highest valid address for the stack. */#endif#if ( portCRITICAL_NESTING_IN_TCB == 1 )UBaseType_t uxCriticalNesting; /*< Holds the critical section nesting depth for ports that do not maintain their own count in the port layer. */#endif#if ( configUSE_TRACE_FACILITY == 1 )UBaseType_t uxTCBNumber; /*< Stores a number that increments each time a TCB is created. It allows debuggers to determine when a task has been deleted and then recreated. */UBaseType_t uxTaskNumber; /*< Stores a number specifically for use by third party trace code. */#endif#if ( configUSE_MUTEXES == 1 )UBaseType_t uxBasePriority; /*< The priority last assigned to the task - used by the priority inheritance mechanism. */UBaseType_t uxMutexesHeld;#endif#if ( configUSE_APPLICATION_TASK_TAG == 1 )TaskHookFunction_t pxTaskTag;#endif#if ( configNUM_THREAD_LOCAL_STORAGE_POINTERS > 0 )void * pvThreadLocalStoragePointers[ configNUM_THREAD_LOCAL_STORAGE_POINTERS ];#endif#if ( configGENERATE_RUN_TIME_STATS == 1 )uint32_t ulRunTimeCounter; /*< Stores the amount of time the task has spent in the Running state. */#endif#if ( configUSE_NEWLIB_REENTRANT == 1 )/* Allocate a Newlib reent structure that is specific to this task.* Note Newlib support has been included by popular demand, but is not* used by the FreeRTOS maintainers themselves. FreeRTOS is not* responsible for resulting newlib operation. User must be familiar with* newlib and must provide system-wide implementations of the necessary* stubs. Be warned that (at the time of writing) the current newlib design* implements a system-wide malloc() that must be provided with locks.** See the third party link http://www.nadler.com/embedded/newlibAndFreeRTOS.html* for additional information. */struct _reent xNewLib_reent;#endif#if ( configUSE_TASK_NOTIFICATIONS == 1 )volatile uint32_t ulNotifiedValue[ configTASK_NOTIFICATION_ARRAY_ENTRIES ];volatile uint8_t ucNotifyState[ configTASK_NOTIFICATION_ARRAY_ENTRIES ];#endif/* See the comments in FreeRTOS.h with the definition of* tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE. */#if ( tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE != 0 ) /*lint !e731 !e9029 Macro has been consolidated for readability reasons. */uint8_t ucStaticallyAllocated; /*< Set to pdTRUE if the task is a statically allocated to ensure no attempt is made to free the memory. */#endif#if ( INCLUDE_xTaskAbortDelay == 1 )uint8_t ucDelayAborted;#endif#if ( configUSE_POSIX_ERRNO == 1 )int iTaskErrno;#endif
} tskTCB;
freeRtos给出的解释:Task control block. A task control block (TCB) is allocated for each task, and stores task state information, including a pointer to the task’s context(the task’s run time environment, including register values)
任务控制块。为每个任务分配一个任务控制块(TCB),它存储任务的状态信息,包括指向任务上下文的指针(任务的运行时环境,包括寄存器值)。
具体的是如何指向该结构体,感兴趣的可以去看看函数的内部实现
2.1.4 示例程序
#include "FreeRTOS.h"
#include "task.h"
#include <stdio.h>// 定义任务栈大小
#define TASK_STACK_SIZE 128// 定义任务优先级
#define TASK_PRIORITY 1// 定义任务栈和任务控制块 (用于静态任务创建)
static StackType_t xTaskStack[TASK_STACK_SIZE];
static StaticTask_t xTaskBuffer;// 动态任务函数
void vTaskDynamic(void *pvParameters) {while(1) {printf("This is the dynamic task!\n");vTaskDelay(pdMS_TO_TICKS(1000));}
}// 静态任务函数
void vTaskStatic(void *pvParameters) {while(1) {printf("This is the static task!\n");vTaskDelay(pdMS_TO_TICKS(1000));}
}int main(void) {// 创建动态任务 (使用xTaskCreate)xTaskCreate(vTaskDynamic, "DynamicTask", TASK_STACK_SIZE, NULL, TASK_PRIORITY, NULL);// 创建静态任务 (使用xTaskCreateStatic)xTaskCreateStatic(vTaskStatic, // 任务函数"StaticTask", // 任务名称TASK_STACK_SIZE, // 栈大小NULL, // 任务参数TASK_PRIORITY, // 优先级xTaskStack, // 静态栈内存&xTaskBuffer // 静态任务控制块);// 启动调度器vTaskStartScheduler();while(1); // 程序不应该到达这里return 0;
}
下面是各自的示例程序,静态和动态
📎freertos_example_createtaskstatic.zip
📎freertos_example_createtask.zip
以上都是基于同个优先级的情况下的,同优先级的任务表面上是并行,实际上却是交叉运行的
2.2 任务删除
void vTaskDelete( TaskHandle_t xTaskToDelete );//xTaskToDelete:要删除的任务的句柄。如果传入 NULL,则删除当前任务。
//删除指定的任务。如果传入的是任务句柄,它将删除对应的任务。任务删除后,任务的内存会被释放,包括栈空间和任务控制块(TCB)。
//如果传入的是 NULL,则会删除当前正在执行的任务,也就是说,当前任务会删除自己。
2.2.1 三种删除
#include "FreeRTOS.h"
#include "task.h"
#include <stdio.h>// 任务句柄
TaskHandle_t xTaskHandle1 = NULL;
TaskHandle_t xTaskHandle2 = NULL;// 任务函数1
void vTaskFunction1(void *pvParameters) {while(1) {printf("Task 1 is running.\n");// 任务1自杀:删除自己if (some_condition) {printf("Task 1 is deleting itself.\n");vTaskDelete(NULL); // 自杀,删除自己}vTaskDelay(pdMS_TO_TICKS(1000)); // 延时1秒}
}// 任务函数2
void vTaskFunction2(void *pvParameters) {while(1) {printf("Task 2 is running.\n");// 被杀:由其他任务删除if (some_condition) {printf("Task 2 is being deleted by Task 1.\n");vTaskDelete(xTaskHandle2); // 删除任务2}vTaskDelay(pdMS_TO_TICKS(1000)); // 延时1秒}
}// 任务函数3(杀人)
void vTaskFunction3(void *pvParameters) {while(1) {printf("Task 3 is running.\n");// 杀人:删除任务1if (some_condition) {printf("Task 3 is deleting Task 1.\n");vTaskDelete(xTaskHandle1); // 删除任务1}vTaskDelay(pdMS_TO_TICKS(1000)); // 延时1秒}
}int main(void) {// 创建任务1xTaskCreate(vTaskFunction1, "Task1", 128, NULL, 1, &xTaskHandle1);// 创建任务2xTaskCreate(vTaskFunction2, "Task2", 128, NULL, 1, &xTaskHandle2);// 创建任务3xTaskCreate(vTaskFunction3, "Task3", 128, NULL, 1, NULL);// 启动调度器vTaskStartScheduler();while(1); // 不应该到达这里return 0;
}
- 自杀(
**vTaskDelete(NULL)**
):任务1在满足某个条件时调用vTaskDelete(NULL)
,删除自己。这个任务会从调度中移除,并且其栈和控制块被释放。 - 被杀(
**vTaskDelete(xTaskHandle2)**
):任务2可以被任务1删除。在任务1中,调用vTaskDelete(xTaskHandle2)
删除任务2。这是通过任务句柄完成的,任务2会被从调度中移除。 - 杀人(
**vTaskDelete(xTaskHandle1)**
):任务3可以删除任务1。任务3通过调用vTaskDelete(xTaskHandle1)
删除任务1。这是通过任务句柄完成的,任务1会被从调度中移除。
以上内容都是基于创建任务的时候,任务的优先级都是相同的情况 — 参数:**uxPriority**
2.2.2 涉及优先级
FreeRTOS在xTaskCreate
中分配TCB和栈,但并不一定就是在vTaskDelete
中释放TCB和栈。可以尝试去不断轮流调用xTaskCreate
和vTaskDelete
,最终的结果是内存被消耗殆尽(自杀的情况下)
调用 vTaskDelete()
删除任务时,FreeRTOS 不会立即释放任务的 TCB 和栈内存。这是因为:
- 任务可能正在执行代码(例如处于临界区、持有资源),直接释放内存可能导致不可预测的行为(如内存访问冲突)。
- FreeRTOS 的
vTaskDelete
会将待释放的任务的 TCB 和栈内存标记为“待回收”,并插入到一个待删除任务列表中。 - 实际的释放操作由 Idle 任务(空闲任务)完成。Idle 任务在系统空闲时(即没有其他任务运行时)会检查这个列表,并安全地释放内存。
Idle 任务的优先级是 0
(最低优先级)。如果其他任务始终处于就绪状态(例如高优先级任务通过 vTaskDelay()
主动释放 CPU),Idle 任务可能无法及时运行。需要设计任务调度策略,确保 Idle 任务有机会执行。例如,在任务中适当使用 vTaskDelay()
或阻塞式 API。
void vTask1( void *pvParameters )
{const TickType_t xDelay100ms = pdMS_TO_TICKS( 100UL );BaseType_t ret;/* 任务函数的主体一般都是无限循环 */for( ;; ){/* 打印任务的信息 */printf("Task1 is running\r\n");ret = xTaskCreate( vTask2, "Task 2", 1000, NULL, 2, &xTask2Handle );if (ret != pdPASS)printf("Create Task2 Failed\r\n");// 如果不休眠的话, Idle任务无法得到执行// Idel任务会清理任务2使用的内存// 如果不休眠则Idle任务无法执行, 最后内存耗尽vTaskDelay( xDelay100ms );}
}void vTask2( void *pvParameters )
{/* 打印任务的信息 */printf("Task2 is running and about to delete itself\r\n");// 可以直接传入参数NULL, 这里只是为了演示函数用法vTaskDelete(xTask2Handle);
}int main( void )
{prvSetupHardware();xTaskCreate(vTask1, "Task 1", 1000, NULL, 1, NULL);/* 启动调度器 */vTaskStartScheduler();/* 如果程序运行到了这里就表示出错了, 一般是内存不足 */return 0;
}
结果是可以看到先打印:Task1 is running,然后打印:Task2 is running and about to delete itself,后面依次轮流打印
main函数中创建任务1,优先级为1。任务1运行时,它创建任务2,任务2的优先级是2。任务2的优先级最高,它马上执行。
任务2打印一句话后,就删除了自己。(可以尝试去将任务2中的代码加一个循环打印,就可以发现任务1的代码接下来没机会执行到)
任务2被删除后,任务1的优先级最高,轮到任务1继续运行,它调用 vTaskDelay() 进入Block状
态
任务1 Block期间,轮到Idle任务执行:它释放任务2的内存(TCB、栈)时间到后,任务1变为最高优先级的任务继续执行。
如此循环。
如果将任务1中的vTaskDelay
注释到,起初的程序现象没啥问题,但是后面就会出现:Create Task2 Failed,这就是前面提到的内存被消耗殆尽,任务1一直在执行导致最低优先级的Idle任务没执行到
3.任务优先级
这一部分上面《任务的删除》部分已经讲的差不多了,高优先级的任务先运行,这里做补充:
FreeRTOS的调度器可以使用2种方法来快速找出优先级最高的、可以运行的任务。使用不同的方法时,configMAX_PRIORITIES 的取值有所不同。
(1) 通用方法(C 语言实现)
-
配置:
configUSE_PORT_OPTIMISED_TASK_SELECTION = 0
或未定义。 -
- 兼容所有架构,代码可移植。
- 对
configMAX_PRIORITIES
无限制(但越大越消耗内存和时间)。 - 通过遍历优先级位图(
uxTopReadyPriority
)找到最高优先级任务。
-
适用于优先级数量较多(>32)或硬件不支持优化指令的架构。
(2) 架构优化方法(汇编加速)
-
配置:
configUSE_PORT_OPTIMISED_TASK_SELECTION = 1
。 -
- 依赖硬件指令(如 ARM 的
CLZ
指令)快速定位最高优先级位。 - 限制:
configMAX_PRIORITIES ≤ 32
。 - 查找效率高,时间复杂度为 O(1)。
- 依赖硬件指令(如 ARM 的
-
适用于优先级数量 ≤32 且硬件支持优化指令(如 Cortex-M 系列)。
猜测的内部大概实现如下:
1. 就绪任务列表(Ready List)
-
按优先级分组:每个优先级对应一个链表,任务根据优先级插入对应链表。
-
优先级位图(uxTopReadyPriority):
-
- 一个 32 位变量,每一位表示对应优先级是否有就绪任务。
- 例如,优先级 5 的任务就绪时,第 5 位被置 1。
2. 查找最高优先级任务的流程
- 从最高优先级向最低优先级遍历
uxTopReadyPriority
。 - 找到第一个非零位,确定最高优先级。
- 从该优先级的链表中取出第一个任务执行。
// 伪代码示例
for (priority = configMAX_PRIORITIES - 1; priority >= 0; priority--) {if (uxTopReadyPriority & (1UL << priority)) {return priority;}
}
任务的优先级这部分涉及到调度算法,调度行为规则如下
-
抢占式调度:
-
- 高优先级任务就绪时,立即抢占低优先级任务(也就是低优先级任务要把CPU让给已经就绪的高优先级任务,让其去执行)。
- 通过中断或任务主动释放 CPU(如
vTaskDelay()
)触发调度。
-
时间片轮转(同优先级任务):
-
- 相同优先级的任务共享 CPU 时间,通过时间片轮流执行(如“喂饭”和“回复信息”交替执行)。
- 时间片长度由
configTICK_RATE_HZ
定义的系统节拍周期决定。
创建任务后给定的任务优先级,在后续也是可以在任务当中去进行更改的
3.1 实验1
以上的内容都是freertos内部对任务优先级的一些设计内容,接下来猜一下这个代码的结果,能猜出就对任务优先级有大概的了解了。
void vTask1( void *pvParameters )
{/* 任务函数的主体一般都是无限循环 */for( ;; ){/* 打印任务的信息 */printf("T1\r\n");}
}
void vTask2( void *pvParameters )
{/* 任务函数的主体一般都是无限循环 */for( ;; ){/* 打印任务的信息 */printf("T2\r\n");}
}
void vTask3( void *pvParameters )
{const TickType_t xDelay3000ms = pdMS_TO_TICKS( 3000UL );/* 任务函数的主体一般都是无限循环 */for( ;; ){/* 打印任务的信息 */printf("T3\r\n");// 如果不休眠的话, 其他任务无法得到执行vTaskDelay( xDelay3000ms );}
}
int main(void)
{prvSetupHardware();xTaskCreate(vTask1, "Task 1", 1000, NULL, 1, NULL);xTaskCreate(vTask2, "Task 2", 1000, NULL, 1, NULL);xTaskCreate(vTask3, "Task 3", 1000, NULL, 2, NULL);/* 启动调度器 */vTaskStartScheduler();/* 如果程序运行到了这里就表示出错了, 一般是内存不足 */return 0;
}
3.2 实验2:修改任务优先级
使用uxTaskPriorityGet来获得任务的优先级:
UBaseType_t uxTaskPriorityGet( const TaskHandle_t xTask );
- 参数xTask用来获取指定任务的优先级,若是为NULL则获取当前任务的优先级
使用vTaskPrioritySet 来设置任务的优先级:
void vTaskPrioritySet( TaskHandle_t xTask,UBaseType_t uxNewPriority );
- 使用参数xTask来指定任务,设置为NULL表示设置自己的优先级;参数uxNewPriority表示新的优先级,取值范围是0~(configMAX_PRIORITIES – 1)。
下面是示例代码:
void vTask1( void *pvParameters )
{UBaseType_t uxPriority;/* Task1,Task2都不会进入阻塞或者暂停状态*//* 根据优先级决定谁能运行*//* 得到Task1自己的优先级 */uxPriority = uxTaskPriorityGet( NULL );for( ;; ){printf( "Task 1 is running\r\n" );printf("About to raise the Task 2 priority\r\n" );/* 提升Task2的优先级高于Task1 *//* Task2会即刻执行 */vTaskPrioritySet( xTask2Handle, ( uxPriority + 1 ) );/* 如果Task1能运行到这里,表示它的优先级比Task2高/* 那就表示Task2肯定把自己的优先级降低了*/}
}
void vTask2( void *pvParameters )
{UBaseType_t uxPriority;/* Task1,Task2都不会进入阻塞或者暂停状态*//* 根据优先级决定谁能运行*//* 得到Task2自己的优先级 */uxPriority = uxTaskPriorityGet( NULL );for( ;; ){/* 能运行到这里表示Task2的优先级高于Task1 *//* Task1提高了Task2的优先级 */printf( "Task 2 is running\r\n" );printf( "About to lower the Task 2 priority\r\n" );/* 降低Task2自己的优先级,让它小于Task1*//* Task1得以运行*/vTaskPrioritySet( NULL, ( uxPriority - 2 ) );}
}int main( void )
{prvSetupHardware();/* Task1的优先级更高, Task1先执行 */xTaskCreate( vTask1, "Task 1", 1000, NULL, 2, NULL );xTaskCreate( vTask2, "Task 2", 1000, NULL, 1, &xTask2Handle );/* 启动调度器 */vTaskStartScheduler();/* 如果程序运行到了这里就表示出错了, 一般是内存不足 */return 0;
}
其中提到的阻塞或者暂停状态,后续任务的状态中会将讲