由于正在学习韦东山老师的RTOS课程,结合了网上的一些资料,整理记录了下自己的感悟,用于以后自己的回顾。如有不对的地方请各位大佬纠正。
 
文章目录
- 一、RTOS的优势
 - 二、RTOS的核心功能
 - 2.1 任务管理
 - 2.1.1 任务的创建
 - 2.1.2 任务的删除*
 - 2.1.3 任务优先级和Tick
 - 一、优先级
 - 二、Tick(滴答)
 - 三、优先级的实验
 - 三、优先级设定的实验
 
- 2.1.4 任务状态
 - 一、阻塞状态(Blocked)
 - 二、就绪状态(Ready)
 - 三、暂停状态(Suspended)
 - 四、完整的状态转移图
 
- 2.1.5 相对延时和绝对延时
 - 一、相对延时函数
 - 二、绝对延时函数
 - 三、延时实验
 
- 2.1.6 空闲任务及钩子函数
 - 一、钩子函数
 
- 2.1.7 调度算法
 - 一、调度算法的配置
 
一、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任务中执行删除自身任务的操作,而相对延时函数vTaskDelay( xDelay100ms );在Task1中的存在与否会有影响么呢?
    代码如下所示,肯定先创建并运行Task1,执行完自身的printf后,创建并优先调用Task2,Task2也会printf自身信息并删除自己的任务(内存还未释放)。此时就要注意了vTaskDelay函数会起了一个很重要的作用。因为vTaskDelay的存在使得Task1进入了阻塞状态,此时没有其他任务(Task2也被删除啦)需要执行,导致系统会执行优先级最低的IDLE任务,这个任务会把Task2所占用栈的内存给释放。代码如下
TaskHandle_t xTask2Handle = NULL;
int main( void )
{
	...
	xTaskCreate(vTask1, "Task 1", 1000, NULL, 1, 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);
}
 
   实验结果如下,Task1带有相对延时函数后,能够正常释放被删除的Task2所占用的内存空间,所以能够如下打印:
 
   通过上文我们知道vTaskDelay函数会起一个很重要的作用。此刻若是删除这个函数的话,Task1自然不会进入阻塞状态而系统更没机会调用IDLE任务,多次被删除的Task2任务所占用的内存一直无法释放而导致最后内存的耗尽,结果如下。
 
2.1.3 任务优先级和Tick
一、优先级
  优先级在上文中提过,优先级的值大的优先执行,相同优先级的则交替执行,这个函数xTaskCreate(vTask1, "Task 1", 1000, NULL, 1, NULL);的第5个参数则是表示优先级。
   如何找到优先级最高的任务,RTOS的调度器会根据configMAX_PRIORITIES的值来判断采用C函数还是汇编指令的方法来实现调度。
二、Tick(滴答)
  函数vTaskDelay可以用于指定任务休眠的时间,一般有以下两种表示方式:
   方式一:vTaskDelay(5)【存在延时不准的问题】
     该方式直接设置5个Tick,根据下面公式可以算出时间T为:
         T=(1/configTICK_RATE_HZ)*5=0.05s=50ms
   方式二:vTaskDelay(pdMS_TO_TICKS(50UL))【存在延时不准的问题】
     该方式采用pdMS_TO_TICKS宏直接将ms转换为tick,上式表示为等待50ms。
