目前阅读的FreeRTOS资料很多都是基于11.0.0以前版本的,我阅读了一下最新的源码,发现FreeRTOS自11.0.0之后已经支持多CPU使用一个内核。可以在FreeRTOSconfig.h内看到。
FreeRTOS支持多核处理器的实现主要分为两种架构模式:
FreeRTOS-SMP是为多核系统专门设计的变种,引入了以下关键技术:
临界区是指在多任务环境中需要被原子化执行的代码段。FreeRTOS通过以下函数实现临界区保护:
void vTaskEnterCritical(void)
{
// 首先禁用所有中断,确保即将进入的代码不会被中断
portDISABLE_INTERRUPTS();
// 只有在调度器启动后才需要管理临界区嵌套计数
if(xSchedulerRunning != pdFALSE)
{
// 增加临界区嵌套计数,支持嵌套的临界区调用
portINCREMENT_CRITICAL_NESTING_COUNT();
// 多核系统特有:检查任务运行状态是否需要切换
#if (configNUMBER_OF_CORES > 1)
prvCheckForRunStateChange();
#endif
}
}
void vTaskExitCritical(void)
{
// 只有在调度器运行期间才需要管理临界区
if(xSchedulerRunning != pdFALSE)
{
// 确保存在有效的嵌套计数,防止嵌套错误
if(portGET_CRITICAL_NESTING_COUNT() > 0)
{
// 减少临界区嵌套计数
portDECREMENT_CRITICAL_NESTING_COUNT();
// 只有当嵌套计数归零时才重新启用中断
if(portGET_CRITICAL_NESTING_COUNT() == 0)
{
portENABLE_INTERRUPTS();
// 多核系统中,如果存在挂起的任务切换请求,立即处理
#if (configNUMBER_OF_CORES > 1)
if(xYieldPendings[portGET_CORE_ID()] == pdTRUE)
{
// 执行任务切换,响应之前被临界区阻止的切换请求
taskYIELD_WITHIN_API();
}
#endif
}
}
}
}
中断服务例程(ISR)中使用的临界区保护函数,提供更精细的中断控制:
UBaseType_t vTaskEnterCriticalFromISR(void)
{
UBaseType_t uxSavedInterruptStatus = 0;
// 保存当前中断状态并禁用可屏蔽中断
// 注意:这通常只禁用低于配置优先级的中断
uxSavedInterruptStatus = portSET_INTERRUPT_MASK_FROM_ISR();
// 如果调度器正在运行,增加临界区嵌套计数
if(xSchedulerRunning != pdFALSE)
{
portINCREMENT_CRITICAL_NESTING_COUNT();
}
// 返回保存的中断状态,以便后续恢复
return uxSavedInterruptStatus;
}
void vTaskExitCriticalFromISR(UBaseType_t uxSavedInterruptStatus)
{
if(xSchedulerRunning != pdFALSE)
{
// 减少临界区嵌套计数
portDECREMENT_CRITICAL_NESTING_COUNT();
// 恢复进入临界区前的中断状态
portCLEAR_INTERRUPT_MASK_FROM_ISR(uxSavedInterruptStatus);
}
}
FreeRTOS为中断服务例程提供了专用API,这些API遵循以下设计原则:
BaseType_t xTaskGenericNotifyFromISR(TaskHandle_t xTaskToNotify,
UBaseType_t uxIndexToNotify,
uint32_t ulValue,
eNotifyAction eAction,
uint32_t *pulPreviousNotificationValue,
BaseType_t *pxHigherPriorityTaskWoken)
{
// 验证当前中断优先级是否允许调用FreeRTOS API
// 防止高优先级中断使用可能导致死锁的API
portASSERT_IF_INTERRUPT_PRIORITY_INVALID();
// 安全进入临界区,保存中断状态
uxSavedInterruptStatus = taskENTER_CRITICAL_FROM_ISR();
// 根据指定操作更新任务通知值
// 可能是设置位、清除位、递增值或直接更新值
// 如果通知操作导致任务从阻塞状态解除且优先级高于当前任务
// 则设置pxHigherPriorityTaskWoken标志为pdTRUE
// 这将通知调用者在ISR结束时需要执行上下文切换
// 安全退出临界区,恢复中断状态
taskEXIT_CRITICAL_FROM_ISR(uxSavedInterruptStatus);
return xReturn; // 返回操作结果
}
BaseType_t xTaskResumeFromISR(TaskHandle_t xTaskToResume)
{
// 验证中断优先级是否允许调用FreeRTOS API
portASSERT_IF_INTERRUPT_PRIORITY_INVALID();
// 安全进入临界区
uxSavedInterruptStatus = taskENTER_CRITICAL_FROM_ISR();
// 检查任务是否真的处于挂起状态
if(prvTaskIsTaskSuspended(pxTCB) != pdFALSE)
{
// 从挂起列表中移除任务控制块
(void)uxListRemove(&(pxTCB->xStateListItem));
// 将任务添加到就绪列表中
prvAddTaskToReadyList(pxTCB);
// 检查被恢复的任务优先级是否高于当前任务
// 如果是,则需要在ISR结束后进行任务切换
if(pxTCB->uxPriority >= pxCurrentTCB->uxPriority)
{
// 标记需要执行上下文切换
xYieldRequired = pdTRUE;
}
}
// 安全退出临界区
taskEXIT_CRITICAL_FROM_ISR(uxSavedInterruptStatus);
return xYieldRequired; // 返回是否需要任务切换
}
FreeRTOS使用分层中断优先级系统,并对使用系统API的中断进行严格验证:
// 在每个ISR级API函数中都会调用此宏进行优先级验证
portASSERT_IF_INTERRUPT_PRIORITY_INVALID();
该机制确保:
configMAX_SYSCALL_INTERRUPT_PRIORITY
的中断能调用系统API调度器启动是系统初始化的关键步骤,完成了从单线程到多任务环境的转变:
void vTaskStartScheduler(void)
{
// 首先创建每个核心的空闲任务
// 空闲任务是任务调度的基础,当没有其他任务可运行时执行
xReturn = prvCreateIdleTasks();
// 如果配置了定时器服务,则创建定时器服务任务
#if (configUSE_TIMERS == 1)
xReturn = xTimerCreateTimerTask();
#endif
if(xReturn == pdPASS)
{
// 创建任务成功,准备启动调度器
// 禁用所有中断,确保后续操作的原子性
portDISABLE_INTERRUPTS();
// 初始化关键的调度器变量
xNextTaskUnblockTime = portMAX_DELAY; // 下一个需要解除阻塞的时间点
xSchedulerRunning = pdTRUE; // 标记调度器为运行状态
xTickCount = configINITIAL_TICK_COUNT; // 初始化系统时钟节拍计数
// 如果需要运行时统计,配置统计计时器
portCONFIGURE_TIMER_FOR_RUN_TIME_STATS();
// 调用端口层特定函数启动第一个任务
// 这个函数通常不会返回,除非发生错误
xPortStartScheduler();
}
}
任务上下文切换是多任务系统的核心,FreeRTOS实现了不同版本以支持单核和多核系统:
// 单核系统的上下文切换
void vTaskSwitchContext(void)
{
// 检查调度器是否挂起
if(uxSchedulerSuspended != 0U)
{
// 如果调度器挂起,仅记录需要切换的请求,不立即执行
xYieldPendings[0] = pdTRUE;
return;
}
// 调用宏选择最高优先级的就绪任务
// 这个宏会更新pxCurrentTCB指向新选择的任务
taskSELECT_HIGHEST_PRIORITY_TASK();
// 设置任务相关环境变量
// 例如线程局部存储(TLS)和追踪标记
#if (configUSE_TASK_NOTIFICATIONS == 1)
traceMOVED_TASK_TO_READY_STATE(pxCurrentTCB);
#endif
}
// 多核系统的上下文切换
void vTaskSwitchContext(BaseType_t xCoreID)
{
// 首先获取全局调度器锁,确保多核间调度的一致性
// 注意先获取任务锁再获取ISR锁,以避免死锁
portGET_TASK_LOCK();
portGET_ISR_LOCK();
// 为特定核心选择最高优先级任务
// 这个函数会考虑任务亲和性和优先级
taskSELECT_HIGHEST_PRIORITY_TASK(xCoreID);
// 操作完成后释放锁,顺序与获取相反
portRELEASE_ISR_LOCK();
portRELEASE_TASK_LOCK();
}
在SMP系统中,FreeRTOS实现了复杂的多核协作机制:
void vTaskCoreAffinitySet(const TaskHandle_t xTask, UBaseType_t uxCoreAffinityMask)
{
TCB_t *pxTCB;
UBaseType_t uxPrevCoreAffinityMask;
// 进入临界区保护任务控制块修改操作
taskENTER_CRITICAL();
{
// 获取任务控制块指针
pxTCB = prvGetTCBFromHandle(xTask);
// 保存旧的亲和性掩码,用于后续比较
uxPrevCoreAffinityMask = pxTCB->uxCoreAffinityMask;
// 更新任务的核心亲和性掩码
// 每一位代表一个核心,置1表示任务可在该核心上运行
pxTCB->uxCoreAffinityMask = uxCoreAffinityMask;
// 如果调度器已运行,检查是否需要触发任务迁移
if(xSchedulerRunning != pdFALSE)
{
// 如果任务当前正在某个核心上运行,但新的亲和性设置不允许在该核心上运行
// 则需要强制该任务从当前核心上让出CPU
if((taskTASK_IS_RUNNING(pxTCB) == pdTRUE) &&
((uxCoreAffinityMask & (1UL << pxTCB->xTaskRunState)) == 0UL))
{
// 要求包含该任务的核心执行任务切换
prvYieldCore(pxTCB->xTaskRunState);
}
}
}
// 退出临界区
taskEXIT_CRITICAL();
}
多核系统中,任务的动态分配需要考虑核心间的负载平衡和任务优先级:
static void prvYieldForTask(const TCB_t *pxTCB)
{
BaseType_t xLowestPriorityCore = -1;
BaseType_t xCoreID;
// 只有当任务优先级高于或等于当前最高优先级任务时才考虑让出CPU
if(pxTCB->uxPriority >= uxTopReadyPriority)
{
// 遍历所有核心,寻找运行优先级最低任务的核心
for(xCoreID = 0; xCoreID < configNUMBER_OF_CORES; xCoreID++)
{
// 比较各核心上当前运行任务的优先级
// 选择优先级最低的核心进行任务让步
// 这样可以确保高优先级任务能够尽快得到执行
}
// 如果找到合适的核心,通知其执行任务切换
if(xLowestPriorityCore >= 0)
{
prvYieldCore(xLowestPriorityCore);
}
}
}
static void prvSelectHighestPriorityTask(BaseType_t xCoreID)
{
UBaseType_t uxCurrentPriority = uxTopReadyPriority;
// 如果当前任务仍在就绪列表中,先将它移到对应优先级列表的末尾
// 这样可以实现同优先级任务的时间片轮转
if(listIS_CONTAINED_WITHIN(&pxReadyTasksLists[pxCurrentTCBs[xCoreID]->uxPriority],
&pxCurrentTCBs[xCoreID]->xStateListItem) == pdTRUE)
{
(void)uxListRemove(&pxCurrentTCBs[xCoreID]->xStateListItem);
vListInsertEnd(&pxReadyTasksLists[pxCurrentTCBs[xCoreID]->uxPriority],
&pxCurrentTCBs[xCoreID]->xStateListItem);
}
// 从最高优先级开始,查找可以在此核心上运行的就绪任务
while(xTaskScheduled == pdFALSE)
{
// 检查当前优先级列表是否有就绪任务
if(listLIST_IS_EMPTY(&pxReadyTasksLists[uxCurrentPriority]) == pdFALSE)
{
// 考虑任务的核心亲和性设置
#if(configUSE_CORE_AFFINITY == 1)
// 检查任务是否允许在当前核心上运行
// 只选择亲和性掩码与当前核心匹配的任务
#endif
// 更新当前核心的活动任务指针
pxCurrentTCBs[xCoreID] = pxTCB;
xTaskScheduled = pdTRUE;
}
else
{
// 如果当前优先级没有就绪任务,检查下一个优先级
--uxCurrentPriority;
}
}
}
FreeRTOS使用空闲任务执行系统维护工作并在没有其他任务可运行时提供基础处理:
static portTASK_FUNCTION(prvIdleTask, pvParameters)
{
// 为安全上下文分配所需内存(如适用)
portALLOCATE_SECURE_CONTEXT(configMINIMAL_SECURE_STACK_SIZE);
// 在SMP系统中,初始让步以允许应用任务启动
#if(configNUMBER_OF_CORES > 1)
taskYIELD();
#endif
// 空闲任务无限循环
for(;;)
{
// 处理待删除任务的资源清理
// 由于任务不能删除自己的资源,所以通过空闲任务完成最终清理
prvCheckTasksWaitingTermination();
// 执行用户配置的空闲钩子函数
#if(configUSE_IDLE_HOOK == 1)
vApplicationIdleHook();
#endif
// 低功耗管理:在没有其他任务需要执行时进入省电模式
#if(configUSE_TICKLESS_IDLE != 0)
// 根据下一个任务需要唤醒的时间计算可休眠时长
// 关闭不必要的外设和系统时钟
// 配置唤醒定时器并进入低功耗模式
#endif
}
}
#if(configNUMBER_OF_CORES > 1)
static portTASK_FUNCTION(prvPassiveIdleTask, pvParameters)
{
// 初始让步,确保应用任务能够启动
taskYIELD();
// 被动空闲任务也是无限循环
for(;;)
{
// 执行被动空闲钩子(如果配置)
// 这允许不同核心执行不同的空闲处理
#if(configUSE_PASSIVE_IDLE_HOOK == 1)
vApplicationPassiveIdleHook();
#endif
// 执行处理器特定的空闲处理函数
// 这可能包括特定于架构的电源管理操作
portIDLE_TASK_HOOK();
}
}
#endif
FreeRTOS提供了先进的低功耗管理机制,通过以下函数评估系统是否可以安全进入休眠状态:
#if(configUSE_TICKLESS_IDLE != 0)
eSleepModeStatus eTaskConfirmSleepModeStatus(void)
{
// 检查是否有挂起的就绪任务等待被处理
// 如果有则不能进入休眠
if(listCURRENT_LIST_LENGTH(&xPendingReadyList) != 0U)
return eAbortSleep;
// 检查是否有任务让步请求
// 这表明可能有更高优先级任务需要执行
if(xYieldPendings[portGET_CORE_ID()] != pdFALSE)
return eAbortSleep;
// 检查是否有挂起的时钟节拍需要处理
// 如果有,需要先处理这些节拍才能考虑休眠
if(xPendedTicks != 0U)
return eAbortSleep;
// 检查是否所有用户任务都处于挂起状态
// 即系统中只有空闲任务处于就绪或运行状态
#if(INCLUDE_vTaskSuspend == 1)
// 具体检查逻辑
#endif
// 所有条件均满足,系统可以安全进入休眠模式
return eStandardSleep;
}
#endif
FreeRTOS的中断管理和CPU管理是紧密结合的系统核心机制,下面详细分析几个关键流程:
中断处理是实时操作系统的核心能力,它遵循严格的流程确保系统稳定性:
中断触发与上下文保存:
ISR中系统API调用:
portASSERT_IF_INTERRUPT_PRIORITY_INVALID();
中断退出与潜在任务切换:
if(xHigherPriorityTaskWoken == pdTRUE)
{
portYIELD_FROM_ISR();
}
任务切换是实现多任务并发执行的基础机制:
切换触发源:
切换过程(硬件相关):
portSAVE_CONTEXT(); // 保存寄存器和栈指针到当前任务的栈
vTaskSwitchContext(); // 更新pxCurrentTCB指向最高优先级就绪任务
portRESTORE_CONTEXT(); // 从新任务的栈中恢复寄存器和栈指针
多核环境的特殊处理:
调度器挂起用于执行需要原子性的复杂操作序列:
调度器挂起过程:
vTaskSuspendAll(); // 增加uxSchedulerSuspended计数
// 执行需要原子操作的代码,此期间不会发生任务切换
xAlreadyYielded = xTaskResumeAll(); // 减少计数并处理挂起期间的事件
恢复时的关键处理:
这些机制和流程共同构成了FreeRTOS的中断和CPU管理核心,通过精心设计的同步和互斥机制,确保了实时操作系统的响应性、确定性和可靠性,同时提供了灵活的多核支持。
FreeRTOS通过精心设计的中断优先级管理机制支持中断嵌套:
中断优先级分层:
系统关键中断:优先级高于configMAX_SYSCALL_INTERRUPT_PRIORITY
的中断
系统兼容中断:优先级低于或等于configMAX_SYSCALL_INTERRUPT_PRIORITY
的中断
嵌套处理流程:
// 低优先级ISR被高优先级ISR打断的情况
void lowPriorityISR(void)
{
// 保存中断状态并进入临界区
UBaseType_t uxSaved = taskENTER_CRITICAL_FROM_ISR();
// 此时可能被高优先级中断打断
// 高优先级ISR执行完毕后会返回到这里
// 继续低优先级ISR的处理
// ...
// 恢复之前的中断状态
taskEXIT_CRITICAL_FROM_ISR(uxSaved);
}
中断控制器配置:
FreeRTOSConfig.h
中定义FreeRTOS-SMP提供了专门为多核系统设计的同步原语:
用于短时间的核间同步,避免任务切换开销:
// 典型的自旋锁实现(简化版)
void portTASK_LOCK_ACQUIRE(void)
{
// 尝试原子性地获取锁
while(!__atomic_compare_exchange_n(&xTaskLock,
&expected,
1,
false,
__ATOMIC_ACQUIRE,
__ATOMIC_RELAXED))
{
// 短暂自旋等待,优化处理器资源利用
portYIELD_PROCESSOR();
}
}
void portTASK_LOCK_RELEASE(void)
{
// 释放锁
__atomic_store_n(&xTaskLock, 0, __ATOMIC_RELEASE);
}
FreeRTOS-SMP实现了高效的核心间通信机制:
核心间中断触发:
核心间通知实现:
// 要求指定核心执行任务切换
static void prvYieldCore(BaseType_t xCoreID)
{
// 如果是当前核心,标记本地让步标志
if(xCoreID == portGET_CORE_ID())
{
xYieldPendings[xCoreID] = pdTRUE;
}
// 否则发送核心间中断
else
{
// 发送处理器间中断(IPI)到目标核心
portSEND_CROSS_CORE_INTERRUPT(xCoreID);
}
}
FreeRTOS通过巧妙设计实现了多核安全的资源访问:
全局锁机制:
获取锁的顺序规则:
// 安全的多锁获取顺序
portGET_TASK_LOCK(); // 必须先获取任务锁
portGET_ISR_LOCK(); // 然后获取ISR锁
// 执行需要保护的操作
// 释放锁的顺序与获取相反
portRELEASE_ISR_LOCK();
portRELEASE_TASK_LOCK();
核心间数据一致性:
FreeRTOS支持高级低功耗策略,使系统在无任务运行时进入深度休眠状态:
休眠条件分析:
// 深度休眠前进行全面系统状态检查
eSleepModeStatus eTaskConfirmSleepModeStatus(void)
{
// 各种检查项,确保系统可以安全休眠
// 决定休眠模式:标准休眠、深度休眠或放弃休眠
return eSleepModeStatus;
}
动态系统时钟调整:
// 根据下一个需要唤醒的时间点确定休眠时长
TickType_t prvGetExpectedIdleTime(void)
{
TickType_t xReturn;
// 只有当空闲任务是唯一就绪任务时才能深度休眠
if(uxTopReadyPriority > tskIDLE_PRIORITY)
{
// 有更高优先级任务就绪,不能进入休眠
xReturn = 0;
}
else
{
// 计算到下一个任务需要唤醒的时间
xReturn = xNextTaskUnblockTime - xTickCount;
}
return xReturn;
}
时钟节拍补偿:
// 在从低功耗模式唤醒后补偿错过的节拍
void vTaskStepTick(TickType_t xTicksToJump)
{
// 调整系统时钟计数
xTickCount += xTicksToJump;
// 确保未错过任何任务唤醒事件
if(xTickCount >= xNextTaskUnblockTime)
{
// 立即处理任务解阻塞
}
}
FreeRTOS-SMP支持复杂的多核系统电源策略:
核心独立休眠:
核心唤醒机制:
电源域管理:
// 核心特定空闲钩子函数可实现定制的电源管理策略
void vApplicationPassiveIdleHook(void)
{
// 当前核心ID
const BaseType_t xCoreID = portGET_CORE_ID();
// 检查特定核心的电源条件
if(prvCorePowerConditionsMet(xCoreID))
{
// 选择合适的低功耗模式
ePowerMode = prvSelectPowerMode();
// 执行特定于处理器的低功耗进入序列
prvEnterLowPowerMode(ePowerMode);
}
}
为实现高效稳定的系统,FreeRTOS建议采用以下设计模式:
延迟处理模式:
// 推荐的中断处理模式
void myISR(void)
{
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
// 快速读取硬件数据
uint32_t ulData = REG_READ(DATA_REG);
// 发送通知激活处理任务
xTaskNotifyFromISR(xProcessingTask,
ulData,
eSetValueWithOverwrite,
&xHigherPriorityTaskWoken);
// 如果解除了更高优先级任务的阻塞,请求任务切换
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}
优先级分配策略:
在FreeRTOS-SMP中实现最佳性能和响应性:
任务亲和性优化:
// 将两个紧密协作的任务绑定到同一核心
vTaskCoreAffinitySet(xTaskA, 1UL << 0); // 仅在核心0上运行
vTaskCoreAffinitySet(xTaskB, 1UL << 0); // 仅在核心0上运行
// 为实时控制任务保留核心1
vTaskCoreAffinitySet(xControlTask, 1UL << 1); // 仅在核心1上运行
// 在所有核心上运行通用任务
vTaskCoreAffinitySet(xGeneralTask, 0xFFUL); // 在所有核心上运行
临界段优化:
FreeRTOS提供多种工具帮助诊断中断和多核相关问题:
运行时统计功能:
多核事件追踪:
中断延迟测量:
// 测量中断延迟的简单方法
void vMeasureInterruptLatency(void)
{
// 设置硬件计时器/GPIO触发
// 配置测试中断
// 在中断处理程序中:
void testISR(void)
{
// 读取计时器/捕获值计算延迟
ulLatency = TIMER_GET_CAPTURE();
// 记录最大、最小和平均值
}
}
FreeRTOS的中断管理与CPU管理系统提供了一套全面且灵活的机制,支持从简单的单核应用到复杂的多核SMP系统。通过精心设计的临界区保护、中断优先级管理、多核同步原语以及低功耗策略,FreeRTOS能够满足各种嵌入式应用的需求。
开发者可以利用这些机制构建既高效又可靠的实时系统,同时在多核平台上充分发挥并行处理能力。理解并正确应用这些概念对于开发高性能、低功耗且响应迅速的嵌入式系统至关重要。
在实际应用中,应结合具体硬件平台特性和应用需求,选择合适的中断优先级策略、任务亲和性设置和电源管理方案,以达到最佳系统性能和实时响应能力。
https://gitee.com/nrush/FreeRTOS-Book
https://github.com/FreeRTOS/FreeRTOS
《FreeRTOS 内核实现与应用开发实战指南》