FreeRTOS学习日志,由于工作原因,目前这棵技能树不作为主要发展方向了,该类型博客无限期停更…

1.关于 osThreadNew ()

1.1 FreeRTOS线程与任务的关系

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
osThreadId_t osThreadNew (osThreadFunc_t func, 
void *argument,
const osThreadAttr_t *attr)
/*******************************没有感情的分界线********************/
BaseType_t xTaskCreate( TaskFunction_t pxTaskCode,
const char * const pcName,
const configSTACK_DEPTH_TYPE usStackDepth,
void * const pvParameters,
UBaseType_t uxPriority,
TaskHandle_t * const pxCreatedTask )
TaskHandle_t xTaskCreateStatic( TaskFunction_t pxTaskCode,
const char * const pcName,
const uint32_t ulStackDepth,
void * const pvParameters,
UBaseType_t uxPriority,
StackType_t * const puxStackBuffer,
StaticTask_t * const pxTaskBuffer )

先后分别是CMSIS-OS和FreeRTOS提供的任务创建API,函数名给我带来了一个疑问线程和任务之间的关系,翻阅资料后找到如下解释。

在多数通用(分时)操作系统(如Linux,Windows)中,进程是系统资源分配的最小单位,线程为CPU调度的最小单元。而在实时操作系统中,多数情况下不区分线程与进程进行独立管理,为了减小系统资源占用以及提高实时性,往往将线程与进程合二为一,采用任务(一个个独立且无法返回的函数)作为应用程序的最小调度运行单元,使用TCB(任务控制块)对任务进行管理,FreeRTOS即是如此。
结论:像FreeRTOS这样的轻量级OS,其中的任务与线程的含义一致,即在FreeRTOS上跑的整个程序即为一个进程,该进程中又包含着多个线程(任务),这些线程有些是系统自动创建的,有些为用户手动创建。
原文链接

翻阅ST提供的用户手册得到如下信息。

本用户手册的目标读者为在 STM32 微控制器上使用 STM32Cube 固件的开发者。它完整描述了如何使用具有实时操作系统 (RTOS)的 STM32Cube 固件组件;本用户手册还提供了一组示例说明,它们基于 FreeRTOS,使用 CMSIS-OS 封装层提供的通用 API。
在 STM32Cube 固件中,通过 ARM 提供的通用 CMSIS-OS 封装层,FreeRTOS 用作实时操作系统。使用 FreeRTOS 的样例和应用可直接移植到其它任何 RTOS 而不需要修改高层API,在此情况下仅需更改 CMSIS-OS 封装。

  • 结论:osThreadNew()是ARM的CMSIS-OS的封装层API,内部包含了FreeRTOS的
    动态任务创建xTaskCreate()
    静态任务创建xTaskCreateStatic()
    根据参数选择选择调用动态还是静态创建,具体实现后文给出。

1.2 osThreadNew ()

1.2.1 osThreadNew ()如何实现动态创建与静态创建的选择

从前文已知osThreadNew()是ARM提供的上层封装,那么具体是如何实现动态与静态的选择。
翻阅源码发现原理在于其参数的选择,一下给出源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
osThreadId_t osThreadNew (osThreadFunc_t func, void *argument, const osThreadAttr_t *attr) {
char empty;
const char *name;
uint32_t stack;
TaskHandle_t hTask;
UBaseType_t prio;
int32_t mem;

hTask = NULL;

if (!IS_IRQ() && (func != NULL)) {
stack = configMINIMAL_STACK_SIZE;
prio = (UBaseType_t)osPriorityNormal;

empty = '\0';
name = ∅
mem = -1;

if (attr != NULL) {
if (attr->name != NULL) {
name = attr->name;
}
if (attr->priority != osPriorityNone) {
prio = (UBaseType_t)attr->priority;
}

if ((prio < osPriorityIdle) || (prio > osPriorityISR) || ((attr->attr_bits & osThreadJoinable) == osThreadJoinable)) {
return (NULL);
}

if (attr->stack_size > 0U) {
/* In FreeRTOS stack is not in bytes, but in sizeof(StackType_t) which is 4 on ARM ports. */
/* Stack size should be therefore 4 byte aligned in order to avoid division caused side effects */
stack = attr->stack_size / sizeof(StackType_t);
}

if ((attr->cb_mem != NULL) && (attr->cb_size >= sizeof(StaticTask_t)) &&
(attr->stack_mem != NULL) && (attr->stack_size > 0U)) {
mem = 1;
}
else {
if ((attr->cb_mem == NULL) && (attr->cb_size == 0U) && (attr->stack_mem == NULL)) {
mem = 0;
}
}
}
else {
mem = 0;
}

if (mem == 1) {
hTask = xTaskCreateStatic ((TaskFunction_t)func, name, stack, argument, prio, (StackType_t *)attr->stack_mem,
(StaticTask_t *)attr->cb_mem);
}
else {
if (mem == 0) {
if (xTaskCreate ((TaskFunction_t)func, name, (uint16_t)stack, argument, prio, &hTask) != pdPASS) {
hTask = NULL;
}
}
}
}

return ((osThreadId_t)hTask);
}
  • 在末尾可以看出使用哪一种创建方式由参数mem决定,阅读前文发现只有

    • attr->stack_mem有值且指定用户数组以及任务控制块地址时的时候会使得mem置1,此时调用的时静态创建任务函数。
    • attr->stack_mem没有值的时候会使得mem置0,此时调用的时动态创建任务函数。
  • attr->stack_mem的值是由const osThreadAttr_t *attr形参结构体中取得,在创建任务的时候需要定义一个结构体作为参数传入osThreadNew()中,CubeMX给出的默认任务参数如下。

    1
    2
    3
    4
    5
    6
    osThreadId_t startTaskHandle;
    const osThreadAttr_t startTask_attributes = {
    .name = "startTask",
    .stack_size = 128 * 4, //0.5KB
    .priority = (osPriority_t) osPriorityLow3,
    };
  • 必须同时给出任务栈大小,任务堆栈,任务控制块地址才能调用静态创建任务函数,否则只取attr->stack_mem的值作为任务栈大小动态创建任务。若连attr->stack_mem都未给出,则取系统默认设置为任务栈大小动态创建任务。

