由于正在学习韦东山大佬的RTOS课程,结合了网上的一些资料,整理记录了下自己的感悟,用于以后自己的回顾。如有不对的地方请各位大佬纠正。
课程链接:https://www.bilibili.com/video/BV1844y1g7ud/?spm_id_from=333.337.search-card.all.click&vd_source=c48fed208dddb3dfb2649420bfdd32d9
一、RTOS的优势【为啥学习RTOS】
①:确定性和实时性:
RTOS的最大特点是能够在严格的时间约束内完成任务。这种确定性对于时间敏感的应用(如工业控制、医疗设备等)至关重要。
②:优先级调度:
RTOS通常支持优先级调度机制,确保高优先级的任务可以抢占低优先级的任务执行。这种机制保证了关键任务能够在最短时间内得到处理。
③:低延迟和高响应性:
RTOS设计的目标是最小化任务切换时间和中断延迟,从而实现高响应性。这在需要快速反应的嵌入式系统中非常重要。
④:资源管理和内存控制:
RTOS通常提供精细的资源管理工具,允许开发者更好地控制内存和CPU资源的使用。这种控制对于嵌入式系统中的资源有限环境尤其重要。
⑤:模块化和灵活性:
RTOS通常具有模块化设计,允许开发者根据具体需求启用或禁用特定的功能模块。这种灵活性有助于优化系统性能和减少系统开销。
⑥:可靠性和稳定性:
RTOS被广泛应用于需要高可靠性和稳定性的系统中,例如自动驾驶、军事系统等。RTOS通过严格的测试和验证,确保其在各种边界情况下都能稳定运行。
⑦:较小的内存占用:
RTOS通常占用的内存和资源较少,这使得它非常适合嵌入式系统或其他资源受限的环境。
二、RTOS的核心功能
RTOS的核心功能块主要分为任务管理、内核管理、时间管理以及通信管理4部分,框架图如下所示:
(1)任务管理:负责管理和调度任务的执行,确保系统中的任务能够按照预期运行。
(2)内核管理:负责系统核心功能的管理,包括内存、中断、异常处理和系统启动等。
(3)时间管理:负责所有与时间相关的操作,包括系统时钟、定时器、任务延迟和周期性任务的执行。
(4)通信管理:提供任务之间的通信机制,确保任务能够有效地协作和共享资源。
2.1 任务管理
2.1.1 任务的创建
任务就是一个无返回的函数(Void)。由于函数传参的不同,一个函数可以创建多个任务,然后每个任务都有对应自身的栈,也就是说一个函数可以有多个栈(当然一个函数对应一个栈也是可以的)。使用下面的函数用于创建任务:
void TaskAFunction(void *param)
{
int* tmp = (int*) param;//首先将void *指针类型的param转为int *类型的指针
int value = *tmp; //然后解引用来获取指针指向的值
while(1)
{
printf("%d",value);
}
}
尽管是同一个函数,但是创建的多个任务主要不同还是在于传参而不是名字,下面的代码使用了相同的名字(“TaskA”)创建了三个参数不同的任务。
int x1=1;int x2=2;int x3=3;
int main( void )
{
TaskHandle_t xHandleTask1;
#ifdef DEBUG
debug();
#endif
prvSetupHardware();
printf("Hello, world!\r\n");
xTaskCreate(TaskAFunction,"TaskA",100,&x1,1,NULL);
xTaskCreate(TaskAFunction,"TaskA",100,&x2,1,NULL);
xTaskCreate(TaskAFunction,"TaskA",100,&x3,1,NULL);
/* Start the scheduler. */
vTaskStartScheduler();
/* Will only get here if there was not enough heap space to create the
idle task. */
return 0;
}
2.1.1 xTaskCreate
上面使用的xTaskCreate是动态创建任务的,当然还有静态创建任务的函数xTaskCreateStatic,后面再提静态创建。下图为xTaskCreate函数的参数及介绍:
下图摘自韦东山的FreeRTOS完全开发手册3.2.2节
2.1.2 任务的删除
任务的删除使用如下函数,其中填入的参数如果是NULL表示自杀,如果是自己的句柄则是被杀,别人的句柄就是杀人。
void vTaskDelete( TaskHandle_t xTaskToDelete );
基础实验如下,在vTask1任务中嵌套vTask2任务的创建,并vTask2任务中进行自杀,所以xTask2Handle设置为NULL。
TaskHandle_t xTask2Handle = NULL;
void vTask1( void *pvParameters )
{
const TickType_t xDelay100ms = pdMS_TO_TICKS( 100UL );//100ms的延时
BaseType_t ret;
/* 任务函数的主体一般都是无限循环 */
for( ;; )
{
/* 打印任务1的信息 */
printf("Task1 is running\r\n");
ret = xTaskCreate( vTask2, "Task 2", 1000, NULL, 2, &xTask2Handle );
if (ret != pdPASS)//判断vTask2是否创建成功,一般pdPASS默认为1
printf("Create Task2 Failed\r\n");
vTaskDelay( xDelay100ms );
}
}
void vTask2( void *pvParameters )
{
/* 打印任务的信息 */
printf("Task2 is running and about to delete itself\r\n");
// 可以直接传入参数NULL,进行“自杀”
vTaskDelete(xTask2Handle);
}
int main( void )
{
#ifdef DEBUG
debug();
#endif
prvSetupHardware();
xTaskCreate(vTask1, "Task 1", 1000, NULL, 1, NULL);
/* Start the scheduler. */
vTaskStartScheduler();
/* Will only get here if there was not enough heap space to create the
idle task. */
return 0;
}
其中vTask2任务的优先级为2,高于vTask1的优先级1(这里是数字大优先级高)。实验结果如下,实现现象为首先打印vTask1的printf,然后执行优先级高的vTask2。然后vTask2会打印自身内容并进行自杀退出vTask2循环中,最后又回到vTask1这样来回交替循环。
其中vTaskDelay( xDelay100ms );
的作用在于保证在Idle任务时有时间执行来保证释放创建任务vTask2时分配的内存。如果没有这一行的延时会导致释放vTask2的内存失败而导致内存耗尽,最终导致无法创建新的任务。删除vTaskDelay( xDelay100ms );
的结果如下:
2.2 内存管理
源码中对应了5种内存管理的方法,如下所示。不过在了解这5种方式前,需要对TCB和Stack有个认识。
(1)TCB:即任务控制块,是操作系统用来管理每个任务的信息结构体。每个任务(或线程)在RTOS中都会有一个对应的TCB。TCB中存储了任务的所有关键信息,用于操作系统对任务的调度、状态管理和上下文切换。通常TCB包含如①任务ID:每个任务的唯一标识符。②任务状态:例如就绪、运行、阻塞、挂起等状态。③程序计数器(PC):保存任务执行的当前指令位置。④寄存器状态:保存任务执行时CPU的寄存器值。⑤任务优先级:用于任务调度中的优先级信息。⑥任务栈指针:指向任务的栈空间,保存任务的栈信息。⑦任务上下文信息:保存任务切换时的上下文信息,如寄存器内容等。
(2)Stack:即栈, 是每个任务用来存储其运行时局部变量、函数调用链和返回地址等信息的内存空间。对于RTOS,每个任务通常都有自己独立的栈空间,这样可以确保任务之间的执行互不干扰。