三、优先级的实验
参考韦东山FreeRTOS手册,创建了3个任务,其中Task1和Task2的优先级为1,Task3的优先级为2。我们知道Task3任务优先级明显高于Task1和Task2的,但是如果不对Task3进行进行vTaskDelay的话,高优先级的会一直占用CPU,那么Task1和Task2的则不会有机会执行(就像备胎一样,一直在当女神的备胎,但是在女神眼里就是没正主优先级高,备胎就算等着舔不到女神,说明不要当舔狗,不过这也对应了任务的阻塞状态)。Task1~3的代码和main代码如下:
xTaskCreate(vTask1,"Task1",1000,NULL,1,NULL);
xTaskCreate(vTask2,"Task2",1000,NULL,1,NULL);
xTaskCreate(vTask3,"Task3",1000,NULL,2,NULL);
void vTask1( void *pvParameters )
{
	/* 任务函数的主体一般都是无限循环 */
	for( ;; )
	{
		/* 打印任务1的信息 */
		printf("T1\r\n");
	}
}
void vTask2( void *pvParameters )
{
	/* 任务函数的主体一般都是无限循环 */
	for( ;; )
	{
		/* 打印任务2的信息 */
		printf("T2\r\n");
	}
}
void vTask3( void *pvParameters )
{
	const TickType_t xDelay3000ms=pdMS_TO_TICKS(1000UL);
	/* 任务函数的主体一般都是无限循环 */
	for( ;; )
	{
		/* 打印任务3的信息 */
		printf("T3\r\n");
//		vTaskDelay(xDelay3000ms);
	}
 
  结果如下,只执行了Task3
 
   当解开vTask3函数中vTaskDelay(xDelay3000ms);代码的注释后,结果如下。Task3只执行1次后就不执行了,后面是Task1和Task2两个优先级为1的相互执行。那是因为Task3执行到vTaskDelay这个函数后会进入休眠状态,尽管优先级高于Task1和2,但是休眠状态不占用CPU资源,于是让给了两个优先级相同的Task1和Task2,而Task3休眠结束后,Task1和Task2没有休眠机制于是疯狂不断运行从而导致Task3的打印只出现了一次。
 
三、优先级设定的实验
  本实验主要是通过vTaskPrioritySet函数实现对任务优先级的设定。该函数具体如下:
void vTaskPrioritySet(TaskHandle_t xTask, UBaseType_t uxNewPriority);
 
  其中第一个参数是也就是对应Task的handle,即每个任务在xTaskCreate创建任务时所传入的第6个参数xTask2Handle。而第二个参数uxNewPriority是通过函数uxTaskPriorityGet进行获取。
 
   完整的实验如下,创建Task1和Task2。在Task1中print,并提高Task2的任务优先级来保证高于Task1。在Task2中同样print自己内容,并降低Task2的任务优先级来保证低于Task1。这样很明显两者通过调整任务优先级来实现一个来回执行的效果,代码如下:
void vTask1( void *pvParameters )
{
	UBaseType_t uxPriority;
	//获取Task1的优先级,其中NULL表示获取自身的优先级
	uxPriority = uxTaskPriorityGet(NULL);
	/* 任务函数的主体一般都是无限循环 */
	for( ;; )
	{
		/* 打印任务1的信息 */
		printf("Task1 is runing\r\n");
		printf("About to raise the Task 2 priority\r\n");
		/*通过使用vTask1的优先级再+1,来保证vTask2具有更高的优先级,*/
		vTaskPrioritySet( xTask2Handle, ( uxPriority + 1 ) );
	}
}
void vTask2( void *pvParameters )
{
	UBaseType_t uxPriority;
	//获取Task2的优先级,其中NULL表示获取自身的优先级
	uxPriority = uxTaskPriorityGet(NULL);
	/* 任务函数的主体一般都是无限循环 */
	for( ;; )
	{
		/* 打印任务2的信息 */
		printf("Task2 is runing\r\n");
		printf("About to lower the Task 2 priority\r\n");
		/*通过使用vTask2的优先级再-2,来保证vTask1具有更低的优先级,*/
		vTaskPrioritySet(NULL,(uxPriority - 2));
	}
}
 
  经代码验证,Task1与Task2的效果如下:
 
2.1.4 任务状态
任务一般可以分为运行(Runing)和非运行(不 Runing)两类。但是非运行的状态还能分成:①阻塞状态;②暂停状态;③就绪状态。
一、阻塞状态(Blocked)
阻塞状态,指的是任务因为等待某个事件或条件发生而无法继续执行的状态。如(1)相对/绝对延时函数这类时间等待;(2)队列或信号量等待;(3)事件标志等待。等等。这个状态下任务不会占用CPU资源,一旦满足某个事件的条件,就能转为就绪状态了。
二、就绪状态(Ready)
  就绪状态,即随时准备响应调度器的号召,可以由阻塞状态转换而成。就像女神会择优选择好的备胎来处一样,调度器也会选择优先级最高且就绪(Ready)的任务来运行。
   优先级最高好理解,就绪状态是怎么由阻塞状态转过来的呢?这个就涉及到了事件的概念,时间一般包含两类:(1)时间相关事件;(2)同步事件。/* 同步事件的具体概念后面学习内容会涉及 */
   (1)时间相关事件:即设定一定的时间,这个时间内会处于阻塞状态,时间满足了就会转成就绪状态,就像延时函数vTaskDelay一样,能够用来实现周期性/超时功能。
   (2)同步事件:某个任务在等待别的任务或者中断服务程序发来的信息来唤醒它。这些同步方式包括:①任务通知;②队列;③事件组;④信号量(semaphoe);⑤互斥量(mutex);等
三、暂停状态(Suspended)
 &emsp暂停状态一般很少用,唯一使用的方法就是通过void vTaskSuspend( TaskHandle_t xTaskToSuspend );来使用。
四、完整的状态转移图

2.1.5 相对延时和绝对延时
  FreeRTOS中两个延时函数分别是相对延时函数vTaskDelay()和绝对延时函数vTaskDelayUntil(),尽管两个函数都能使任务进入堵塞状态,但是由于延时方式的差异也会导致应用也有所不同。
一、相对延时函数
  相对延时函数vTaskDelay()的开始时间是从任务中执行到这个函数开始计算的,上面提到过这个函数的时间并不准确,是因为容易受到其他任务和中断活动的影响导致的。以当前任务遇到更高优先级的任务为例,当前任务执行到这个相对延时函数后会进入阻塞状态,系统会调度其他任务运行。如果有更高的优先级任务处于就绪状态,那么调度器会优先运行高优先级任务。当高优先级任务占用了CPU资源后,当前这个调用了vTaskDelay函数的低优先级任务则需等待高优先级任务结束或者进入阻塞状态,才能再次运行,这也会导致延迟时间的不准确。此外遇到中断处理时间较长或者频繁发生导致占用过多的CPU时间,也会导致原计划中任务被推迟,在中断结束后调度器才会重新调度任务,因此vTaskDelay延迟时间可能会比预期要长。
void vTaskDelay(TickType_t xTicksToDelay);
 
二、绝对延时函数
  绝对延时函数vTaskDelayUntil()的开始时间。如下所示,参数pxPreviousWakeTime用于存储上次任务唤醒的时刻;而参数xTimeIncrement用于表示每次任务被唤醒后所要延时的时间。正是由于有存储上轮任务唤醒时刻的机制,这个绝对延时函数更适合用于实现周期性的延时操作。
void vTaskDelayUntil( TickType_t * const pxPreviousWakeTime, const TickType_t xTimeIncrement )
 
三、延时实验
  相对延时的实验结果如下图所示,flag1为1表示Task1任务运行中,flag1为0表示Task2运行中(Task1处于堵塞状态)。相对延时函数的开始时间是从调用vTaskDelay这个函数开始(即flag1从1跳变到0时)计算的50ms。
 
   绝对延时的实验结果如下图所示,flag1为1表示Task1任务运行中,flag1为0表示Task2运行中(Task1处于堵塞状态)。绝对延时函数的开始时间是从Task1记录的上轮任务调用时间开始计算的的50ms。
 
   具体实验代码如下所示:
void vTask1( void *pvParameters )
{
	const TickType_t xDelay50ms = pdMS_TO_TICKS(50UL);
	TickType_t xLastWakeTime;
	int i;
	//获取获取当前的Tick Count
	xLastWakeTime = xTaskGetTickCount();
	/* 任务函数的主体一般都是无限循环 */
	for( ;; )
	{
		flag=1;
		for(i=0;i<5;i++){
			printf("Task 1 is running\r\n");
		}
#if 1
		vTaskDelay(xDelay50ms);
#else
		vTaskDelayUntil(&xLastWakeTime,xDelay50ms);
#endif
	}
}
void vTask2( void *pvParameters )
{
	for( ;; )
	{
		flag=0;
		printf("Task 2 is running\r\n");
	}
}
 
2.1.6 空闲任务及钩子函数
  空闲任务也就是IDLE任务,在本文的 “ 2.1.2 任务的删除 ”这个实验例子中有体现。在任务的删除中一般离不开IDLE任务,可以回看下,我个人感觉还是写的比较清晰的。
   IDLE任务的比较特殊,永远不会堵塞,优先级为0。一般在系统没有任务或任务处于堵塞状态下,IDLE任务会被调出来。
一、钩子函数
空闲任务的钩子函数是FreeRTOS提供的一种机制,允许用户在系统进入空闲任务时执行一些特定的操作。可以通过定义一个空闲任务钩子函数(vApplicationIdleHook())来扩展 IDLE 任务的功能,比如在系统空闲时进入低功耗模式、执行后台任务等,具体作用如下:
- 执行一些低优先级的、后台的、需要连续执行的函数
 - 测量系统的空闲时间:空闲任务能被执行就意味着所有的高优先级任务都停止了,所以测量空闲任务占据的时间,就可以算出处理器占用率。
 - 让系统进入省电模式:空闲任务能被执行就意味着没有重要的事情要做,当然可以进入省电模式了。
 
2.1.7 调度算法
一、调度算法的配置
  调度算法不仅要保证高优先级的任务先运行,还要确保同优先级的就绪态任务以“轮转调度”的策略来轮流执行。当然轮流调度存在的不保证任务运行时间的公平分配,因此可以细化运行时间的分配。
   从3个角度理解多种调度算法:
   (1)可否抢占?高优先级的任务能否优先执行(配置项: configUSE_PREEMPTION)
       √: 可以:被称作"可抢占调度"(Pre-emptive),高优先级的就绪任务马上执行,下面再细化。
       ×: 不可以:不能抢就只能协商了,被称作"合作调度模式"(Co-operative Scheduling)
          ①:当前任务执行时,更高优先级的任务就绪了也不能马上运行,只能等待当前任务主动让出CPU资源。
          ②:其他同优先级的任务也只能等待:更高优先级的任务都不能抢占,平级的更应该老实点
   (2)可抢占的前提下,同优先级的任务是否轮流执行(配置项:configUSE_TIME_SLICING)
       √: 轮流执行:被称为"时间片轮转"(Time Slicing),同优先级的任务轮流执行,你执行一个时间片、我再执行一个时间片
       ×: 不轮流执行:英文为"without Time Slicing",当前任务会一直执行,直到主动放弃、或者被高优先级任务抢占
   (3)在"可抢占"+"时间片轮转"的前提下,进一步细化:空闲任务是否让步于用户任务(配置项:configIDLE_SHOULD_YIELD)
       √: 空闲任务低人一等,每执行一次循环,就看看是否主动让位给用户任务
       ×: 空闲任务跟用户任务一样,大家轮流执行,没有谁更特殊
   下表用于配置调度算法,一共包含三个配置项,分别是(1)用于可抢占调度的配置项configUSE_PREEMPTION;(2)用于时间片轮转的配置项configUSE_TIME_SLICING;(3)用于关闭Tick中断来实现省电的配置项onfigUSE_TICKLESS_IDLE。
 
 (1)配置项configUSE_PREEMPTION的影响
   实验共有两个优先级为0的Task1和Task2,一个优先级为2的Task3。每个任务都有自己对应的flag(1表示该任务运行中),若系统所有任务都未执行则将IDLE任务的标志位置1。改变FreeRTOSConfig.c中配置项configUSE_PREEMPTION的值来判断影响:
此时FreeRTOSConfig.c里面配置项USE_PREEMPTION为1表示高优先级抢占
int main(void)
{
	......
	xTaskCreate(vTask1, "Task 1", 1000, NULL, 0, NULL);
	xTaskCreate(vTask2, "Task 2", 1000, NULL, 0, NULL);
	xTaskCreate(vTask3, "Task 3", 1000, NULL, 2, NULL);
	......
}
void vTask1( void *pvParameters )
{
	for( ;; )
	{
		flagIdleTaskrun = 0;
		flagTask1run = 1;
		flagTask2run = 0;
		flagTask3run = 0;
		/* 打印任务的信息 */
		printf("T1\r\n");				
	}
}
void vTask2( void *pvParameters )
{	
	for( ;; )
	{
		flagIdleTaskrun = 0;
		flagTask1run = 0;
		flagTask2run = 1;
		flagTask3run = 0;
		/* 打印任务的信息 */
		printf("T2\r\n");				
	}
}
void vTask3( void *pvParameters )
{	
	const TickType_t xDelay5ms = pdMS_TO_TICKS( 5UL );		
	for( ;; )
	{
		flagIdleTaskrun = 0;
		flagTask1run = 0;
		flagTask2run = 0;
		flagTask3run = 1;
		/* 打印任务的信息 */
		printf("T3\r\n");				
		// 如果不休眠的话, 其他任务无法得到执行
		vTaskDelay( xDelay5ms );
	}
}
void vApplicationIdleHook(void)//空闲状态下的钩子函数,在task.c里面掉用
{
	flagIdleTaskrun = 1;
	flagTask1run = 0;
	flagTask2run = 0;
	flagTask3run = 0;	
	/* 故意加入打印让flagIdleTaskrun变为1的时间维持长一点 */
	printf("Id\r\n");				
}
#if ( configUSE_IDLE_HOOK == 1 )//钩子函数的调用
{
       extern void vApplicationIdleHook( void );
       /* Call the user defined function from within the idle task.  This
       * allows the application designer to add background functionality
       * without the overhead of a separate task.
       * NOTE: vApplicationIdleHook() MUST NOT, UNDER ANY CIRCUMSTANCES,
       * CALL A FUNCTION THAT MIGHT BLOCK. */
       vApplicationIdleHook();
}
 
  实验结果如下所示,Task3优先级最高,优先执行了该任务,并在红线1的时刻调用延时函数进入了阻塞状态。此时Task1和Task2两个同优先级的任务开始交叉执行。等到Task3延时结束后由于优先级最高则会立马抢占重新开始Task3任务的执行。而在这三个任务都不执行的时候,系统则会执行IDLE状态(对应红线)。
 
   相应的如果FreeRTOSConfig.c中配置项configUSE_PREEMPTION的值为0表示不抢占的话,结果如下所示。可以看到在红线前半部分正常,当Task3因为延时进入阻塞状态后,开始就混乱了。没有抢占更没有协商好,即使Task3延时超时后,优先级更高的它也没机会执行了。
 
 (2)配置项configUSE_TIME_SLICING的影响
   实验代码如上,只不过这里是对时间片是否轮转来判断影响的。因此这里只改变configUSE_TIME_SLICING的值,另外两个配置项都为1。
   下图为时间片轮转,即配置项configUSE_TIME_SLICING值为1。
 
   下图为时间片不轮转,即配置项configUSE_TIME_SLICING值为0。不同于时间片轮转会在高优先级任务Task3阻塞的时候(flag3为0的时候)轮流执行相同优先级的Task1和Task2。时间片不轮转的情况下,在高优先级任务阻塞时只引起了一个任务的执行(Task1/Task2)。而只有高优先级任务就绪或者不再运行时才会引起任务的切换。
 
(3)配置项configIDLE_SHOULD_YIELD的影响
   实验代码如上,只不过这里是对空闲任务是否让步来进行。因此这里只改变configIDLE_SHOULD_YIELD的值,另外两个配置项都为1。
   下图为空闲任务让步,即配置项configIDLE_SHOULD_YIELD值为1。
 
   下图为空闲任务不让步,即配置项configIDLE_SHOULD_YIELD值为0。可以看到配置为空闲任务为不让步后,三者的优先级是相同的。在高优先级任务阻塞的时候,Task1、Task2以及IDLE任务都是相同优先级,因此他们会采用轮流执行。
 









![[环境配置]ubuntu20.04安装后wifi有图标但是搜不到热点解决方法](https://i-blog.csdnimg.cn/blog_migrate/c40e82403b25f0299fe82b03f60cabcb.png)









