大部分freertos api函数都有两个版本, 区别在于名称后面的FromISR
带有改后缀的, 称为中断安全函数
为什么执行同一功能的函数必须设计为两个版本呢
其根本原因是因为 在函数调用之中 如果使更高优先级的任务进入到就绪态, 该任务则会抢占当前任务
而在中断中不能进行上下文切换
那为了解决这个问题, freertos的设计采用了将其分为两个函数的做法
这样的优势是:
代码不用再去实现 辨别函数来源的逻辑 即不用去判定其是从中断中调用的还是从任务中调用的
确保传入的参数都是该函数会使用的, 在任务中和中断中调用函数时使用的api函数参数可能不同
不同freertos移植版本执行上下文机制不同
兼容不同架构, 中断和硬件有关
缺点呢:
在可能同时被中断和任务调用的函数中无法确定使用哪个函数
解决办法是使用 延迟中断处理, 即把中断处理函数统一由任务或进程守护任务调用, 而这些任务由中断进行唤醒
那么中断安全函数是如何解决无法在中断中切换上下文的呢
中断安全函数都有一个统一的参数xHigherPriorityTaskWoken
这个参数有两个值, pdTRUE和pdFALSE
通常初始化为pdFALSE
在执行完一个api函数(中断安全版)之后, 如果导致更高优先级的任务变为就绪态
则在api函数内部会将该参数改为pdTRUE
最后在ISR结尾处通过调用portYIELD_FROM_ISR( xHigherPriorityTaskWoken )来决定是否跳转
portYIELD_FROM_ISR是如何起作用的呢?
该函数会先判定woken参数是否为true, 如果为true则会悬起一个pendSV异常
这会导致当该ISR推出后 引起一个上下文切换
这样就避免了在中断中进行任务切换
示例如下, 使用的api函数为二进制信号量相关函数
1 | static uint32_t ulExampleInterruptHandler( void ) |
在讲信号量之前先来了解下延迟中断处理
由于ISR运行时会导致内核的短暂停止
这可能会导致 任务的开始时间和执行时间的抖动
所以必须保证ISR逻辑尽量简短
但世事总不那么如意
在必须处理复杂逻辑时该怎么办呢
就只能使用延迟中断处理了
延迟中断处理在检测到中断时 唤醒处理函数
然后在处理函数中进行逻辑的处理
ISR中只需要写唤醒函数的代码即可

那么ISR要如何唤醒一个任务呢
这里我们使用的是 信号量(官方推荐的是使用任务通知)
信号量分为二进制信号量和计数信号量
二进制信号量可以看作一个队列长度为1的, 队列内容无关的队列, 那么计数信号量就是队列长度不为1的, 队列内容无关的队列
使用流程:
创建一个信号量
中断处理任务中调用take(获取信号量函数)
在take发现信号量中没有信号时 该任务进入阻塞状态
当检测到中断发生
ISR中调用give(写信号量函数)
此时内核发现该信号量有值了, 通知中断处理任务进入就绪态
如果中断处理任务的优先级大于现有任务
则会将xHigherPriorityTaskWoken置为pdTRUE, 然后ISR返回后切换上下文为中断处理任务
中断处理任务take到信号量
执行中断处理逻辑
在下一轮执行时, take再次发现信号量为空, 进入阻塞态

二进制信号量的问题也很明显
在当中断处理函数take信号量之前 如果再次发生了一个中断
那么第二个中断将被忽略 于是有个计数信号量
作用机制是相同的, 只是多了能够处理多个中断

api函数:
1 | SemaphoreHandle_t xSemaphoreCreateBinary( void ); |
1 | BaseType_t xSemaphoreTake( SemaphoreHandle_t xSemaphore, TickType_t xTicksToWait |
1 | BaseType_t xSemaphoreGiveFromISR( SemaphoreHandle_t xSemaphore, |
1 | SemaphoreHandle_t xSemaphoreCreateCounting( UBaseType_t uxMaxCount, |
上文还提到可以将中断处理工作延时给守护进程任务
优点在于:
能够降低资源浪费率, 无需为每个延迟中断创建单独任务
简化用户模型: 延迟中断处理函数是一个标准的C函数
缺点在于:
由于都是适用的守护进程任务, 那么无法为每个中断处理设置单独的优先级
守护进程执行函数是通过队列来获取所需要的执行函数的, 可能ISR结束后无法立即执行中断处理函数
1 | BaseType_t xTimerPendFunctionCallFromISR( PendedFunction_t |
ISR中也是可以使用队列的, 但如果只是用来传递信号 那还是信号量相对更佳(最佳的还是 任务通知)
在进程中使用队列也需要使用ISR专用的中断安全api函数
在UART程序中, 可以使用队列来接收UART传送过来的数据, 然后通过延迟中断处理计数 在任务中进行处理
中断是可以嵌套的
每个中断源都有数值优先级和逻辑优先级的属性
逻辑优先级高的能抢占逻辑优先级低的中断 这点和裸机一样
逻辑优先级由数字优先级控制
在CM3中, 数字优先级低的逻辑优先级越高, 这点也和裸机一样

对于越高的优先级对ISR的执行事件越严格, 具体表现为是否能够使用api函数
CM3中最高循序使用8位来指定每个中断优先级
通常只使用八位数字的一部分
使用规范则是左对齐, 未使用的低有效位通常置为1