任务与协程区别
一个程序可以只有任务、只有协程、二者都有,但不可以通过队列/信号量互相传递数据
任务特点
任务之间可以互相独立
每个任务分配自己的堆栈,提高了RAM使用率
操作简单、按优先级抢占式执行
抢占容易导致重入(执行任务时被其他线程或进程调用了)
协程特点
一般用于小型,RAM有限制的处理器上
所有协程共用一个堆栈
通过一组宏实现的
减少了重入问题,但是和任务混用总抢不过人家
任务状态
运行、就绪、阻塞、挂起
任务优先级
0–configMAX_PRIORITIES – 1,最大值定义在FreeRTOSConfig.h中
设置的越小越好,某些特殊情况不得超过32
空闲任务优先级为0
默认情况,相同优先级按时间切片轮流运行
任务调度单核调度
抢占式调度,即使在同一时间片,出现了一个高优先级任务,也会打断当前的低优先级任务。
时间片指两个tick中断之间的时间,相同优先级的任务,一人一个时间片依次执行。
如果一个高优先级任务永远不阻塞、不挂起,低优先级任务永远也不会执行,因此推荐创建事件驱动的任务,等待事件的时候阻塞掉它,收到事件再解除阻塞
高优先级任务处于“阻塞”状态时, 低优先级任务会运行。
FreeRTOSConfig中:
configUSE_PREEMPTION=0
关闭抢占,只有阻塞/挂起/运行的任务调用 taskYIELD()/ISR才会切换下文的任务
configUSE_TIME_SLICING=0
关闭时间片,相同优先级的任务不会在tick间隔后切换。
AMP调度
非对称多核处理,每个核心独立运行字节的实例,不需要有相同架构
SMP调度
对称多核处理,一个程序跨多个处理器调度
- 单核移植到SMP
configRUN_MULTIPLE_PRIORITIES = 0 ,可以同时运行优先级相同的多个任务
configUSE_CORE_AFFINITY = 1,通过vTaskCoreAffinitySet() 方法定义某个任务可以在哪个核运行
任务实现
定义任务
void vMyTask(void *pvParameters){ for(;;){ Task coding here...//任务代码写在死循环里,非必要不退出,退出一定要delete} vTaskDelete(NULL);}
typedef void (*TaskFunction_t)(void*);
这句话是什么意思呢,就是TaskFunction_t是一个指向函数的指针,这类函数具有void * 类型的形参,返回值类型是void
事件型驱动代码框架,WaitForEvent是消息队列接收/事件等待等
if(WaitForEvent(事件,等待超时时间)){Tasking coding here...}else{报错、处理错误等}
- 通过宏定义实现一个任务定义
portTASK_FUNCTION_PROTO( vMyTask, pvParameters );
队列
队列是任务间通信的主要形式,先入先出
数据被拷贝到队列中,而不是传地址
读空队列、写入满队列都会导致阻塞,高优先级的优先解除阻塞
队列API允许指定阻塞最长时间,
信号量
是一种实现任务间通信的机制,是一个计数器
每当有个任务获取信号量计数器就减一,变成0后所有试图获取信号量的任务都阻塞了
信号量当前的值就是可用资源数,
二值信号量
许多情况下可以用任务通知代替二值信号量
信号量API允许指定最大阻塞时间,阻塞时间指的是tick间隔的数量
二进制信号量可视为容量为1的队列
用处
一个任务只用来服务一个外设,但不是一直服务的,还有轮询或者其他无关操作,这时候就可以用二值信号量减少资源浪费。
具体操作为:
任务执行时获取信号量,所谓获取信号量就是让计数器-1,变成0了,于是进入阻塞状态,其他任务想执行就可以执行
当外设需要任务了,就触发中断给出信号量,也就是变成1了,原来的任务不阻塞了,继续为外设服务。
我觉得可以理解成把主动权交给外设,需要任务就叫他
计数信号量
长度大于1的队列
用处
初始定义信号量为0,用来盘点事件,发生一个事件+1,处理后-1
初始定义信号量为MAX,用于资源管理,每执行一次任务减一
互斥锁
互斥锁是包含优先级继承机制的二进制信号量
在 FreeRTOS 中,通常采用抢占式调度。这意味着当一个高优先级任务准备好并且具备运行条件时,它会立即抢占当前正在执行的低优先级任务。
然而,即使是抢占式调度,仍然可能出现低优先级任务占用资源导致高优先级任务无法执行的情况,原因就是高优先级任务没有准备好
互斥锁之所以叫互斥,是因为它执行当前的任务时把他优先级调到最高了,就算浪费资源也要先把它执行完
不要在中断中使用互斥锁,因为中断无法保持阻塞等待互斥锁的任务执行完毕,而且优先级继承机制要求从任务中拿出而不是中断中
递归互斥锁
只有一个任务成功执行n次(递归深度)才可以解锁
任务通知
每个任务都有一个任务通知数组,数组里每条通知都有挂起、不挂起俩状态和一个32位通知值
configTASK_NOTIFICATION_ARRAY_ENTRIES 设置任务通知数组最大索引
直达任务通知是直接发到任务的事件,而不是通过队列信号量等间接发送到任务的事件
发送通知的任务,发送前后状态不变(就绪/运行);接收通知的任务,接收前可能是阻塞/运行,取决于通知的状态是否已经是挂起(挂起就是已经发过了,读取后变为非挂起);接收后取决于接收到的通知内容变化
任务通知创建任务时就带了,是一个任务通知另一个任务,或者通知自己!!!
软件定时器概述
作用是 让函数在未来的设定时间执行,这个函数就叫做回调函数
定时器启动–经过一个周期–回调函数执行
定时器回调函数,不要调用vTaskDelay()等会阻塞的函数
定时器服务任务,通过队列接收命令(通过API函数发出),这个队列叫定时器队列,不可直接操作,只能用API写入,定时器任务函数读取并删除
定时器是可选功能,想用的话:
timer.c
FreeRTOSConfig.h中,configUSE_TIMERS = 1,表示创建调度器自动创建定时器任务
configTIMER_TASK_PRIORITY 定时器任务优先级,其实就是任务优先级
configTIMER_QUEUE_LENGTH 定时器队列长度
configTIMER_TASK_STACK_DEPTH 分配给定时器任务的堆栈大小,取决于回调函数
一次性定时器和自动重载定时器
一次性定时器只执行一次回调函数,后面再想执行需要手动启动
自动重载定时器,执行完后自动重新执行
定时器可以重置,重置后从0开始算,之前的计时清零