FreeRTOS任务之使用篇

article/2025/7/20 5:56:25

目录

  • 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:修改任务优先级

img
基础的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_tunsigned int 类型。
  • 定义任务的优先级。FreeRTOS 中的任务是基于优先级调度的,优先级值越小,优先级越低。有效优先级范围是 0configMAX_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中的文件:

img

img

实现函数

img

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

以上都是基于同个优先级的情况下的,同优先级的任务表面上是并行,实际上却是交叉运行的

img

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和栈。可以尝试去不断轮流调用xTaskCreatevTaskDelete,最终的结果是内存被消耗殆尽(自杀的情况下

调用 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)。
  • 适用于优先级数量 ≤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;
}

img

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;
}

img

其中提到的阻塞或者暂停状态,后续任务的状态中会将讲


http://www.hkcw.cn/article/tPGlNUuZih.shtml

相关文章

K3s简介、实战、问题记录

概述 K3s由Rancher Labs开发&#xff0c;是一个开源的&#xff0c;轻量级的Kubernetes&#xff08;下文简称k8s&#xff09;发行版&#xff0c;专为边缘计算、IoT和资源受限环境设计&#xff1b;保留k8s核心功能&#xff0c;并去掉部分非必要组件。 官网&#xff0c;中文文档…

【Unity博客节选】Timeline 内部结构 IntervalTree 分析

注&#xff1a;软件版本Unity 6.0 Timeline 1.8.7 作者&#xff1a;CSDN RingleaderWang 原文&#xff1a;《Unity第25期——Timeline结构及其源码浅析》 文章首发Github&#x1f44d;&#xff1a;《Timeline结构及其源码浅析》 Bilibili 视频版&#x1f44d;&#x1f44d;&a…

太原一路虎车横冲直撞 路人纷纷避让 危险驾驶引热议

今天刷手机时看到一段让人揪心的视频。6月2日凌晨,太原南中环花海酒吧门口,一辆路虎越野车失控冲撞。车辆先是猛烈撞击酒吧大门,停顿片刻后又朝人群冲去,吓得周围人四处躲避。有个穿黑衣服的小伙子试图上前拉开车门,结果被晃得一个趔趄。据现场网友说,事发时间大约是凌晨…

吃不起的玉米蛋挞到底谁在买 轻奢甜品引争议

“我亲手种的苞谷,终究成了我吃不起的玉米蛋挞。”这几天,许多短视频展示了从玉米地到高价烘焙品玉米蛋挞的转场画面,台词表达了消费者对高价烘焙品的不满。一块名为玉米蛋挞的网红甜品引发了广泛关注。在产地,一斤玉米的收购价大约在一元上下,但在烘焙店里,一枚玉米蛋挞…

泡泡玛特市值首超三丽鸥 登顶亚洲角色经济榜首

2025年6月2日,中国潮玩巨头泡泡玛特迎来重要时刻。截至当日收盘,公司总市值突破2500亿港元,首次超越日本IP巨头三丽鸥,登顶亚洲角色经济市值榜首。这一成就主要归功于泡泡玛特以自有IP Labubu为核心的全球爆火以及海外市场同比超475%的爆发式增长。公司计划年内将海外门店数…

哈佛演讲的中国女生否认走后门入学 回应争议自述经历

哈佛大学毕业典礼上,中国学生蒋雨融的演讲引起了广泛关注。6月2日凌晨,一个名为“哈佛蒋雨融Luanna”的账号发文回应了争议。她提到自己从小父母离婚分居,跟随母亲四处搬家转学。在初中时曾遭受霸凌,只能通过阅读书籍来寻找安慰和答案。在美国求学期间,她的推荐信分别来自…

PostgreSQL 性能问题诊断:锁等待、索引失效与查询计划分析

在高并发、大数据量的 PostgreSQL 应用场景中,锁等待、索引失效与查询计划异常是导致性能下降的三大核心问题。 本文将系统解析如何通过 pg_locks、pg_stat_activity 和 EXPLAIN ANALYZE 等工具快速诊断问题,并结合实战案例与代码示例,帮助开发者构建完整的性能调优知识体系…

3D可视化/元宇宙方向前端岗位30道Three.js高频面试题及解析

文章目录 一、核心概念二、对象与材质三、动画与交互四、性能优化五、高级渲染六、加载与资源七、工程实践八、特效实现九、调试与问题十、综合应用 一、核心概念 Three.js三大核心组件及作用 解析&#xff1a; 场景&#xff08;Scene&#xff09;&#xff1a;容器&#xff0c;…

食养有方:进行性核上性麻痹患者的健康饮食指南

进行性核上性麻痹&#xff08;PSP&#xff09;是一种罕见的神经系统变性疾病&#xff0c;患者常出现吞咽困难、肌肉强直等症状&#xff0c;因此合理的饮食对于维持患者营养状况、延缓病情发展至关重要。 患者饮食应遵循 “均衡、易消化、高营养” 原则。主食方面&#xff0c;可…

