怎么保护一个临界资源?
比如说一个全局变量,我们可以使用互斥量
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个话题:怎么实现互斥量?
简单粗暴:屏蔽中断
我要去修改互斥量,先屏蔽中断
我们来看一下代码:
答: 启动调度器,只是说可以重新调度了,并不是说去把本来就暂停的任务强制唤醒
答: 因为在任务函数里,任何函数运行时,中断状态就是使能的
答: 先回答第2个问题:中断函数里的中断却不一定是使能的
为什么任务里中断都是使能的?
1.启动第1个任务的时候,中断是使能的
2.任务关闭中断之前,中断是使能的
3.任务关闭中断后,要开启中断:关、开,是一一配对的
所以:任务里,平时,中断是使能的
答: 想想看我们有几类中断?会重新进入exti0的中断函数
有两类中断:使用syscall的,不使用syscall的
所以,这个中断状态指的是 “使用syscall函数的那类中断”,能否使能
configMAX_SYSCALL_INTERRUPT_PRIORITY~255
之间的中断?答: 非常正确
答:
但是,一个芯片,他可能没有实现那么多的中断优先级
比如说:
#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
那么有那些接口是只有任务调用中断调用的?
换句话说freertos中有哪些资源是任务和任务直接竞争的?
答: 我来贴出两个函数:
为什么在操作队列的时候:屏蔽中断
为什么在设置事件组的时候:只需要关闭调度器?
1.操作队列的时候,可能发生中断,中断函数也使用同一个队列:所以要屏蔽中断
2.在设置事件组的时候:也可能发生中断呀?中断函数是不是也会去设置事件组呀?
先告诉大家答案:事件组函数xEventGroupSetBitsFromISR
,并不会直接设置事件组
而是:唤醒定时器任务,由定时器任务来设置事件组
也就是说:事件组的修改,只能由任务进行
换句话说freertos中有哪些资源是任务和任务直接竞争的?
答案:事件组
至于为什么不在中断里面直接设置事件组:
因为事件组可能会唤醒很多任务,会导致很复杂的队列操作,使得中断的处理时长不可控