前言
在我们学习 RTOS 的过程中,很多朋友都不会遇到内存不够的问题,因为大部分的开发板使用的芯片对学习来说,内存 “足够大” 。所以基本上很多人学会了基本功能,到了实际工作中使用,往往会遇到内存不够的问题,因为公司产品是需要控制成本的,一般来说,内存越大成本越高,工作中有时候使用的芯片内存可能会比较小,很多朋友就会遇到学习的时候遇不到的问题,一下子不知如何处理了。
本文的说明以 FreeRTOS 为例,其实在博主的 FreeRTOS 系列博文当中说得最多的就是内存问题:
【导航】FreeRTOS学习专栏目录 【快速跳转】
即便当时写 FreeRTOS 专栏的时候整个系列流程好像不是那么合理,适合有一定基础的小伙伴,但是对于内存的节约 问题上有详细的说明。
最近在一些项目的 FreeRTOS 使用上,其实博主自己也经历思考了一些问题,所以又来对 FreeRTOS 的专栏进行一些补充,那么本文就来测试一下 使用 FreeRTOS 的时候如何应用 “ 在任务中创建任务的方式 ” 合理的分配任务以节约内存 。
我是矜辰所致,全网同名,尽量用心写好每一系列文章,不浮夸,不将就,认真对待学知识的我们,矜辰所致,金石为开!
目录
- 前言
- 一、目的说明(为什么要在任务中创建任务)
- 二、实验验证
- 2.1 测试环境
- 2.2 FreeRTOS 任务 API 复习
- 2.3 实际操作
- 2.4 问题测试
- 2.5 最终测试
- 结语
一、目的说明(为什么要在任务中创建任务)
在写文章之间,首先说明一下本文要解决的是什么问题,我们使用一个例子来说明:
你使用的 CPU 的内存只有 5K RAM 空间可以提供给你的 FreeRTOS 任务,
而你需要用到4个任务:
周期任务A:1.5K
周期任务B:1.5K
周期任务C: 1.5K
一次性任务D: 1.5K(开机后接收到某个任务通知/消息的一次性任务)
这时候,如果一开始全部任务都创建,显然不可行,那么我们可以开始只创建必要的任务,等到一次性任务完成后,然后在这个任务中创建其他的周期任务,再删除这个一次性任务即可。
说明!!! 对于本文所提出的问题,其实实际中有更多更好的解决方式,比如把一次性任务加在初始化阶段中完成,或者直接在某个周期任务循环前面(while(1) 前面)走一遍一次性的任务过程。
但是本着研究的态度,还挺想看看在 STM32CubeMX 的 FreeRTOS 环境下面,是否能够正常的使用上面这个不太聪明的方法实现效果,或许以后有用得着的地方!所以我还是计划简单的测试下。
二、实验验证
上面分析完成,我们来进行实际的使用测试,先说明下本文使用的测试环境。
2.1 测试环境
测试环境:
硬件平台: STM32L051C8T6 RAM大小: 8K
软件环境: STM32CubeMX
FreeRTOS分配空间: 3K
先看一下下面的图片,对应上面我们说过的最后其实我们想要长期周期运行的任务只有前面3个,最后一个是需要开机初始化时候运行一次的任务(设计的时候因为用到了 FreeRTOS 的任务通信机制,所以不太好直接在初始化还没开始调度前面进行完成)。
由上图可知,我们是无法直接创建全部的任务的,即便你想先创建,然后运行完了删除来节约内存,是行不通的。
先说明,上面的任务去掉 64 大小的 myTask04 是可以的,就是256 + 192 +128 的任务组合是我们最终需要保留的周期任务。
那么我们来使用上面提到的思路进行,在允许的范围内,先建立一个初始化的任务(其中 KeyTask 任务我写了一些程序,所以此次就保留在这里,这个保留这个任务不影响本次测试):
上面的 InitTask 就对应一次性任务 Task04,因为会删除,我们可以创建得大一点,但是也得注意大小。
在 STM32CubeMX 的设置到这里就完成了,其他的地方都需要我们在代码中直接修改。
在 KeyTask 任务中,我们还是加入了任务状态查看功能:
这里额外说明一下,相比我以前的系列文章,这里多加了一个剩余空间的打印:
printf("free space is %d\n",xPortGetFreeHeapSize());
刚开始,在我们的 InitTask 中,我们什么都不做,初始状态如下图:
接下来我们要考虑的就是在当前程序中,我们该如何创建新的任务,而不是依靠 STM32CubeMX 初始化创建。
2.2 FreeRTOS 任务 API 复习
我们先来复习一下 FreeRTOS 的创建任务函数原型(示例以动态创建函数来说明),如下图:
而在 CMSIS 接口下面通过封装后统一使用的是 osThreadCreate
, 我们也来复习一下:
详细的任务 API 解析请参考以前的博文:FreeRTOS记录(二、FreeRTOS任务API认识和源码简析)
我们再来参考一下系统自动的任务创建操作:
2.3 实际操作
我们参考这种方式,手动的在 Init 任务中创建新的任务,首先手动定义任务句柄以及声明任务函数,如下图:
然后我们直接在 InitTask 任务中进行任务创建,当然为了更好的查看结果,我还加入了任务状态打印,如下图:
最后不要忘了实现一下 myTask02 任务函数:
测试一下看看效果:
上面可以看到,出问题了,并没有创建新的任务,奇了怪了!
我以为是创建函数出问题了,把下面的创建函数语句:
osThreadDef(myTask02, StartmyTask02Task, osPriorityNormal, 0, 192);
myTask02Handle = osThreadCreate(osThread(myTask02), NULL);
改成了
xTaskCreate( StartmyTask02Task, "myTask02", 192, NULL, osPriorityHigh, &myTask02Handle );
还是不行。
其中还以为是前后要加点延时,测试过也不行,难道是不能创建,没有道理啊!
于是我在原本的另外一个 KeyTask 任务中,加上了一个按键操作,按下按键新建一个 myTask02 ,语句是和在 InitTask 中语句一样的,如下图:
IninTask 中的代码保持不变,再次测试看看:
这样可以说明,在任务中创建任务是没问题的,使用的程序语句也是没有问题的,但是开始为什么会创建失败呢?
2.4 问题测试
我进行了一些测试排除了一些问题:
测试记录:
和任务优先级无关(把新创建的任务的优先级设置成比自己高,比自己低,和自己相等)
和延时无关(前后加上足够的延时)
和本任务的栈大小无关(怕自己任务栈不够,去掉了打印,还是创建失败,对比 KeyTask 的栈空间一样还是可以成功)
…
(中间写了一大堆,最后全部删除了,这都怪自己犯二了 !)
…
前面刚刚用图片说明了,任务大小 256 + 192 + 128 + 64 都已经内存不够了:
在上面测试中我初始的任务为 256 + 256 ,还想在没删除任务的时候创建一个大小为 192 的任务……
好吧! 我是傻子!
2.5 最终测试
这次冷静下来发现了这么二的问题,唉 ~
文章开头说过,我们最终需要的任务是 256 + 192 +128 的组合,然后还有一些操作需要在 InitTask 中实现的,实际上我开始把 InitTask 放成 256 当时是为了打印一些数据方便测试,脑子一下子没反应过来……
我们这里把思路理清楚,我们首先创建一个 256 的 KeyTask 和 192 的 InitTask, 然后在 192 的InitTask 中做初始化的工作,然后创建一个 128 的 myTask03 ,然后 InitTask 自己删除,删除完成以后在 myTask03 中循环前面创建一个 192 的 myTask02 ,结束(确实有点绕,但是前面说了只是为了测试)。
那么我们调整一下,IninTask 改成 192 大小,如下图:
然后我们 Task02 和 Task03 的定义我们这里就直接一起测试了,首先是任务2 和任务 3 句柄定义和函数声明:
InitTask 还是打印一下状态,新建任务3 ,这次的空间是足够的:
实现任务2 和任务 3 函数,在任务3 中创建任务 2 ,如下图:
我们直接上一下最后测试的结果:
整理来说最后结果是正常的!
虽然上图中有一个疑问,我加了延时测试了一下无果,暂时也不想折腾了,因为在 FreeRTOS 中使用 printf 函数打印调试是很占用任务内存大小的,尤其是在我们这种小内存的 CPU 上是很痛苦的一件事情。
最终发现 任务 3 的剩余栈基本没了,这也可能是一个问题,是因为在任务中使用任务创建的操作也会占用很大的内存吗?有必要测试一下。
因为过程都正常了,所以我也不需要 printf 操作,所以我把所有的 printf 去掉后,看看任务剩余栈的情况,这里直接上一下结果:
至此,本文的测试也算是圆满结束!!
结语
本来是一次简单的实验测试,但是因为自己一下子翻了糊涂,导致中途出现了莫民奇妙的问题,这种问题总是无法避免啊,但是好在最后我们成功实现了自己想要的结果,也算是实验成功了!
在文章开头我们就说过,在实际中遇到此类问题,或许会有更好的方法。但是通过本次的实验,可以让我们更好的应用操作系统的任务创建与删除操作,而不是只会在程序开始时候创建到现成的一直用到底 。
好了,本文就到这里,谢谢大家!