FreeRTOS(两套函数资源管理)

发布时间:2026/8/21 22:21:55
FreeRTOS(两套函数资源管理) 两类API的差别FreeRTOS使用两套函数的好处使用同一套函数的话需要增加额外的判断代码、增加额外的分支是的函数更长、更复杂、难以测试在任务、ISR 中调用时需要的参数不一样比如1、在任务中调用需要指定超时时间表示如果不成功就阻塞一会2、在 ISR 中调用不需要指定超时时间无论是否成功都要即刻返回3、如果强行把两套函数揉在一起会导致参数臃肿、无效移植 FreeRTOS 时还需要提供监测上下文的函数比如 is_in_isr ()有些处理器架构没有办法轻易分辨当前是处于任务中还是处于 ISR 中就需要额外添加更多、更复杂的代码使用两套函数可以让程序更高效但是也有一些缺点比如你要使用第三方库函数时即会在任务中调用它也会在 ISR 总调用它。这个第三方库函数用到了 FreeRTOS 的 API 函数你无法修改库函数。这个问题可以解决把中断的处理推迟到任务中进行 (Defer interrupt processing)在任务中调用库函数尝试在库函数中使用 “FromISR” 函数1、在任务中、在 ISR 中都可以调用 “FromISR” 函数2、反过来就不行非 FromISR 函数无法在 ISR 中使用第三方库函数也许会提供 OS 抽象层自行判断当前环境是在任务还是在 ISR 中分别调用不同的函数下面我来通过举例子说明为什么在中断中要使用 “FromISR” 函数在任务中调用xQueueSend函数的时候这个函数里面会发生什么呢往下看可以看到调用这个函数后这个函数就会写队列如果队列满了就失败阻塞阻塞时间自己决定如果队列为空那就写入数据然后唤醒接收队列数据的任务如果这个任务为更高优先级的任务调度器就会马上调用这个任务那在中断调用这个函数呢我假设A发数据B接收在中断中确实也可以把阻塞时间设置为0要么成功要么失败如果成功它就会唤醒B那么问题来了唤醒成功后如果这个B任务是更高优先级任务是否要立马切换呢答案是没有必要为什么呢因为在中断中中断的优先级就是最高的所以你在这里切换是没有意义的它不会立刻执行只有在中断处理完只会它才会切换。那怎么办呢这时我们只要标记一下是否要切换就好了唤醒这些任务的时候这些任务的优先级是不是比当前优先级的任务更高如果有更高的优先级任务那么这时我们只需要记录然后在中断处理完那些复杂任务在中断退出之前再切换这个任务就行了所以第二套函数就是解决这个不能立马切换任务的问题。使用第二套函数示例记得之前我们再红外回调函数中调用的就是第二套函数吗那时我们将第三个参数设置为NULL也就是不会在中断结束之前发起调度那这会发生什么呢往下看可以看到我们在中断结束前并没有选择发起调度然后中断结束这时任务A会继续执行要等到第二个中断系统才会切换到任务B然后在中断接收后才执行任务B这就好造成任务B延迟了一个Tick时间对于我们来说这一个Tick很快但对计算机来说这或许很重要那怎么发起调度呢代码示例voidXXX_ISR(){inti;BaseType_t xHigherPriorityTaskWokenpdFALSE;for(i0;iN;i){xQueueSendToBackFromISR(...,xHigherPriorityTaskWoken);/* 被多次调用 */}/* 最后再决定是否进行任务切换 * xHigherPriorityTaskWoken为pdTRUE时才切换 */portYIELD_FROM_ISR(xHigherPriorityTaskWoken);}xHigherPriorityTaskWoken 这个变量用来记录是否有更高优先级的任务被唤醒初始化为pdFALSE表示默认不需要切换在中断中调用xQueueSendToBackFromISR等 FromISR 函数时如果该函数唤醒了一个比当前任务优先级更高的任务就会把xHigherPriorityTaskWoken置为pdTRUE中断处理完所有事务后最后调用portYIELD_FROM_ISR(xHigherPriorityTaskWoken)该函数检测到参数为pdTRUE时就会在中断退出前发起一次任务调度切换到那个更高优先级的任务这样做的意义中断处理期间不立即切换任务中断优先级最高切换了也不会立刻执行而是先记录下来等中断里该做的事都做完、即将退出中断时再统一决定是否切换既保证了实时性又避免了中断中频繁切换带来的开销。资源管理前面我们学习互斥的时候介绍过使用全局变量可能会出现上图的情况。而我们学习的队列、信号量/互斥量、事件组它们就能解决这个问题那它们的互斥操作是如何实现的还记得我们在讲全局变量解决互斥的缺陷的时候介绍过的关闭中断的方法吗这没错除了关闭中断的方法还有关闭任务调度的方法而且队列、信号量/互斥量、事件组的互斥操作内部就是通过这两个实现的暂停中断代码/* 在任务中当前时刻中断是使能的 * 执行这句代码后屏蔽中断 */taskENTER_CRITICAL();/* 访问临界资源 *//* 重新使能中断 */taskEXIT_CRITICAL();暂停调度器代码/* 暂停调度器 */voidvTaskSuspendAll(void);/* 恢复调度器 * 返回值pdTRUE表示在暂定期间有更高优先级的任务就绪了 * 可以不理会这个返回值 */BaseType_txTaskResumeAll(void);其中队列和信号量/互斥量是通过关闭中断taskENTER_CRITICAL/taskEXIT_CRITICAL来实现互斥的而事件组则是通过关闭调度器vTaskSuspendAll/xTaskResumeAll来实现的可以看到事件组是在定时器任务中写入的而定时器任务本身就是一个普通任务它运行在任务上下文中并不会被中断打断。因此这里只需要关闭调度器vTaskSuspendAll就能保证写事件组这个操作的原子性不需要像队列、信号量那样去关闭中断。通过测出空闲栈的大小来调节每个人任务的栈原理这里的原理就是在这个栈填满0xa5a5a5a5当栈指针从上移动到下时会往里面填入数据返回地址局部变量之类的然后就会破会这些数据当从栈底部往上遍历这个栈数一下没有被破坏的空间有多大就就知道空闲的栈有多少了函数代码1、获取空闲栈的大小函数/*返回空闲栈的大小*/UBaseType_tuxTaskGetStackHighWaterMark(TaskHandle_t xTask);2、获取任务名字函数功能根据任务句柄拿到任务的名字创建任务时填写的任务名字符串。 参数 xTaskToQuery要查询的任务句柄。 返回值char*任务名字符串指针char*pcTaskGetName(TaskHandle_t xTaskToQuery);获取当前正在运行任务的句柄函数功能获取当前正在运行任务的句柄。 返回值当前任务的任务句柄 TaskHandle_t。 参数无参数。 用法xTaskHandlexTaskGetCurrentTaskHandle();TaskHandle_txTaskGetCurrentTaskHandle(void);代码示例UBaseType_t freeNum;TaskHandle_t xTaskHandle;while{.......//获取当前运行任务句柄xTaskHandlexTaskGetCurrentTaskHandle();//获取这个任务栈最小剩余(words)freeNumuxTaskGetStackHighWaterMark();//打印任务名字 剩余栈大小printf(FreeStack of Task %s : %d\n\r,pcTaskGetName(xTaskHandle),freeNum);}