FreeRTOS学习笔记—②RTOS的认识及任务管理篇(正在更新中)

  由于正在学习韦东山老师的RTOS课程,结合了网上的一些资料,整理记录了下自己的感悟,用于以后自己的回顾。如有不对的地方请各位大佬纠正。

文章目录

一、RTOS的优势

①:确定性和实时性:

  RTOS的最大特点是能够在严格的时间约束内完成任务。这种确定性对于时间敏感的应用(如工业控制、医疗设备等)至关重要。

②:优先级调度:

  RTOS通常支持优先级调度机制,确保高优先级的任务可以抢占低优先级的任务执行。这种机制保证了关键任务能够在最短时间内得到处理。

③:低延迟和高响应性:

  RTOS设计的目标是最小化任务切换时间和中断延迟,从而实现高响应性。这在需要快速反应的嵌入式系统中非常重要。

④:资源管理和内存控制:

  RTOS通常提供精细的资源管理工具,允许开发者更好地控制内存和CPU资源的使用。这种控制对于嵌入式系统中的资源有限环境尤其重要。

⑤:模块化和灵活性:

  RTOS通常具有模块化设计,允许开发者根据具体需求启用或禁用特定的功能模块。这种灵活性有助于优化系统性能和减少系统开销。

⑥:可靠性和稳定性:

  RTOS被广泛应用于需要高可靠性和稳定性的系统中,例如自动驾驶、军事系统等。RTOS通过严格的测试和验证,确保其在各种边界情况下都能稳定运行。

⑦:较小的内存占用:

  RTOS通常占用的内存和资源较少,这使得它非常适合嵌入式系统或其他资源受限的环境。

二、RTOS的核心功能

RTOS的核心功能块主要分为任务管理、内核管理、时间管理以及通信管理4部分,框架图如下所示:

(1)任务管理:负责管理和调度任务的执行,确保系统中的任务能够按照预期运行。

(2)内核管理:负责系统核心功能的管理,包括内存、中断、异常处理和系统启动等。

(3)时间管理:负责所有与时间相关的操作,包括系统时钟、定时器、任务延迟和周期性任务的执行。

(4)通信管理:提供任务之间的通信机制,确保任务能够有效地协作和共享资源。

2.1 任务管理
2.1.1 任务的创建

  任务就是一个无返回的函数(Void)。由于函数传参的不同,一个函数可以创建多个任务,然后每个任务都有对应自身的栈,也就是说一个函数可以有多个栈(当然一个函数对应一个栈也是可以的)。使用下面的函数用于创建任务:

c 复制代码
void TaskAFunction(void *param)
{
	int* tmp	= (int*) param;//首先将void *指针类型的param转为int *类型的指针 
	int value = *tmp;	       //然后解引用来获取指针指向的值
	while(1)
	{
		printf("%d",value);
	}
}

尽管是同一个函数,但是创建的多个任务主要不同还是在于传参而不是名字,下面的代码使用了相同的名字("TaskA")创建了三个参数不同的任务。

c 复制代码
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表示自杀 ,如果是自己的句柄则是被杀 ,别人的句柄就是杀人

c 复制代码
void vTaskDelete( TaskHandle_t xTaskToDelete );

  基础实验如下,在vTask1任务中嵌套vTask2任务的创建,并vTask2任务中进行自杀,所以xTask2Handle设置为NULL。

c 复制代码
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.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代码如下:

c 复制代码
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函数实现对任务优先级的设定。该函数具体如下:

c 复制代码
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。这样很明显两者通过调整任务优先级来实现一个来回执行的效果,代码如下:

c 复制代码
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)

  在章节 " 三、优先级的实验 " 中提过这个状态。Task3的优先级高于Task2和Task1,就像女神(CPU资源)的正主(Task3)肯定比舔狗(Task1/Task2)的优先级高的一样。那么女神和正主玩的时候,压根轮不到舔狗。对于舔狗来说还会接着舔且依旧忠诚的等着女神,虽然没机会,更不会占用着女神,这个就是舔狗这个任务处于阻塞状态。

二、就绪状态(Ready)

  就绪状态就像女神被正主霸占着,但是舔狗随时准备着女神和她的正主分手后,舔狗来占有女神。

三、暂停状态(Suspended)

 &emsp暂停状态一般很少用,唯一使用的方法就是通过void vTaskSuspend( TaskHandle_t xTaskToSuspend );来使用。

四、完整的状态转移图
五、个人感悟

  不要盲目的当舔狗,自己冷静想想,在未来到底能不能舔到有价值的。舔久了得不到回应那就算了,不如过好自己,开开心心。此外我可不是舔狗,但是感觉无论在校或社会中,多少还是需要些讨好吧,把领导舔高兴了自己也能混的更舒坦了,很多人可能看不起,但是这种现象也无处不在,希望自己以后也能成为一只合格的舔狗吧。当然能力够强的或后台够硬的肯定不屑啦,就当没看见吧。不过估计也没多少人会看到哈哈。

2.1.5 任务状态
相关推荐
通信小呆呆4 天前
当算法有了“五感”:多模态数据融合如何向人体感官协同学习?
人工智能·学习·算法·机器学习·机器人
H__Rick4 天前
自动对焦学习-3
人工智能·学习·计算机视觉
Daisy Lee4 天前
量化学习-第1章-什么是量化金融
学习·金融·datawhale
Alsn864 天前
等待学习-学习目录:Docker 容器安全攻防
学习·安全·docker
YM52e4 天前
买菜计算器小应用 - HarmonyOS ArkUI 开发实战-PC版本
学习·华为·harmonyos·鸿蒙·鸿蒙系统
小雨下雨的雨4 天前
HarmonyOS ArkUI训练营入门-组件掌握系列-Animation 动画效果实现-PC版本
学习·华为·harmonyos·鸿蒙
闪闪发亮的小星星4 天前
高斯光以及高斯光公式解释
笔记
cqbzcsq4 天前
CellFlow虚拟细胞论文阅读
论文阅读·人工智能·笔记·学习·生物信息
YangYang9YangYan4 天前
2026初入职场学习数据分析的价值
学习·数据挖掘·数据分析
阿米亚波4 天前
【Windows】QEMU 启动 openEuler aarch64/arm64 架构系统 + 离线软件源
linux·windows·经验分享·笔记·架构·arm