1.2.2 xTaskCreate()任务栈空间理解

在阅读动态创建函数传参时候发现,居然也有任务栈空间这一项参数,而我记得我阅读操作手册的时候写的是动态任务创建任务栈空间由操作系统分配,归操作系统管理,那么为什么还要传这一个参数呢。

1
xTaskCreate ((TaskFunction_t)func, name, (uint16_t)stack, argument, prio, &hTask) 

回去重新阅读了操作手册发现自己理解错误,以下贴出操作手册原文。

每个任务都需要 RAM 来保存任务状态,并由任务用作其堆栈。 如果使用 xTaskCreate() 创建任务,则所需的 RAM 将自动 从 FreeRTOS 堆中分配。 如果创建任务 使用了 xTaskCreateStatic(),则 RAM 由应用程序编写者提供,因此可以在编译时进行静态分配。

所以实际上动态创建任务也是需要指定任务栈空间的,但是我们创建时并没有给出参数,他又是传了什么进去呢?

阅读上文源码发现,该参数一开始就被赋值了
stack = configMINIMAL_STACK_SIZE
其中
configMINIMAL_STACK_SIZE
是个宏定义,我们在FreeRTOSConfig.h文件中找到了定义
#define configMINIMAL_STACK_SIZE ((uint16_t)128)

  • 也就是说当我们不指定任务栈空间,CMSIS-OS提供的封装帮我自动设置了任务栈空间,并且调用动态创建任务函数。

1.2.3 CMSIS-OS与FreeRTOS任务栈空间单位不同!

接下来我遇到了最坑的一个点,如题所说,CMSIS-OS与FreeRTOS任务栈空间单位不同。

  • 在学习FreeRTOS时我观看了正点原子的视频,包括在网上搜索的资料都是面向FreeRTOS的。而FreeRTOS提供的创建任务函数中指定任务栈大小的参数stack其单位是

    在stm32中,1字=4字节 1字节=8位

所以,我理所当然(我大意了)的认为osThreadNew()参数中的stack_size 其单位也是字,所以在计算的时候一直当作字来计算。

1
2
3
4
5
const osThreadAttr_t startTask_attributes = {
.name = "startTask",
.stack_size = 128 * 4,
.priority = (osPriority_t) osPriorityLow3,
};

并且,在这个函数中的注释写了

1
2
3
4
5
6
/* In FreeRTOS stack is not in bytes, but in sizeof(StackType_t) which is 4 on ARM ports.       */
/* Stack size should be therefore 4 byte aligned in order to avoid division caused side effects */
翻译过来就是
/*在FreeRTOS中,堆栈不是以字节为单位,而是以sizeof(StackType_t)为单位,在ARM端口上为4*/
/*因此,堆栈大小应为4字节对齐,以避免除法造成的副作用*/

但当我调试时候计算栈空间时发现不太对劲,具体怎么发现的我就不说了,反正不是很聪明的样子。贴个调试图。
在这里插入图片描述
在和老学长一番争论,并且重新翻阅源码以及用户手册后我突然发现在注释的前后写了一段代码

