如何学习并掌握一个中间件
静态路线: 阅读代码、官方资料、官方例程、Beyond Compare
动态路线:使用 jlink 实时仿真,跟随代码。
基础知识储备:比如 lwip 要有 tcp/ip 的基础。rtos 要有裸机编程,CPU架构,任务切换、堆和栈
通用RTOS观点
1. 实时操作系统,以任务为调度单位。典型地,任务切换,中断服务程序,都能涉及到上下文切换,切入切出,都是独立的、严谨的、过程也是完整的。任何RTOS的设计,都需要考虑到任务上下文切换,也需要考虑中断具体行为。中断服务与具体硬件MCU内核设计相关,且通常中断服务程序的优先级都要高于RTOS的任务。通常,任务调度过程寄托于MCU系统时钟中断服务程序(timer ISR for OS tick scheduler)。Armv7-M 架构的MCU中断具体行为参考《Arm®v7-M_Architecture - Reference Manual》的 B1.5.6 Exception entry behavior 、B1.5.8 Exception return behavior 等。
中断行为的详细分析[todo]
2. 对于资源相对紧张相对不高级的MCU,使用类似 μCOS-II 超级经典的位图调度算法;对于资源相对丰富相对高级的MCU(stm32f103,支持前置导零指令的)使用类似 μCOS-III 、FreeRTOS 前置导零汇编指令的调度算法,这种硬件指令加持情况,拥有更快的响应速度(当然位图软件调度算法已经足够快了),且在内核设计方面,使用同优先级链表,更容易实现支持同任务优先级的时间片轮转调度。
3. 任务初始化,必然涉及到内核管理任务数据结构的方面,包括保存任务信息的任务控制块(TaskControlBlock)TCB、调度信息、通信信息。任务初始化还必然涉及到任务函数栈区的初始化,也就是第一次PC的值,必然是任务函数的首地址;通常任务的栈空间来自RTOS内存管理大堆区中动态申请的一片RAM区域或者静态分配。
4. 常规普通变量,比如 int a; struct b;,在经典的前后台系统中,不存在变量竞争导致的逻辑错乱,但是!,在RTOS中引入了多任务对象,涉及到上下文切换,可能存在变量的非原子错误操作,因此在编写RTOS相关的代码时,还是老老实实地使用任务通信机制的支持,除非保证(全局)变量只被一个固定的任务使用。也许您会疑问:为什么RTOS内部普遍使用的是常规普通变量和没有保护机制的链表队列,那是因为RTOS的内部代码不参与应用代码,是单独的内部存在交互,也仅活动在一个栈空间,也就是不涉及多任务切换,所以不存在那种竞争带来的负面影响。
5. 资源管理
上文说了不能再多任务OS中公用全局变量,变量也是一种资源,操作也是一种资源,比如原子操作。换言之,就是数据和算法可能都是敏感资源,所以OS业务代码在使用时,需要保证完整性、一致性、原子性。
数据上的可以有任务同步,任务通信(队列、邮箱、信号量、互斥量等)
FreeRTOS的调度算法
FreeRTOS中提供的任务调度器是基于优先级的全抢占式调度:在系统中除了中断处理函数、调度器上锁部分的代码和禁止中断的代码是不可抢占的之外,系统的其他部分都是可以抢占的。系统理论上可以支持无数个优先级(0 ~ N,优先级数值越小的任务优先级越低,0为最低优先级,分配给空闲任务使用,一般不建议用户来 使用这个优先级。假如使能了configUSE_PORT_OPTIMISED_TASK_SELECTION这个宏(在FreeRTOSConfig.h文件定义),一般强制限定最大可用优先级数目为32。在一些资源比较紧张的系统中,可以根据实际情况选择只支持8个或32个优先级的系统配置。在系统中,当有比当前 任务优先级更高的任务就绪时,当前任务将立刻被换出,高优先级任务抢占处理器运行。
一个操作系统如果只是具备了高优先级任务能够“立即”获得处理器并得到执行的特点,那么它仍然不算是实时操作系统。因为这个查找最高优先级任务的过程决定了调度时间是否具有确定性,例如一个包含n个就绪任务的系统中,如果仅仅从头找到尾,那么这个时间将直接和n相关,而下一个就绪任务抉择时间的长短将会极大的影响系统 的实时性。
FreeRTOS内核中采用两种方法寻找最高优先级的任务,第一种是通用的方法,在就绪链表中查找从高优先级往低查找uxTopPriority,因为在创建任务的时候已经将优先级进行排序,查找到的第一个uxTopPriority就是我们需要的任务,然后通过uxTopPriority获取对应的任务控制块。第二 种方法则是特殊方法,利用计算前导零指令CLZ,直接在uxTopReadyPriority这个32位的变量中直接得出uxTopPriority,这样子就知道哪一个优先级任务能够运行,这种调度算法比普通方法更快捷,但受限于平台(在I.MX RT中我们就使用这种方法)。
实时维护更新就绪的任务,也就是维护 uxTopReadyPriority 这个32位全局变量,每个位对应一个任务的就绪状态。也就是说,若某个优先级从未就绪状态转为就绪状态,那么内核程序将会在uxTopReadyPriority中把对应优先级的位将从“0”改成“1”。
调度方式:纯软件调度和CLZ汇编指令参与的硬件调度
CLZ汇编指令参与的硬件调度,类似ucosii的位图调度算法。如下图所示:
这个是纯软件调度算法,就做了个循环自减操作,其他的都不需要做。
补充一个更详细的图,说明每刷新一个任务,就在 uxTopReadyPriority 中,置位/清零(1/0)对应的位。
其中, xTaskIncrementTick 函数:
/* 此函数为内核心跳服务函数,更新内核状态,涉及任务切换,重要! */
/*----------------------------------------------------------*/
BaseType_t xTaskIncrementTick( void )
{
TCB_t * pxTCB;
TickType_t xItemValue;
BaseType_t xSwitchRequired = pdFALSE;
/* Called by the portable layer each time a tick interrupt occurs.
* Increments the tick then checks to see if the new tick value will cause any
* tasks to be unblocked. */
traceTASK_INCREMENT_TICK( xTickCount );
if( uxSchedulerSuspended == ( UBaseType_t ) pdFALSE )
{
/* Minor optimisation. The tick count cannot change in this
* block. */
const TickType_t xConstTickCount = xTickCount + ( TickType_t ) 1;
/* Increment the RTOS tick, switching the delayed and overflowed
* delayed lists if it wraps to 0. */
xTickCount = xConstTickCount;
if( xConstTickCount == ( TickType_t ) 0U ) /*lint !e774 'if' does not always evaluate to false as it is looking for an overflow. */
{
taskSWITCH_DELAYED_LISTS();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
/* See if this tick has made a timeout expire. Tasks are stored in
* the queue in the order of their wake time - meaning once one task
* has been found whose block time has not expired there is no need to
* look any further down the list. */
if( xConstTickCount >= xNextTaskUnblockTime )
{
for( ; ; )
{
if( listLIST_IS_EMPTY( pxDelayedTaskList ) != pdFALSE )
{
/* The delayed list is empty. Set xNextTaskUnblockTime
* to the maximum possible value so it is extremely
* unlikely that the
* if( xTickCount >= xNextTaskUnblockTime ) test will pass
* next time through. */
xNextTaskUnblockTime = portMAX_DELAY; /*lint !e961 MISRA exception as the casts are only redundant for some ports. */
break;
}
else
{
/* The delayed list is not empty, get the value of the
* item at the head of the delayed list. This is the time
* at which the task at the head of the delayed list must
* be removed from the Blocked state. */
pxTCB = listGET_OWNER_OF_HEAD_ENTRY( pxDelayedTaskList ); /*lint !e9079 void * is used as this macro is used with timers and co-routines too. Alignment is known to be fine as the type of the pointer stored and retrieved is the same. */
xItemValue = listGET_LIST_ITEM_VALUE( &( pxTCB->xStateListItem ) );
if( xConstTickCount < xItemValue )
{
/* It is not time to unblock this item yet, but the
* item value is the time at which the task at the head
* of the blocked list must be removed from the Blocked
* state - so record the item value in
* xNextTaskUnblockTime. */
xNextTaskUnblockTime = xItemValue;
break; /*lint !e9011 Code structure here is deemed easier to understand with multiple breaks. */
}
else
{
mtCOVERAGE_TEST_MARKER();
}
/* It is time to remove the item from the Blocked state. */
listREMOVE_ITEM( &( pxTCB->xStateListItem ) );
/* Is the task waiting on an event also? If so remove
* it from the event list. */
if( listLIST_ITEM_CONTAINER( &( pxTCB->xEventListItem ) ) != NULL )
{
listREMOVE_ITEM( &( pxTCB->xEventListItem ) );
}
else
{
mtCOVERAGE_TEST_MARKER();
}
/* Place the unblocked task into the appropriate ready
* list. */
prvAddTaskToReadyList( pxTCB );
/* A task being unblocked cannot cause an immediate
* context switch if preemption is turned off. */
#if ( configUSE_PREEMPTION == 1 )
{
/* Preemption is on, but a context switch should
* only be performed if the unblocked task's
* priority is higher than the currently executing
* task.
* The case of equal priority tasks sharing
* processing time (which happens when both
* preemption and time slicing are on) is
* handled below.*/
if( pxTCB->uxPriority > pxCurrentTCB->uxPriority )
{
xSwitchRequired = pdTRUE;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
#endif /* configUSE_PREEMPTION */
}
}
}
/* Tasks of equal priority to the currently running task will share
* processing time (time slice) if preemption is on, and the application
* writer has not explicitly turned time slicing off. */
#if ( ( configUSE_PREEMPTION == 1 ) && ( configUSE_TIME_SLICING == 1 ) )
{
if( listCURRENT_LIST_LENGTH( &( pxReadyTasksLists[ pxCurrentTCB->uxPriority ] ) ) > ( UBaseType_t ) 1 )
{
xSwitchRequired = pdTRUE;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
#endif /* ( ( configUSE_PREEMPTION == 1 ) && ( configUSE_TIME_SLICING == 1 ) ) */
#if ( configUSE_TICK_HOOK == 1 )
{
/* Guard against the tick hook being called when the pended tick
* count is being unwound (when the scheduler is being unlocked). */
if( xPendedTicks == ( TickType_t ) 0 )
{
vApplicationTickHook();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
#endif /* configUSE_TICK_HOOK */
#if ( configUSE_PREEMPTION == 1 )
{
if( xYieldPending != pdFALSE )
{
xSwitchRequired = pdTRUE;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
#endif /* configUSE_PREEMPTION */
}
else
{
++xPendedTicks;
/* The tick hook gets called at regular intervals, even if the
* scheduler is locked. */
#if ( configUSE_TICK_HOOK == 1 )
{
vApplicationTickHook();
}
#endif
}
return xSwitchRequired;
}
FreeRTOS 实战问题及解决方案
1. 中断卡在configASSERT( ucCurrentPriority >= ucMaxSysCallPriority );
问题描述:
在使用stm32f4-discovery的FreeRTOS是设置一个按键中断,但是中断发生之后程序就卡住了,通过调试,发现程序卡在port.c中的configASSERT()语句。通过查找资料发现是中断优先级设置的问题。
翻译:如果优先级高于 configMAX_SYSCALL_INTERRUPT_PRIORITY 的中断的服务例程 (ISR) 调用 ISR 安全 FreeRTOS API 函数,则以下断言将失败。ISR 安全 FreeRTOS API 函数只能从优先级等于或低于 configMAX_SYSCALL_INTERRUPT_PRIORITY 的中断调用。
网上的解决方案:
在使用stm32f4-discovery板的FreeRTOS系统中,遇到一个中断问题,中断发生后程序卡在了configASSERT()。原因是中断优先级设置过高,超过了configMAX_SYSCALL_INTERRUPT_PRIORITY,导致在中断中调用操作系统API不安全。修正方法是降低中断优先级到5或以上,以确保符合FreeRTOS的中断调用规范。
解决方案:
问题原因:
确实是因为外设中断优先级设置错误导致,反复通过实验验证OK。
处理过程:
起初,检查了代码中 EXTI Line3 的中断优先级设置,没发现问题,然后就怀疑人生了。因为没有头绪,但是转念,我一定要验证此优先级实际现场是否正确。
其次,仿真调试摸索调试外设中断的方法,网上超级少,或者根本没有。也就是 View->SystemViewer->CorePeripherals->NVIC.
然后,保证 systick timer 的优先级为 0 且 EXTI Line3 的中断优先级 >= 0x50;在 NVIC 中可以手动实时设置。
最后,验证通过,反复复核,验证OK,没问题。