任务管理基础
在嵌入式系统中,任务管理是操作系统的核心功能之一。FreeRTOS作为一个轻量级的实时操作系统,提供了丰富的任务管理功能,使得多任务处理变得简单高效。任务管理包括任务的创建、删除、挂起、恢复、优先级设置等操作。在FreeRTOS中,每个任务都有自己的任务控制块(TCB),用于存储任务的状态、优先级、堆栈等信息。
任务控制块(TCB)
任务控制块(Task Control Block, TCB)是FreeRTOS中用于管理任务的数据结构。每个任务在创建时都会分配一个TCB,TCB中包含了任务的所有管理信息,如任务状态、优先级、堆栈指针等。以下是一个TCB的简化结构示例:
// 简化的TCB结构示例
typedef struct TCB {
char *pcTaskName; // 任务名称
UBaseType_t uxPriority; // 任务优先级
StackType_t *pxStack; // 任务堆栈指针
configSTACK_DEPTH_TYPE usStackDepth; // 任务堆栈深度
ListItem_t xStateItem; // 任务状态列表项
ListItem_t xEventListItem; // 任务事件列表项
TickType_t xBlockTime; // 任务阻塞时间
// 其他管理信息
} TCB_t;
任务创建
在FreeRTOS中,任务的创建通过xTaskCreate函数实现。该函数需要提供任务的入口函数、任务名称、堆栈深度、任务参数、任务优先级以及一个指向任务句柄的指针。以下是一个创建任务的示例:
// 任务入口函数
void vTaskFunction(void *pvParameters) {
// 任务名称
char *pcTaskName = (char *)pvParameters;
while (1) {
// 任务主体代码
printf("Task %s running\n", pcTaskName);
vTaskDelay(pdMS_TO_TICKS(1000)); // 延迟1000毫秒
}
}
// 主函数
int main(void) {
// 创建任务
TaskHandle_t xHandle;
xTaskCreate(vTaskFunction, "Task 1", configMINIMAL_STACK_SIZE, (void *)"Task 1", 1, &xHandle);
// 启动调度器
vTaskStartScheduler();
// 应该不会到达这里
for (;;);
}
任务删除
任务的删除通过vTaskDelete函数实现。该函数会释放任务的堆栈和其他资源,并将任务从任务列表中移除。以下是一个删除任务的示例:
// 任务入口函数
void vTaskFunction(void *pvParameters) {
// 任务名称
char *pcTaskName = (char *)pvParameters;
while (1) {
// 任务主体代码
printf("Task %s running\n", pcTaskName);
vTaskDelay(pdMS_TO_TICKS(1000)); // 延迟1000毫秒
// 删除任务
vTaskDelete(NULL);
}
}
// 主函数
int main(void) {
// 创建任务
TaskHandle_t xHandle;
xTaskCreate(vTaskFunction, "Task 1", configMINIMAL_STACK_SIZE, (void *)"Task 1", 1, &xHandle);
// 启动调度器
vTaskStartScheduler();
// 应该不会到达这里
for (;;);
}
任务挂起与恢复
任务可以被挂起(暂停)或恢复。挂起任务通过vTaskSuspend函数实现,恢复任务通过vTaskResume函数实现。以下是一个挂起和恢复任务的示例:
// 任务入口函数
void vTaskFunction(void *pvParameters) {
// 任务名称
char *pcTaskName = (char *)pvParameters;
while (1) {
// 任务主体代码
printf("Task %s running\n", pcTaskName);
vTaskDelay(pdMS_TO_TICKS(1000)); // 延迟1000毫秒
}
}
// 主函数
int main(void) {
// 创建任务
TaskHandle_t xHandle;
xTaskCreate(vTaskFunction, "Task 1", configMINIMAL_STACK_SIZE, (void *)"Task 1", 1, &xHandle);
// 启动调度器
vTaskStartScheduler();
// 挂起任务
vTaskSuspend(xHandle);
// 延迟10秒后恢复任务
vTaskDelay(pdMS_TO_TICKS(10000));
vTaskResume(xHandle);
// 应该不会到达这里
for (;;);
}
任务调度机制
FreeRTOS的任务调度机制是基于优先级的抢占式调度。每个任务都有一个优先级,调度器会根据优先级选择最高优先级的就绪任务来执行。如果多个任务具有相同的优先级,调度器会使用时间片轮转机制来调度这些任务。
优先级调度
优先级调度是FreeRTOS中最基本的调度机制。每个任务在创建时都会指定一个优先级,优先级越高的任务越先被调度执行。FreeRTOS支持的任务优先级范围通常从0到(configMAX_PRIORITIES - 1),其中0是最低优先级。
以下是一个优先级调度的示例:
// 任务1入口函数
void vTask1Function(void *pvParameters) {
while (1) {
printf("Task 1 running\n");
vTaskDelay(pdMS_TO_TICKS(500)); // 延迟500毫秒
}
}
// 任务2入口函数
void vTask2Function(void *pvParameters) {
while (1) {
printf("Task 2 running\n");
vTaskDelay(pdMS_TO_TICKS(1000)); // 延迟1000毫秒
}
}
// 主函数
int main(void) {
// 创建任务1,优先级为1
TaskHandle_t xTask1Handle;
xTaskCreate(vTask1Function, "Task 1", configMINIMAL_STACK_SIZE, NULL, 1, &xTask1Handle);
// 创建任务2,优先级为2
TaskHandle_t xTask2Handle;
xTaskCreate(vTask2Function, "Task 2", configMINIMAL_STACK_SIZE, NULL, 2, &xTask2Handle);
// 启动调度器
vTaskStartScheduler();
// 应该不会到达这里
for (;;);
}
在这个示例中,任务2的优先级高于任务1,因此任务2会优先执行。
时间片轮转
当多个任务具有相同的优先级时,FreeRTOS会使用时间片轮转机制来调度这些任务。每个任务在运行一定时间后会被挂起,调度器会选择下一个就绪的任务来执行。时间片的长度可以通过configTICK_RATE_HZ配置。
以下是一个时间片轮转的示例:
// 任务1入口函数
void vTask1Function(void *pvParameters) {
while (1) {
printf("Task 1 running\n");
vTaskDelay(pdMS_TO_TICKS(500)); // 延迟500毫秒
}
}
// 任务2入口函数
void vTask2Function(void *pvParameters) {
while (1) {
printf("Task 2 running\n");
vTaskDelay(pdMS_TO_TICKS(500)); // 延迟500毫秒
}
}
// 主函数
int main(void) {
// 创建任务1,优先级为1
TaskHandle_t xTask1Handle;
xTaskCreate(vTask1Function, "Task 1", configMINIMAL_STACK_SIZE, NULL, 1, &xTask1Handle);
// 创建任务2,优先级为1
TaskHandle_t xTask2Handle;
xTaskCreate(vTask2Function, "Task 2", configMINIMAL_STACK_SIZE, NULL, 1, &xTask2Handle);
// 启动调度器
vTaskStartScheduler();
// 应该不会到达这里
for (;;);
}
在这个示例中,任务1和任务2具有相同的优先级,因此调度器会使用时间片轮转机制来交替执行这两个任务。
任务优先级的动态调整
FreeRTOS允许在运行时动态调整任务的优先级。可以通过vTaskPrioritySet函数来设置任务的优先级。以下是一个动态调整任务优先级的示例:
// 任务1入口函数
void vTask1Function(void *pvParameters) {
while (1) {
printf("Task 1 running\n");
vTaskDelay(pdMS_TO_TICKS(500)); // 延迟500毫秒
}
}
// 任务2入口函数
void vTask2Function(void *pvParameters) {
while (1) {
printf("Task 2 running\n");
vTaskDelay(pdMS_TO_TICKS(1000)); // 延迟1000毫秒
// 动态提高任务1的优先级
vTaskPrioritySet(xTask1Handle, 2);
}
}
// 主函数
int main(void) {
// 创建任务1,优先级为1
TaskHandle_t xTask1Handle;
xTaskCreate(vTask1Function, "Task 1", configMINIMAL_STACK_SIZE, NULL, 1, &xTask1Handle);
// 创建任务2,优先级为2
TaskHandle_t xTask2Handle;
xTaskCreate(vTask2Function, "Task 2", configMINIMAL_STACK_SIZE, NULL, 2, &xTask2Handle);
// 启动调度器
vTaskStartScheduler();
// 应该不会到达这里
for (;;);
}
在这个示例中,任务2在运行时会动态提高任务1的优先级,使得任务1的优先级高于任务2,从而被优先调度执行。
任务间的同步与通信
在多任务系统中,任务间的同步与通信是非常重要的。FreeRTOS提供了多种机制来实现任务间的同步与通信,包括信号量、互斥量、事件组、消息队列等。
信号量
信号量用于实现任务间的同步。FreeRTOS提供了二值信号量和计数信号量两种类型。二值信号量通常用于任务间的互斥访问,计数信号量用于计数资源的使用情况。
二值信号量
以下是一个使用二值信号量的示例:
// 任务1入口函数
void vTask1Function(void *pvParameters) {
while (1) {
// 等待信号量
xSemaphoreTake(xBinarySemaphore, portMAX_DELAY);
// 任务主体代码
printf("Task 1 running\n");
vTaskDelay(pdMS_TO_TICKS(500)); // 延迟500毫秒
// 释放信号量
xSemaphoreGive(xBinarySemaphore);
}
}
// 任务2入口函数
void vTask2Function(void *pvParameters) {
while (1) {
// 任务主体代码
printf("Task 2 running\n");
vTaskDelay(pdMS_TO_TICKS(1000)); // 延迟1000毫秒
// 释放信号量
xSemaphoreGive(xBinarySemaphore);
}
}
// 主函数
int main(void) {
// 创建二值信号量
xBinarySemaphore = xSemaphoreCreateBinary();
// 创建任务1
TaskHandle_t xTask1Handle;
xTaskCreate(vTask1Function, "Task 1", configMINIMAL_STACK_SIZE, NULL, 1, &xTask1Handle);
// 创建任务2
TaskHandle_t xTask2Handle;
xTaskCreate(vTask2Function, "Task 2", configMINIMAL_STACK_SIZE, NULL, 2, &xTask2Handle);
// 启动调度器
vTaskStartScheduler();
// 应该不会到达这里
for (;;);
}
在这个示例中,任务1和任务2通过二值信号量来同步。任务1在获取信号量后执行,任务2在释放信号量后任务1继续执行。
计数信号量
以下是一个使用计数信号量的示例:
// 任务1入口函数
void vTask1Function(void *pvParameters) {
while (1) {
// 等待信号量
xSemaphoreTake(xCountingSemaphore, portMAX_DELAY);
// 任务主体代码
printf("Task 1 running\n");
vTaskDelay(pdMS_TO_TICKS(500)); // 延迟500毫秒
}
}
// 任务2入口函数
void vTask2Function(void *pvParameters) {
while (1) {
// 任务主体代码
printf("Task 2 running\n");
vTaskDelay(pdMS_TO_TICKS(1000)); // 延迟1000毫秒
// 释放信号量
xSemaphoreGive(xCountingSemaphore);
}
}
// 主函数
int main(void) {
// 创建计数信号量,初始值为0,最大值为1
xCountingSemaphore = xSemaphoreCreateCounting(1, 0);
// 创建任务1
TaskHandle_t xTask1Handle;
xTaskCreate(vTask1Function, "Task 1", configMINIMAL_STACK_SIZE, NULL, 1, &xTask1Handle);
// 创建任务2
TaskHandle_t xTask2Handle;
xTaskCreate(vTask2Function, "Task 2", configMINIMAL_STACK_SIZE, NULL, 2, &xTask2Handle);
// 启动调度器
vTaskStartScheduler();
// 应该不会到达这里
for (;;);
}
在这个示例中,任务1在获取计数信号量后执行,任务2在释放计数信号量后任务1继续执行。
互斥量
互斥量用于保护共享资源,防止多个任务同时访问。以下是一个使用互斥量的示例:
// 任务1入口函数
void vTask1Function(void *pvParameters) {
while (1) {
// 获取互斥量
xSemaphoreTake(xMutex, portMAX_DELAY);
// 任务主体代码
printf("Task 1 running\n");
vTaskDelay(pdMS_TO_TICKS(500)); // 延迟500毫秒
// 释放互斥量
xSemaphoreGive(xMutex);
}
}
// 任务2入口函数
void vTask2Function(void *pvParameters) {
while (1) {
// 获取互斥量
xSemaphoreTake(xMutex, portMAX_DELAY);
// 任务主体代码
printf("Task 2 running\n");
vTaskDelay(pdMS_TO_TICKS(1000)); // 延迟1000毫秒
// 释放互斥量
xSemaphoreGive(xMutex);
}
}
// 主函数
int main(void) {
// 创建互斥量
xMutex = xSemaphoreCreateMutex();
// 创建任务1
TaskHandle_t xTask1Handle;
xTaskCreate(vTask1Function, "Task 1", configMINIMAL_STACK_SIZE, NULL, 1, &xTask1Handle);
// 创建任务2
TaskHandle_t xTask2Handle;
xTaskCreate(vTask2Function, "Task 2", configMINIMAL_STACK_SIZE, NULL, 2, &xTask2Handle);
// 启动调度器
vTaskStartScheduler();
// 应该不会到达这里
for (;;);
}
在这个示例中,任务1和任务2通过互斥量来保护共享资源,确保同一时间只有一个任务在访问资源。
事件组
事件组用于实现任务之间的复杂同步机制。每个事件组可以包含多个事件位,任务可以通过设置或清除这些事件位来进行同步。以下是一个使用事件组的示例:
// 任务1入口函数
void vTask1Function(void *pvParameters) {
while (1) {
// 等待事件组中的某个事件
EventBits_t uxBits = xEventGroupWaitBits(
xEventGroup, // 事件组句柄
0x01, // 等待的事件位
pdTRUE, // 清除事件位
pdFALSE, // 仅等待指定的事件位
portMAX_DELAY // 等待时间
);
// 任务主体代码
printf("Task 1 running\n");
vTaskDelay(pdMS_TO_TICKS(500)); // 延迟500毫秒
}
}
// 任务2入口函数
void vTask2Function(void *pvParameters) {
while (1) {
// 任务主体代码
printf("Task 2 running\n");
vTaskDelay(pdMS_TO_TICKS(1000)); // 延迟1000毫秒
// 设置事件组中的某个事件
xEventGroupSetBits(xEventGroup, 0x01);
}
}
// 主函数
int main(void) {
// 创建事件组
xEventGroup = xEventGroupCreate();
// 创建任务1
TaskHandle_t xTask1Handle;
xTaskCreate(vTask1Function, "Task 1", configMINIMAL_STACK_SIZE, NULL, 1, &xTask1Handle);
// 创建任务2
TaskHandle_t xTask2Handle;
xTaskCreate(vTask2Function, "Task 2", configMINIMAL_STACK_SIZE, NULL, 2, &xTask2Handle);
// 启动调度器
vTaskStartScheduler();
// 应该不会到达这里
for (;;);
}
在这个示例中,任务1在等待事件组中的某个事件位被设置后执行,任务2在运行时设置事件组中的某个事件位。
消息队列
消息队列用于任务间的通信。任务可以通过消息队列发送和接收消息。以下是一个使用消息队列的示例:
// 任务1入口函数
void vTask1Function(void *pvParameters) {
while (1) {
// 发送消息到队列
int message = 1;
xQueueSend(xQueue, &message, portMAX_DELAY);
// 任务主体代码
printf("Task 1 running, sent message: %d\n", message);
vTaskDelay(pdMS_TO_TICKS(500)); // 延迟500毫秒
}
}
// 任务2入口函数
void vTask2Function(void *pvParameters) {
while (1) {
// 从队列接收消息
int message;
if (xQueueReceive(xQueue, &message, portMAX_DELAY)) {
// 任务主体代码
printf("Task 2 running, received message: %d\n", message);
}
vTaskDelay(pdMS_TO_TICKS(1000)); // 延迟1000毫秒
}
}
// 主函数
int main(void) {
// 创建消息队列,队列长度为10,每个消息的大小为4字节
xQueue = xQueueCreate(10, sizeof(int));
// 创建任务1
TaskHandle_t xTask1Handle;
xTaskCreate(vTask1Function, "Task 1", configMINIMAL_STACK_SIZE, NULL, 1, &xTask1Handle);
// 创建任务2
TaskHandle_t xTask2Handle;
xTaskCreate(vTask2Function, "Task 2", configMINIMAL_STACK_SIZE, NULL, 2, &xTask2Handle);
// 启动调度器
vTaskStartScheduler();
// 应该不会到达这里
for (;;);
}
在这个示例中,任务1通过消息队列发送消息,任务2从消息队列接收消息。任务1和任务2通过消息队列进行通信,确保任务2在接收到消息后执行相应的操作。
任务调度与调度器配置
FreeRTOS的调度器负责管理任务的执行顺序和时间。调度器的配置可以通过修改FreeRTOSConfig.h文件中的宏定义来实现。以下是一些重要的配置选项:
配置选项
configMAX_PRIORITIES: 定义系统支持的最大优先级数。默认值为5,可以根据需要调整。
configTICK_RATE_HZ: 定义系统时钟的频率,单位为Hz。例如,设置为1000表示每秒1000个时钟滴答。
configMINIMAL_STACK_SIZE: 定义系统中最小的堆栈大小。不同平台和任务的需求可能不同,可以根据实际情况调整。
configUSE_PREEMPTION: 启用或禁用抢占式调度。启用抢占式调度可以提高系统的实时性。
调度器启动与运行
调度器通过vTaskStartScheduler函数启动。启动后,调度器会根据任务的优先级和状态来调度任务。调度器的运行依赖于系统时钟中断,通过时钟中断来触发任务的切换。
// 主函数
int main(void) {
// 创建任务1
TaskHandle_t xTask1Handle;
xTaskCreate(vTask1Function, "Task 1", configMINIMAL_STACK_SIZE, NULL, 1, &xTask1Handle);
// 创建任务2
TaskHandle_t xTask2Handle;
xTaskCreate(vTask2Function, "Task 2", configMINIMAL_STACK_SIZE, NULL, 2, &xTask2Handle);
// 启动调度器
vTaskStartScheduler();
// 如果调度器启动失败,会到达这里
for (;;);
}
调度器的停止
在某些情况下,可能需要停止调度器。可以通过vTaskEndScheduler函数来停止调度器。需要注意的是,停止调度器后,系统将不再调度任何任务,所有任务都会停止执行。
// 任务1入口函数
void vTask1Function(void *pvParameters) {
while (1) {
// 任务主体代码
printf("Task 1 running\n");
vTaskDelay(pdMS_TO_TICKS(500)); // 延迟500毫秒
// 停止调度器
vTaskEndScheduler();
}
}
// 任务2入口函数
void vTask2Function(void *pvParameters) {
while (1) {
// 任务主体代码
printf("Task 2 running\n");
vTaskDelay(pdMS_TO_TICKS(1000)); // 延迟1000毫秒
}
}
// 主函数
int main(void) {
// 创建任务1
TaskHandle_t xTask1Handle;
xTaskCreate(vTask1Function, "Task 1", configMINIMAL_STACK_SIZE, NULL, 1, &xTask1Handle);
// 创建任务2
TaskHandle_t xTask2Handle;
xTaskCreate(vTask2Function, "Task 2", configMINIMAL_STACK_SIZE, NULL, 2, &xTask2Handle);
// 启动调度器
vTaskStartScheduler();
// 应该不会到达这里
for (;;);
}
在这个示例中,任务1在运行一段时间后会调用vTaskEndScheduler函数来停止调度器,所有任务将不再执行。
任务调度的性能优化
为了提高系统的实时性和性能,FreeRTOS提供了一些优化机制和技巧。以下是一些常见的性能优化方法:
优先级继承
优先级继承是一种防止优先级反转的机制。当一个低优先级任务持有互斥量,而高优先级任务需要访问该互斥量时,低优先级任务的优先级会被临时提高到高优先级任务的优先级,以加快互斥量的释放。
// 任务1入口函数
void vTask1Function(void *pvParameters) {
while (1) {
// 获取互斥量
xSemaphoreTake(xMutex, portMAX_DELAY);
// 任务主体代码
printf("Task 1 running\n");
vTaskDelay(pdMS_TO_TICKS(500)); // 延迟500毫秒
// 释放互斥量
xSemaphoreGive(xMutex);
}
}
// 任务2入口函数
void vTask2Function(void *pvParameters) {
while (1) {
// 获取互斥量
xSemaphoreTake(xMutex, portMAX_DELAY);
// 任务主体代码
printf("Task 2 running\n");
vTaskDelay(pdMS_TO_TICKS(1000)); // 延迟1000毫秒
// 释放互斥量
xSemaphoreGive(xMutex);
}
}
// 任务3入口函数
void vTask3Function(void *pvParameters) {
while (1) {
// 获取互斥量
xSemaphoreTake(xMutex, portMAX_DELAY);
// 任务主体代码
printf("Task 3 running\n");
vTaskDelay(pdMS_TO_TICKS(1500)); // 延迟1500毫秒
// 释放互斥量
xSemaphoreGive(xMutex);
}
}
// 主函数
int main(void) {
// 创建互斥量
xMutex = xSemaphoreCreateMutex();
// 创建任务1,优先级为1
TaskHandle_t xTask1Handle;
xTaskCreate(vTask1Function, "Task 1", configMINIMAL_STACK_SIZE, NULL, 1, &xTask1Handle);
// 创建任务2,优先级为2
TaskHandle_t xTask2Handle;
xTaskCreate(vTask2Function, "Task 2", configMINIMAL_STACK_SIZE, NULL, 2, &xTask2Handle);
// 创建任务3,优先级为3
TaskHandle_t xTask3Handle;
xTaskCreate(vTask3Function, "Task 3", configMINIMAL_STACK_SIZE, NULL, 3, &xTask3Handle);
// 启动调度器
vTaskStartScheduler();
// 应该不会到达这里
for (;;);
}
在这个示例中,任务3的优先级最高,任务1的优先级最低。当任务1持有互斥量,任务3需要访问该互斥量时,任务1的优先级会被临时提高到任务3的优先级,以加快互斥量的释放。
空闲任务和内存管理
FreeRTOS提供了一个空闲任务(Idle Task),它在没有其他任务可运行时执行。空闲任务通常用于处理系统空闲时的一些后台任务,如内存管理。FreeRTOS还支持动态内存分配和静态内存分配,可以根据系统的实际需求选择合适的内存管理方式。
动态内存分配
动态内存分配使用pvPortMalloc和vPortFree函数来分配和释放内存。动态内存分配提供了灵活性,但可能会导致内存碎片。
// 任务入口函数
void vTaskFunction(void *pvParameters) {
while (1) {
// 动态分配内存
char *pcMessage = (char *)pvPortMalloc(100 * sizeof(char));
if (pcMessage != NULL) {
sprintf(pcMessage, "Task %s running", (char *)pvParameters);
printf("%s\n", pcMessage);
// 释放内存
vPortFree(pcMessage);
}
vTaskDelay(pdMS_TO_TICKS(1000)); // 延迟1000毫秒
}
}
// 主函数
int main(void) {
// 创建任务
TaskHandle_t xHandle;
xTaskCreate(vTaskFunction, "Task 1", configMINIMAL_STACK_SIZE, (void *)"Task 1", 1, &xHandle);
// 启动调度器
vTaskStartScheduler();
// 应该不会到达这里
for (;;);
}
静态内存分配
静态内存分配使用预先分配的内存区域来创建任务。静态内存分配可以避免内存碎片,但需要在创建任务时提供内存区域。
// 任务入口函数
void vTaskFunction(void *pvParameters) {
while (1) {
// 任务主体代码
printf("Task %s running\n", (char *)pvParameters);
vTaskDelay(pdMS_TO_TICKS(1000)); // 延迟1000毫秒
}
}
// 主函数
int main(void) {
// 预先分配的任务堆栈和TCB
StaticTask_t xTaskBuffer;
StackType_t xStack[configMINIMAL_STACK_SIZE];
// 创建任务
TaskHandle_t xHandle = xTaskCreateStatic(
vTaskFunction, // 任务入口函数
"Task 1", // 任务名称
configMINIMAL_STACK_SIZE, // 堆栈深度
(void *)"Task 1", // 任务参数
1, // 任务优先级
xStack, // 任务堆栈
&xTaskBuffer // 任务控制块
);
// 启动调度器
vTaskStartScheduler();
// 应该不会到达这里
for (;;);
}
在这个示例中,任务1的堆栈和TCB是在创建任务时预先分配的,使用xTaskCreateStatic函数创建任务,可以避免动态内存分配带来的内存碎片问题。
总结
FreeRTOS的多任务管理与调度功能非常强大,提供了丰富的API来创建、删除、挂起、恢复任务,以及设置任务的优先级。通过使用信号量、互斥量、事件组和消息队列,可以实现任务间的同步与通信。合理配置调度器和优化内存管理方式,可以提高系统的实时性和性能。理解这些基本概念和机制,有助于开发高效、可靠的嵌入式实时系统。
————————————————
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/weixin_42749425/article/details/142647388
|
|