通过学习韦东山老师的课程,对于Freertos有了一个较为系统的观念,下面对于这段时间的时间进行一个总结。
堆,
heap
,就是一块空闲的内存,需要提供管理函数;
malloc
:从堆里划出一块空间给程序使用;
free
:用完后,再把它标记为
"
空闲
"
的,可以再次使用;
栈,
stack
,函数调用时局部变量保存在栈中,当前程序的环境也是保存在栈中;
可以从堆中分配一块空间用作栈;
数据类型和编码规范
变量名
函数名
宏
1、任务管理
以日常生活为例,比如这个母亲要同时做两件事:
喂饭:这是一个任务
回信息:这是另一个任务
这可以引入很多概念:
任务状态
(State)
当前正在喂饭,它是
running
状态;另一个
"
回信息
"
的任务就是
"not running"
状态
"not running"
状态还可以细分:
ready
:就绪,随时可以运行
blocked
:阻塞,卡住了,母亲在等待同事回信息
suspended
:挂起,同事废话太多,不管他了
优先级
(Priority)
栈
(Stack) :对于程序,是记在栈里 ,每个任务有自己的栈。
事件驱动
协助式调度
(Co-operative Scheduling)
1.1任务的创建和删除
1.1.1 创建任务
void
vTask1
(
void *
pvParameters
) { while(1) {} }
void
vTask2
(
void *
pvParameters
) { while(1) {} }
int
main
(
void
)
{
prvSetupHardware
();
xTaskCreate
(
vTask1
,
"Task 1"
,
1000
,
NULL
,
1
,
NULL
);
xTaskCreate
(
vTask2
,
"Task 2"
,
1000
,
NULL
,
1
,
NULL
);
/*
启动调度器
*/
vTaskStartScheduler
();
/*
如果程序运行到了这里就表示出错了
,
一般是内存不足
*/
return
0
;
}
或者可这么写(拿我做过的项目举个例子)
#define LED0_TESK 1 // LED0任务 开启 (1) 禁用 (0)
/******************* LED0任务 *******************************/
#if LED0_TESK
//任务优先级
#define LED0_TASK_PRIO 2
//任务堆栈大小
#define LED0_STK_SIZE 256
//任务句柄
TaskHandle_t LED0Task_Handler;
//任务函数
void led0_task(void *pvParameters);
#endif
int main(void)
{ //创建开始任务
xTaskCreate((TaskFunction_t )start_task, //任务函数
(const char* )"start_task", //任务名称
(uint16_t )START_STK_SIZE, //任务堆栈大小
(void* )NULL, //传递给任务函数的参数
(UBaseType_t )START_TASK_PRIO, //任务优先级
(TaskHandle_t* )&StartTask_Handler); //任务句柄
vTaskStartScheduler(); //开启任务调度
}
void start_task(void *pvParameters)
{
taskENTER_CRITICAL(); //进入临界区
#if LED0_TESK
//创建LED0任务
xTaskCreate((TaskFunction_t )led0_task,
(const char* )"led0_task",
(uint16_t )LED0_STK_SIZE,
(void* )NULL,
(UBaseType_t )LED0_TASK_PRIO,
(TaskHandle_t* )&LED0Task_Handler);
#endif
}
#if LED0_TESK
void led0_task(void *pvParameters)
{
while(1)
{
vTaskDelay(10);
}
}
#endif
1.1.2 任务删除
如果有两个任务,在任务2中任务2自杀,任务1的函数中,如果不调用vTaskDelay,则Idle任务(空闲任务)用于没有机会执行,它就无法释放创建任务2是分配的内存。
而任务
1
在不断地创建任务,不断地消耗内存,最终内存耗尽再也无法创建新的任务。
要注意的是:如果使用 vTaskDelete() 来删除任务,那么你就要确保空闲任务有机会执行,否则就无 法释放被删除任务的内存。
1.2 任务优先级和
Tick
优先级的取值范围是:0~(configMAX_PRIORITIES – 1),数值越大优先级越高,高优先级的任务先运行。
FreeRTOS
的调度器可以使用
2
种方法来快速找出优先级最高的、可以运行的任务。使用不同的方法时, configMAX_PRIORITIES 的取值有所不同。
通用方法:
A.使用
C
函数实现,对所有的架构都是同样的代码。对
configMAX_PRIORITIES
的取值没有限制。但 是configMAX_PRIORITIES
的取值还是尽量小,因为取值越大越浪费内存,也浪费时间。
configUSE_PORT_OPTIMISED_TASK_SELECTION
被定义为
0
、或者未定义时,使用此方法。
架构相关的优化的方法
B.架构相关的汇编指令,可以从一个
32
位的数里快速地找出为
1
的最高位。使用这些指令,可以快速 找出优先级最高的、可以运行的任务。
使用这种方法时,
configMAX_PRIORITIES
的取值不能超过
32
。
configUSE_PORT_OPTIMISED_TASK_SELECTION
被定义为
1
时,使用此方法。
1.2.2 Tick
FreeRTOS
中也有心跳,它使用定时器产生固定间隔的中断。这叫
Tick
、滴答,比如每
10ms
发生一次时钟中断。
两次中断之间的时间被称为时间片
(time slice
、
tick period).
时间片的长度由
configTICK_RATE_HZ
决定,假设
configTICK_RATE_HZ
为
100
,那么时间片长度就是10ms。
相同优先级的任务怎么切换呢?
A.任务
2
从
t1
执行到
t2
B.在t2
发生
tick
中断,进入
tick
中断处理函数:
选择下一个要运行的任务
执行完中断处理函数后,切换到新的任务:任务
1
C.任务
1
从
t2
执行到
t3
从图中可以看出,任务运行的时间并不是严格从
t1,t2,t3
哪里开始
1.2.3 修改优先级
修改任务
优先级
设置任务优先级
1.3 任务状态
我们可以把任务分为四种状态
运行
(Runing)
阻塞状态
(Blocked)
暂停状态
(Suspended)
就绪状态
(Ready)
1.4 Delay函数
1.5 空闲任务及其钩子函数
Idle任务(空闲任务)的作用:释放被删除的任务的内存
一个良好的程序,它的任务都是事件驱动的:平时大部分时间处于阻塞状态。有可能我们自己创建的所有任务都无法执行,但是调度器必须能找到一个可以运行的任务:所以,我们要提供空闲任务。在使用 vTaskStartScheduler() 函数来创建、启动调度器时,这个函数内部会创建空闲任务:
空闲任务优先级为
0
:它不能阻碍用户任务运行
空闲任务要么处于就绪态,要么处于运行态,永远不会阻塞
钩子函数(
Idle Task Hook Functions)
使用钩子函数的前提
A.把这个宏定义为
1
:
configUSE_IDLE_HOOK
B.实现
vApplicationIdleHook
函数
1.6 调度算法
所谓调度算法,就是怎么确定哪个就绪态的任务可以切换为运行状态。
通过配置文件
FreeRTOSConfig.h
的两个配置项来配置调度算法:
configUSE_PREEMPTION
、
configUSE_TIME_SLICING。
还有第三个配置项:
configUSE_TICKLESS_IDLE
,它是一个高级选项,用于关闭
Tick
中断来实现省电。
调度算法的行为主要体现在两方面:高优先级的任务先运行、同优先级的就绪态任务如何被选中。
调度算法要确保同优先级的就绪态任务,能"
轮流
"
运行,策略是
"
轮转调度
"(Round Robin Scheduling)
。轮转调度并不保证任务的运行时间是公平分配的,我们还可以细化时间的分配方法。
从
3
个角度统一理解多种调度算法:
A.可否抢占?高优先级的任务能否优先执行(配置项: configUSE_PREEMPTION)
B.可抢占的前提下,同优先级的任务是否轮流执行(配置项:configUSE_TIME_SLICING)
C.在"可抢占"+"时间片轮转"的前提下,进一步细化:空闲任务是否让步于用户任务(配置项: configIDLE_SHOULD_YIELD)