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 任务状态
相关推荐
晓数38 分钟前
【硬核干货】JetBrains AI Assistant 干货笔记
人工智能·笔记·jetbrains·ai assistant
我的golang之路果然有问题1 小时前
速成GO访问sql,个人笔记
经验分享·笔记·后端·sql·golang·go·database
genggeng不会代码1 小时前
用于协同显著目标检测的小组协作学习 2021 GCoNet(总结)
学习
lwewan1 小时前
26考研——存储系统(3)
c语言·笔记·考研
搞机小能手1 小时前
六个能够白嫖学习资料的网站
笔记·学习·分类
nongcunqq2 小时前
爬虫练习 js 逆向
笔记·爬虫
汐汐咯2 小时前
终端运行java出现???
笔记
The_cute_cat4 小时前
25.4.22学习总结
学习
无敌小茶4 小时前
Linux学习笔记之环境变量
linux·笔记
冰茶_4 小时前
.NET MAUI 发展历程:从 Xamarin 到现代跨平台应用开发框架
学习·microsoft·微软·c#·.net·xamarin