一,资源管理(Resource Management)

        引入:屏蔽 / 使能中断,暂停 / 恢复调度器

        (前面的互斥量,引入过临界资源的概念,已经实现临界资源的互斥访问),要独立地访问临界资源,有 3 种方法:

  • 公平竞争,如互斥量的获得
  • 屏蔽中断(中断争抢资源时)
  • 禁止调度器(不进行任务切换)

1,屏蔽中断(之后要使能中断)

任务中使用:taskENTER_CRITICA() / taskEXIT_CRITICAL()
ISR 中使用: taskENTER_CRITICAL_FROM_ISR() / taskEXIT_CRITICAL_FROM_ISR()

        taskENTER_CRITICA()/taskEXIT_CRITICAL()之间:低优先级的中断被屏蔽了,高优先级的中断可以产生;分界点是宏设置的configMAX_SYSCALL_INTERRUPT_PRIORITY。

        【注意】:

  1. 这部分中断 ISR 里,不允许使用 FreeRTOS 的 API 函数(任务调度依赖于中断、依赖于 API 函数,所以:这两段代码之间,不会有任务调度产生
  2. 可以递归使用,内部会记录嵌套的深度
  3. 用屏蔽中断的方法访问临界资源很粗鲁(中断、任务调度无法正常运行),要尽可能快的执行
/* 在任务中,当前时刻中断是使能的
* 执行这句代码后,屏蔽中断*/
taskENTER_CRITICAL();

/* 访问临界资源 */

/* 重新使能中断 */
taskEXIT_CRITICAL();
void vAnInterruptServiceRoutine( void )
{
/* 用来记录当前中断是否使能 */
UBaseType_t uxSavedInterruptStatus;

/* 在 ISR 中,当前时刻中断可能是使能的,也可能是禁止的
* 所以要记录当前状态, 后面要恢复为原先的状态
* 执行这句代码后,屏蔽中断
*/
uxSavedInterruptStatus = taskENTER_CRITICAL_FROM_ISR();

/* 访问临界资源 */

/* 恢复中断状态 */
taskEXIT_CRITICAL_FROM_ISR( uxSavedInterruptStatus );
/* 现在,当前 ISR 可以被更高优先级的中断打断了 */
}

2,暂停调度器

        关中断的代价太大了,如果只是禁止别的任务竞争,暂停调度器就可以了:在这期间, 中断还是可以发生、处理。(也可递归使用)

vTaskSuspendScheduler();

/* 访问临界资源 */

xTaskResumeScheduler();

【写队列xQueueSend保证互斥操作通过先关闭中断实现,xQueueSendFromISR内部也关中断,而且有保存中断状态操作;事件组通过调度器开闭实现,任务间抢占临界资源不需要关中断】

    示例:I2C 的互斥访问 (之前用 GetI2C获得互斥量,PutI2C释放互斥量 ),可改为暂停调度器 与 恢复调度器。【问题:若调度器暂停时间过长,其它任务都无法执行;用信号量、互斥量更合理】

二,调试与优化

1,调试

        FreeRTOS 提供了很多调试手段:打印、断言 configASSERT、Trace、Hook 函数(回调函数)。详见手册 19章。

    判断栈溢出的方法有两种:

  • 当前任务被切换出去之前,它的整个运行现场都被保存在栈里,这时很可能就是它对栈的使用到达了峰值。(高效但不准确(运行过程中A函数 用了大量的栈,但之后才调度A函数))
  • 在栈中填入固定值:如 0xa5,检测栈里最后 16 字节的数据,如果不是 0xa5 的话表示栈即将、或者已经被用完了。(效率不如方法 1,能捕获几乎所有的栈溢出)

2,优化

        Win 中系统卡断时,可查看任务管理器找到消耗 CPU 资源多的程序。

        FreeRTOS 中,也可以 查看任务使用CPU的情况、使用栈的情况,然后针对性地进行优化。即 “任务的统计”信息。

1)栈的使用情况:

        在创建任务时分配了栈,可以填入固定的数值比如 0xa5,以后可以使用以下函数查看" 栈的高水位",也就是还有多少空余的栈空间(原理是:从栈底往顶逐个字节判断,值持续是 0xa5 就表明空闲):

UBaseType_t uxTaskGetStackHighWaterMark( TaskHandle_t xTask );

2)任务运行时间统计

        Tick 中断函数统计当前任务的累计运行时间不精确(有更高优先级的任务就绪时,当前任务还没运行一个完整的Tick 就被抢占了),需要比 Tick 更快的时钟来衡量任务运行时间

        代码执行流程:在任务切换时统计运行时间

  • uxTaskGetSystemState:获得任务的统计信息,在一个 TaskStatus_t 结构体里
UBaseType_t uxTaskGetSystemState( TaskStatus_t * const pxTaskStatusArray, 
                                  const UBaseType_t uxArraySize, 
                                  uint32_t * const pulTotalRunTime );
vTaskList :获得任务的统计信息,形式为可读的字符串。
void vTaskList( signed char *pcWriteBuffer );

vTaskGetRunTimeStats:获得任务的运行信息,形式为可读的字符串。
void vTaskGetRunTimeStats( signed char *pcWriteBuffer )

Logo

openvela 操作系统专为 AIoT 领域量身定制,以轻量化、标准兼容、安全性和高度可扩展性为核心特点。openvela 以其卓越的技术优势,已成为众多物联网设备和 AI 硬件的技术首选,涵盖了智能手表、运动手环、智能音箱、耳机、智能家居设备以及机器人等多个领域。

更多推荐