1
2
3
4
5
if (attr->stack_size > 0U) {
/* In FreeRTOS stack is not in bytes, but in sizeof(StackType_t) which is 4 on ARM ports. */
/* Stack size should be therefore 4 byte aligned in order to avoid division caused side effects */
stack = attr->stack_size / sizeof(StackType_t);
}

他把我传进去的参数除了一个StackType_t大小。
查阅其定义发现
typedef portSTACK_TYPE StackType_t;
#define portSTACK_TYPE uint32_t

这个类型大小32位,8位1字节,也就4个字节,1个字。
sizeof()用于返回运算对象的内存大小,单位是字节。
所以sizeof(StackType_t)=4

  • 也就是说在osThreadNew()的任务栈空间参数在经过ARM封装后实际上是以“字节”为单位的,所以也就是说我我传进来的数字实际上是 128 * 4=512字节/4=128字赋给了stack

这里贴出官网对参数usStackDepth 也就是stack 赋给的形参值的介绍与地址。用户手册参数介绍地址

要分配用于 任务堆栈的 字数(不是字节)。例如,如果堆栈的宽度为 16 位,usStackDepth 为 100,则将分配 200 字节用作该任务的堆栈。 再举一例,如果堆栈的宽度为 32 位,usStackDepth 为 400,则将分配 1600 字节用作该任务的堆栈。
堆栈深度与堆栈宽度的乘积不得超过 size_t 类型变量所能包含的最大值。

1.2.4 选择动态分配时osThreadNew()分配的任务栈空间

这个比较好找,直接阅读源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
osThreadId_t osThreadNew (osThreadFunc_t func, void *argument, const osThreadAttr_t *attr) {
char empty;
const char *name;
uint32_t stack;
TaskHandle_t hTask;
UBaseType_t prio;
int32_t mem;

hTask = NULL;

if (!IS_IRQ() && (func != NULL)) {
stack = configMINIMAL_STACK_SIZE;
prio = (UBaseType_t)osPriorityNormal;

empty = '\0';
name = &empty;
mem = -1;

if (attr != NULL) {
if (attr->name != NULL) {
name = attr->name;
}
if (attr->priority != osPriorityNone) {
prio = (UBaseType_t)attr->priority;
}

if ((prio < osPriorityIdle) || (prio > osPriorityISR) || ((attr->attr_bits & osThreadJoinable) == osThreadJoinable)) {
return (NULL);
}

if (attr->stack_size > 0U) {
/* In FreeRTOS stack is not in bytes, but in sizeof(StackType_t) which is 4 on ARM ports. */
/* Stack size should be therefore 4 byte aligned in order to avoid division caused side effects */
stack = attr->stack_size / sizeof(StackType_t);
}

if ((attr->cb_mem != NULL) && (attr->cb_size >= sizeof(StaticTask_t)) &&
(attr->stack_mem != NULL) && (attr->stack_size > 0U)) {
mem = 1;
}
else {
if ((attr->cb_mem == NULL) && (attr->cb_size == 0U) && (attr->stack_mem == NULL)) {
mem = 0;
}
}
}
else {
mem = 0;
}

if (mem == 1) {
hTask = xTaskCreateStatic ((TaskFunction_t)func, name, stack, argument, prio, (StackType_t *)attr->stack_mem,
(StaticTask_t *)attr->cb_mem);
}
else {
if (mem == 0) {
if (xTaskCreate ((TaskFunction_t)func, name, (uint16_t)stack, argument, prio, &hTask) != pdPASS) {
hTask = NULL;
}
}
}
}

return ((osThreadId_t)hTask);
}

在函数开头 stack = configMINIMAL_STACK_SIZE;

参数stackosThreadNew()函数一开始就被赋值了,若参数attr->stack_mem缺省,则stack值不会被改变。翻找configMINIMAL_STACK_SIZE定义。
#define configMINIMAL_STACK_SIZE ((uint16_t)128)

  • 也就是说当用户不指定任务栈空间,CMSIS-OS会自动给用户分配128的任务栈。而只有attr->stack_size 的值大于0的时候,才会被除4。
  • 所以此时的128是以为单位的。
1
2
3
4
5
if (attr->stack_size > 0U) {
/* In FreeRTOS stack is not in bytes, but in sizeof(StackType_t) which is 4 on ARM ports. */
/* Stack size should be therefore 4 byte aligned in order to avoid division caused side effects */
stack = attr->stack_size / sizeof(StackType_t);
}
结论:
1.使用静态任务创建需要用户自己指定内存空间大小,参数单位是“字节”,并调用xTaskCreateStatic()。
2.使用动态任务创建CMSIS-OS指定了任务栈大小128字,并调用xTaskCreate()。
3.用户可以根据需要修改configMINIMAL_STACK_SIZE宏定义的值,来修改attr->stack_size缺省时任务栈大小的默认值。