一:资源管理
怎么保护一个临界资源?
比如说一个全局变量,我们可以使用互斥量
int a;
int add_val(int val)
{
// 获得互斥量
a += val;
// 释放互斥量
}
那问题又变成:怎么实现互斥量?
一句话就可以理解今晚的主题:
神挡杀神,佛挡杀佛。
- 任务B跟任务A抢?关闭调度器
- 中断跟任务抢?屏蔽中断
- 中断跟中断抢?屏蔽中断
当然他没那么狠,任务相争,不会杀掉任务:但是会禁止任务的切换
我们先讲最简单的任务相争:
int a;
void xxx_func(int val)
{
vTaskSuspendAll(); // 关闭调度器
/* 访问临界资源 */
xQueueSend(....)
xTaskResumeAll();
}
你看他就先关闭调度器:禁止任务的调度
关闭调度器很简单,让某个变量累加就可以:
那显然,调度函数:他会判断这个变量,如果这个变量等于0才调度,大于0就不会调度
调度在什么情况下发生?
1.高优先级的任务会抢占
2.同优先级的任务,轮流执行
3.当前任务可以主动放弃
先来看看,轮流执行:tick中断
我们再来看看高优先级的任务怎么抢占?
这个变量代表什么意思?就是一个标志变量。
再来看看调用vTaskSuspendAll
后:高优先级任务,能否抢占低优先级任务?
假设他可以,那就要切换任务
大家可以看到:关闭调度器是如此的简单
就是设置某个标记变量
关闭调度器之后,怎么重新开启调度器?看代码:
不是直接清零,而是减1
这也意味着: 可以嵌套地使用taskENTER_CRITICA()
/taskEXIT_CRITICAL()
这也就意味着: 调用一次taskENTER_CRITICA()
,就要调用一次taskEXIT_CRITICAL()
如果调用了一次taskENTER_CRITICA()
,但是调用了2次taskEXIT_CRITICAL()
,会发生什么事?
这里会做什么事情?
会看看有没有高优先级的任务就绪,有的话就调度
我们来看看代码:
我们来总结一下:
神挡杀神,佛挡杀佛。
- 任务B跟任务A抢?关闭调度器
- 中断跟任务抢?屏蔽中断
- 中断跟中断抢?屏蔽中断
任务之间竞争的时候,关闭调度器就可以。
假设 taskA调用xTaskDelay(10)
,10个tick之后它就绪了,怎么办呢?
在关闭调度器期间,假设发生了10次tick,那么xPendedTicks就等于10
新开启调度器的时候,他发现xPendedTicks
等于10,就会调用10次:xTaskIncrementTick
之前taskA调用 xTaskDelay(10)
,它就被唤醒了
我们再来看另外一个问题:
这个时候就不能够仅仅关闭调度器
关闭调度器的期间,中断还是可以产生的
如果有中断也来使用临界资源,你只是关闭调度器的话:根本防不住中断
解决办法就是:关闭中断
关闭所有的中断吗?
关闭中断之后,调度器也就没有办法执行了
在一个rtos系统中:不能够关闭所有的中断,有些中断事关生死
无论什么时候都不能关闭
那显然:只能够关闭某一类中断
哪一类?使用FreeRTOS的syscall就是系统函数的哪些中断
FreeRTOS的中断分为上图的两类
1.不使用syscall的中断,优先级比较高
2.使用syscall的中断,优先级比较低
syscall不是某个中断,而是会用到系统函数的中断
比如GPIO中断:
1.GPIOA是按键,要用到定时器,它就属于第2类中断
2.GPIOB是安全报警中断,为了可靠,它的函数不能调用freertos的函数,它就属于第1类中断
我们配置GPIOA,GPIOB的中断优先级时,就要特定设置:
GPIOA的优先级的值,处于图中第2类
GPIOB的优先级的值,处于图中第1类
来提几个问题:
1.任务运行的时候,中断是使能还是禁止?是使能的
2.中断函数中,中断是使能的,还是禁止的?都有可能
现在我们知道了:在任务中屏蔽中断,在中断中屏蔽中断,用的函数不一样
回到我们的第1个话题:怎么实现互斥量?
简单粗暴:屏蔽中断
我要去修改互斥量,先屏蔽中断
我们来看一下代码:
二. 晚课学员提问
1. 问: 老师,我假如有一个任务暂时不用。让他暂停。执行了上图的两个指令以后。本来不想让这个任务运行。那实际上他是不是也会运行?
答: 启动调度器,只是说可以重新调度了,并不是说去把本来就暂停的任务强制唤醒
2. 问: 老师 ,在任务中屏蔽中断和在ISR中屏蔽中断,为什么在任务中屏蔽中断的时候不需要记录返回值恢复之前中断状态啊?
答: 因为在任务函数里,任何函数运行时,中断状态就是使能的
3. 问: 老师,为什么任务里中断都是使能的而中断函数里的中断却不一定?中断不是同样的中断吗?
答: 先回答第2个问题:中断函数里的中断却不一定是使能的
为什么任务里中断都是使能的?
1.启动第1个任务的时候,中断是使能的
2.任务关闭中断之前,中断是使能的
3.任务关闭中断后,要开启中断:关、开,是一一配对的
所以:任务里,平时,中断是使能的
4. 问:老师 中断状态指的都是全局中断状态吗??
答: 想想看我们有几类中断?会重新进入exti0的中断函数
有两类中断:使用syscall的,不使用syscall的
所以,这个中断状态指的是 “使用syscall函数的那类中断”,能否使能
5. 问: 进入临界区,本质上是 屏蔽了 从 configMAX_SYSCALL_INTERRUPT_PRIORITY~255
之间的中断?
答: 非常正确
6. 问: 优先级低于5的硬件中断都不会屏蔽吧?
答:
但是,一个芯片,他可能没有实现那么多的中断优先级
比如说:
#define configMAX_SYSCALL_INTERRUPT_PRIORITY 191
按理说, 191到255,都是使用SYSCALL的中断
但是:
#define configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY 5
怎么只有5呀?
显然:191是FreeRTOS理论上的优先级
5是HAL库能支持的优先级
STM32F103内部,对于每个中断,都有8位的优先级寄存器
但是,并没有实现所有的8位
比如:STM32F103只实现了多少位?我得查看一下芯片手册
芯片实现了多少位?我现在先不去查,假设有4位
这4位,又被分为两部分:抢占优先级,子优先级
抢占优先级是什么意思?
1.比如GPIOA的抢占优先级是1,GPIOB的抢占优先级是2
GPIOB先产生,先执行它的函数,然后GPIOA产生
A的抢占优先级更高, A可以抢占B,B的处理暂停,A先处理
这叫抢占优先级
2.子优先级是什么意思?
GPIOC、GPIOD的抢占优先级相同,都是1,但是子优先级不同,分别是3、4
GPIOC、GPIOD中断同时产生,谁先运行?
GPIOC的中断先运行,因为它的优先级是3,高于4
注意了:值越高,优先级越低
我们现在知道了
1.每个中断有8位优先级寄存器
2.芯片不一定全部实现这8位,比如说只实现了4位
3.这4位,可以拆分为:抢占优先级,子优先级
4.怎么拆分?还有一个“优先级组”寄存器
第4组是什么意思?
每个中段不是有4位寄存器吗?
这4位里面,哪些位用来表示抢占优先?哪些位用来表示子优先级?
使用第4组的时候:所有的4位都表示"抢占优先级",没有多余的位表示子优先级
那就是说:就这款芯片来说,它可以表示16个优先级
4位,表示16个优先级
他说是ST的库里面允许的优先级有16个:0到15
configLIBRARY_KERNEL_INTERRUPT_PRIORITY
这个宏并没有在代码里面用到
我们可以看看其他代码,看看他怎么去设置中断的优先级:
为什么FreeRTOS里优先级191,实际上落地后是芯片的优先级11 ?
191表示十进制,对应的二进制是10111111,高四位是1011
但是STM32F103只实现了高4位,高4位是1011,就是十进制11
7. 问: 老师,有个问题,按理来说任务中能调用的系统接口都可以实现一套中断也能调的接口,这样的话直接屏蔽中断就完了。
那么有那些接口是只有任务调用中断调用的?
换句话说freertos中有哪些资源是任务和任务直接竞争的?
答: 我来贴出两个函数:
为什么在操作队列的时候:屏蔽中断
为什么在设置事件组的时候:只需要关闭调度器?
1.操作队列的时候,可能发生中断,中断函数也使用同一个队列:所以要屏蔽中断
2.在设置事件组的时候:也可能发生中断呀?中断函数是不是也会去设置事件组呀?
先告诉大家答案:事件组函数xEventGroupSetBitsFromISR
,并不会直接设置事件组
而是:唤醒定时器任务,由定时器任务来设置事件组
也就是说:事件组的修改,只能由任务进行
换句话说freertos中有哪些资源是任务和任务直接竞争的?
答案:事件组
至于为什么不在中断里面直接设置事件组:
因为事件组可能会唤醒很多任务,会导致很复杂的队列操作,使得中断的处理时长不可控