瘦了不少!姚明进行恢复训练曝光 一脸笑容被调侃“亚洲杯复出”

北京时间6月2日,有博主爆料,给儿时的偶像训练引发外界的热议,特别是姚明身型变化相对比较明显,几乎是瘦了一圈,身穿运动套装一脸笑容,在过去几年极为罕见,姚明几乎每一次公开场合,或者是私下的活动都是身穿正装为主。有球迷调侃说道:姚明要复出吗,这不是亚洲杯马上要…

CodeTop100 Day19

55、从前序与中序遍历序列构造二叉树 前序是根左右&#xff0c;中序是左根右 写一个递归函数&#xff0c;build&#xff08;preorder,0,preorder.length-1,inorder,0,inorder.length-1&#xff09;;接收的参数是先序遍历数组&#xff0c;起始位置&#xff0c;长度&#xff0c;…

又要热热热回来了 多地将迎持续高温

华北和黄淮地区本周将迎来更多高温天气。明天,河北南部和河南北部将出现零星高温,随后高温范围会逐渐扩大。石家庄、济南和郑州等地预计本周会有连续3到5天的高温天气。从5日起,随着副热带高压加强并向北移动,南方地区的高温也将增加。武汉、杭州和长沙在6日预报了高温,如…

如何利用自动生成文档工具打造出色的技术文档

文章目录 每日一句正能量前言一、自动生成文档工具的优势&#xff08;一&#xff09;提高效率&#xff08;二&#xff09;保持一致性&#xff08;三&#xff09;实时更新 二、常见的自动生成文档工具&#xff08;一&#xff09;Sphinx&#xff08;二&#xff09;Javadoc&#x…

张雪峰称或告别直播:动了太多人蛋糕 真情流露引共鸣

5月31日晚,有网友发布视频称张雪峰2025届高考志愿填报季直播结束。直播中,张雪峰提到:“做这行不容易,触动了太多人的利益,有些话不能说得太直白。我们计划8月份恢复直播,希望8月1日能和大家见面,如果不行的话,9月1日也可以,但也有可能这是你最后一次在网上见到我,我…

[蓝桥杯]剪格子

剪格子 题目描述 如下图所示&#xff0c;3 x 3 的格子中填写了一些整数。 我们沿着图中的红色线剪开&#xff0c;得到两个部分&#xff0c;每个部分的数字和都是 60。 本题的要求就是请你编程判定&#xff1a;对给定的 mnmn 的格子中的整数&#xff0c;是否可以分割为两个部…

中国援喀麦隆医疗队开展义诊活动 情暖六一关爱健康

当地时间5月30日至6月1日,中国(山西)第24批援喀麦隆姆巴尔马尤医疗队在喀麦隆杜阿拉开展了一系列健康公益活动,通过专业医疗服务和人文关怀为中非友谊注入新活力。5月30日,医疗队在中企杜阿拉项目园区进行了健康知识宣讲。医生们通过理论讲解与实操演示,系统培训了AED(自…

【连载22】基础智能体的进展与挑战综述-超对齐

21. 超对齐与人工智能智能体中的安全扩展法则 21.1 超对齐&#xff1a;面向目标的人工智能智能体对齐 随着大规模语言模型&#xff08;LLMs&#xff09;越来越多地成为自主智能体决策的核心&#xff0c;确保它们的输出保持安全、伦理&#xff0c;并始终与人类目标一致&#x…

【技术追踪】InverseSR:使用潜在扩散模型进行三维脑部 MRI 超分辨率重建(MICCAI-2023)

LDM 实现三维超分辨率~ 论文&#xff1a;InverseSR: 3D Brain MRI Super-Resolution Using a Latent Diffusion Model 代码&#xff1a;https://github.com/BioMedAI-UCSC/InverseSR 0、摘要 从研究级医疗机构获得的高分辨率&#xff08;HR&#xff09;MRI 扫描能够提供关于成像…

美科罗拉多州恐袭嫌疑人曝光 FBI定性恐袭

当地时间6月1日,美国科罗拉多州博尔德市一名男子向人群投掷燃烧瓶,造成6人烧伤。受害者年龄在67至88岁之间,均已送医。嫌疑人确认是45岁的穆罕默德苏莱曼,案发后亦因伤入院。FBI局长卡什帕特尔称该事件为“有针对性的恐怖袭击”,并指出FBI已将此案按恐袭处理。副局长丹邦吉…

成都90后小伙让刀剑重获新生 十年磨一剑

在成都邛崃郊外的一间工作室里,一把锈迹斑斑的古刀静静地躺在工作台上。王一凯戴上手套,拿起磨石,开始了又一个漫长的工作日。磨石与刀身接触,发出“嗤嗤”的摩擦声。褐色锈层缓缓剥落,千年前的钢铁本色渐渐显露。这把沉睡已久的古刀,在他手中慢慢苏醒。1991年出生的王一…