FreeRTOS记录(三、RTOS任务调度原理解析_Systick、PendSV、SVC)_systick,pendsv 和 svc(1)

img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上物联网嵌入式知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、电子书籍、讲解视频,并且后续会持续更新

需要这些体系化资料的朋友,可以加我V获取:vip1024c (备注嵌入式)

如果你需要这些资料,可以戳这里获取

*/
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.
1、从任务延时列表中,获取第一个任务控制块
2、延时列表,插入永远是把时间片最短的任务,放在第一个
3、获取任务控制块的延时时间
*/
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 deedmed easier to understand with multiple breaks. */
}
else
{
mtCOVERAGE_TEST_MARKER();
}

				/\* 

It is time to remove the item from the Blocked state.
把任务从延时列表中移除
*/
( void ) uxListRemove( &( 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 )
{
( void ) uxListRemove( &( 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 has a
priority that is equal to or higher than the
currently executing task.
判断优先级是否大于当前任务
大于则进行调度
*/
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.
时间片处理机制
1、获取就绪列表长度
2、就绪列表指的是,当前任务优先级的列表
3、如果有其他任务在就绪列表中,就开始调度
*/
#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( uxPendedTicks == ( UBaseType_t ) 0U )
{
vApplicationTickHook();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
#endif /* configUSE_TICK_HOOK */
}
else //内核调度器挂起了
{
++uxPendedTicks;//挂起的tick+1

	/\* The tick hook gets called at regular intervals, even if the

scheduler is locked. */
#if ( configUSE_TICK_HOOK == 1 )
{
vApplicationTickHook();
}
#endif
}
/*
如果是抢占模式,要开启调度
*/
#if ( configUSE_PREEMPTION == 1 )
{
if( xYieldPending != pdFALSE )
{
xSwitchRequired = pdTRUE;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
#endif /* configUSE_PREEMPTION */

return xSwitchRequired;//返回调度器状态

}


### Systick优先级分析


结合后面的中断管理和任务调度相关的内容,需要说明一下Systick优先级的问题。先来看一下简单的任务调度模型。


![在这里插入图片描述](https://img-blog.csdnimg.cn/b8f4deb5765d419dba41fd24fc542e4b.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA55-c6L6w5omA6Ie0,size_20,color_FFFFFF,t_70,g_se,x_16)


在上面图示中,可以看到优先级SysTick优先级最高!那么这和我们常听到的SysTick优先级需要设置为最低优先级怎么相互冲突呢?初学者往往在这个问题上感到困惑。


首先要明白:SysTick是中断,中断优先级和任务优先级没有任何关系,不管中断优先级是多少,中断的优先级永远高于任何线程任务的优先级


那么在上图中的线程,不管什么线程,SysTick中断来了肯定是需要去执行SysTick中断事件的。


上图中还有一个IRQ,比SysTick优先级低,这也是可能的,但是实际上我们应用过程中,一般都把SysTick优先级设置为最低,因为不想让SysTick中断打断用户的IRQ中断。


那么SysTick中断优先级和外设中断优先级是怎么确定的?


1、SysTick属于内核异常,用SHPRx(x=1.2.3)来设置其优先级;外设中断属于 ISR,用NVIC\_IPRx来设置优先级。


SPRH1-SPRH3是一个32位的寄存器,只能通过字节访问,每8个字段控制着一个内核外设的中断优先级的配置。位7:4这高四位有效,所以可编程为0 ~ 15。如果软件优先级配置相同,那就根据他们在中断向量表里面的位置编号来决定优先级大小,编号越小,优先级越高。


对于SysTick的配置,系统默认配置为15,`(1UL << __NVIC_PRIO_BITS) - 1UL)` 在m3、m4中`__NVIC_PRIO_BITS`为4:



/*
core_cm0/3/4.h中关于SysTick_Config
m3 m4 中是 4 ,4位就是0~15
#define __NVIC_PRIO_BITS 4U
m0 中是2, 2位就是 0 ~ 3
#endif
*/
__STATIC_INLINE uint32_t SysTick_Config(uint32_t ticks)
{
if ((ticks - 1UL) > SysTick_LOAD_RELOAD_Msk)
{
return (1UL); /* Reload value impossible */
}

SysTick->LOAD = (uint32_t)(ticks - 1UL); /* set reload register */
NVIC_SetPriority (SysTick_IRQn, (1UL << __NVIC_PRIO_BITS) - 1UL); /* set Priority for Systick Interrupt */
SysTick->VAL = 0UL; /* Load the SysTick Counter Value */
SysTick->CTRL = SysTick_CTRL_CLKSOURCE_Msk |
SysTick_CTRL_TICKINT_Msk |
SysTick_CTRL_ENABLE_Msk; /* Enable SysTick IRQ and SysTick Timer */
return (0UL); /* Function successful */
}

/*
NVIC_SetPriority对中断分了类,分内核中断和外设中断,
内核外设中断枚举值小于0,普通外设>=0。其中,SysTick_IRQn = -1。
*/
__STATIC_INLINE void NVIC_SetPriority(IRQn_Type IRQn, uint32_t priority)
{
if ((int32_t)(IRQn) < 0)
{
SCB->SHP[(((uint32_t)(int32_t)IRQn) & 0xFUL)-4UL] = (uint8_t)((priority << (8U - __NVIC_PRIO_BITS)) & (uint32_t)0xFFUL);
}
else
{
NVIC->IP[((uint32_t)(int32_t)IRQn)] = (uint8_t)((priority << (8U - __NVIC_PRIO_BITS)) & (uint32_t)0xFFUL);
}
}


2、NVIC的中断优先级分组不仅对片上外设有效,同样对内核的外设也有效。


systick的优先级15转换成二进制值就是1111,又因为NVIC的优先级分组2,那么前两位的11就是3,3抢占,后两位的11也是3,3子优先级。这样就可以和外设的优先级进行对比。


如果外设中断的优先级也分成了15,无论怎么分组,SYSTICK优先级高于同优先级的外设(毕竟内核异常优先级高于外设中断,因为中断向量表里面的位置编号内核的靠前更小)。


3、设置systick优先级的方法`NVIC_SetPriority (SysTick_IRQn, (1<<__NVIC_PRIO_BITS) - 15);` 即`SCB->SHP[11] = 0x00;`设置最高的话可以得到精准延时,但是会频繁打断用户使用的中断程序,不建议。


## 内核中断管理


中断是微处理器外部发送的,通过中断通道送入处理器内部,一般是硬件引起的;  
 而异常通常是微处理器内部发生的,大多是软件引起的,比如除法出错异常,特权调用异常。


### Cortex-M的异常类型


如下图:


![在这里插入图片描述](https://img-blog.csdnimg.cn/4617c2817f394d50b5b421a7aefe3aac.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA55-c6L6w5omA6Ie0,size_20,color_FFFFFF,t_70,g_se,x_16#pic_center)


### Cortex-M的寄存器


如下图:  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/dcd8d224276744919bac09112adfbc11.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA55-c6L6w5omA6Ie0,size_20,color_FFFFFF,t_70,g_se,x_16)


这个图主要记住 R13 寄存器,有两个指针:MSP: 主栈指针 和 PSP: 进程栈指针,相关说明如下:


![在这里插入图片描述](https://img-blog.csdnimg.cn/72c0483b90374981bb9f328ac053b943.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA55-c6L6w5omA6Ie0,size_20,color_FFFFFF,t_70,g_se,x_16)


### Cortex-M的特殊寄存器


如下图:


![在这里插入图片描述](https://img-blog.csdnimg.cn/67d6fb429c0045d7a5ece5a19c72190f.png)


#### xPSR


组合程序状态寄存器,该寄存器由三个程序状态寄存器组成


应用PSR(APSR) : 包含前一条指令执行后的条件标志  
 中断PSR(IPSR) : 包含当前ISR的异常编号  
 执行PSR(EPSR) : 包含Thumb状态位


![在这里插入图片描述](https://img-blog.csdnimg.cn/7dcdec8738a2460da69a9137c088a849.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA55-c6L6w5omA6Ie0,size_20,color_FFFFFF,t_70,g_se,x_16)


#### PRIMSK


PRIMSK:中断屏蔽特殊寄存器。


利用PRIMSK,可以禁止除HardFault 和 NMI外的所有异常。


![在这里插入图片描述](https://img-blog.csdnimg.cn/2d257a1d3ed04a60a252e8fddc98e322.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA55-c6L6w5omA6Ie0,size_20,color_FFFFFF,t_70,g_se,x_16)


![在这里插入图片描述](https://img-blog.csdnimg.cn/c1c70bf9004f4947bcc45a61cc028ada.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA55-c6L6w5omA6Ie0,size_20,color_FFFFFF,t_70,g_se,x_16)


#### BASEPRI


利用BASEPRI寄存器来选择屏蔽低于特定优先级的异常或中断。(在上一篇博文中的进入临界区所使用的寄存器就是这个寄存器)


![在这里插入图片描述](https://img-blog.csdnimg.cn/7ae4c6622f814c6fa5639d125e9d90e2.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA55-c6L6w5omA6Ie0,size_20,color_FFFFFF,t_70,g_se,x_16)


#### CONTROL


CONTROL:控制寄存器,部分介绍如下:


![在这里插入图片描述](https://img-blog.csdnimg.cn/0dd88a3f0002407bb0fc769cdee47e98.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA55-c6L6w5omA6Ie0,size_20,color_FFFFFF,t_70,g_se,x_16)


![在这里插入图片描述](https://img-blog.csdnimg.cn/f22325e3cce142f28644f4bd8feb3a92.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA55-c6L6w5omA6Ie0,size_12,color_FFFFFF,t_70,g_se,x_15)  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/aad5b5cd877d4606b192baf21c154963.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA55-c6L6w5omA6Ie0,size_12,color_FFFFFF,t_70,g_se,x_15)  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/19cb16d808b947339af7a69a84627e08.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA55-c6L6w5omA6Ie0,size_20,color_FFFFFF,t_70,g_se,x_16)


### Cortex-M的工作模式


Cortex-M有两种工作模式和两种工作状态:


线程模式(Thread Mode):芯片复位后,进入线程模式,执行用户程序;


处理模式(Handler Mode):当处理器发生了异常或者中断,则进入处理模式,处理完后返回线程模式。


Thumb状态: 正常运行时处理器的状态


调试状态:调试程序时处理器的状态


![在这里插入图片描述](https://img-blog.csdnimg.cn/22b54310dd8f4faa852497d98446ed94.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA55-c6L6w5omA6Ie0,size_10,color_FFFFFF,t_70,g_se,x_16)


进入Systick后,发生异常,则进入处理模式进行处理:


如果是裸机编程,从哪里进去就返回哪里


但是用了操作系统,该返回哪里呢?


所以这里就有必要单独讲解下MSP和PSP


## 影子栈指针


在上面的Cortex-M的寄存器图中我们标注过R13寄存器:


堆栈指针SP。


在处理模式下,只能使用主堆栈(MSP)。


在线程模式下,可以使用主堆栈也可以使用进程栈。


由 CONTROL 寄存器控制,如下:


![在这里插入图片描述](https://img-blog.csdnimg.cn/8c6c6eee41ee4434afbb3bba93720a03.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA55-c6L6w5omA6Ie0,size_20,color_FFFFFF,t_70,g_se,x_16)![在这里插入图片描述](https://img-blog.csdnimg.cn/73a09ffcec404ade90c009e1403fef89.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA55-c6L6w5omA6Ie0,size_20,color_FFFFFF,t_70,g_se,x_16)


## PendSV和SVC异常


PendSV异常用于任务切换。


为了保证操作系统的实时性,除了使用Systick的时间片调度,还得加入pendSV异常加入抢占式调度。


PendSV(可挂起的系统调用),异常编号为14,可编程。可以写入中断控制和状态寄存器(ICSR)设置挂起位以触发 PendSV异常。它是不精确的。因此,它的挂起状态可以在更高优先级异常处理内设置,且会在高优先级处理完成后执行。


### 为什么需要 PendSV异常?


如下图所示,如果中断请求在Systick异常前产生,则Systick可能会抢占IRQ处理(图中的IRQ优先级小于Systick)。这样执行上下文切换会导致IRQ延时处理,这种行为在任何一种实时操作系统中都是不能容忍的,在CortexM3中如果OS在活跃时尝试切入线程模式,将触发Fault异常。


![在这里插入图片描述](https://img-blog.csdnimg.cn/366308c5b15143ac93faec81f16cce59.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA55-c6L6w5omA6Ie0,size_20,color_FFFFFF,t_70,g_se,x_16)


为了解决上面的问题,使用了 PendSV异常。 PendSV异常会自动延迟上下文切换的请求,直到其他的eISR都完成了处理后才放行。为实现这个机制,需要把 PendSV编程为最低优先级的异常。


在FreeRTOS中,每一次进入Systick中断,系统都会检测是否有新的进入就绪态的任务需要运行,如果有,则悬挂PendSV异常,来缓期执行上下文切换。


如下:


![在这里插入图片描述](https://img-blog.csdnimg.cn/fadf4672b283430792739cf76b308263.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA55-c6L6w5omA6Ie0,size_20,color_FFFFFF,t_70,g_se,x_16)


在Systick中会挂起一个PendSV异常用于上下文切换,每产生一个Systick,系统检测到任务链表变化都会触发一个PendSV如下图:


![在这里插入图片描述](https://img-blog.csdnimg.cn/5cc24d08c099459cbfa6d90c14fa805c.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA55-c6L6w5omA6Ie0,size_19,color_FFFFFF,t_70,g_se,x_16)


**PendSV业务流程**


中断过程中不但要像一般的C函数调用一样保存(R0-R3,R12,LR,PSR),还要保存中断返回地址(return address)。中断的硬件机制会把EXC\_RETURN放进LR,在中断返回时触发中断返回。


如下图:


![在这里插入图片描述](https://img-blog.csdnimg.cn/b82db9401232484a93d33ba05caddd5a.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA55-c6L6w5omA6Ie0,size_20,color_FFFFFF,t_70,g_se,x_16)  
 **如何触发PendSV异常**


触发PendSV异常,向PendSV中断寄存器写1,触发一次PendSV异常。用户可以主动调用`portYIELD`函数进行任务切换,`portYIELD`函数如下:



/* Scheduler utilities. */
#define portYIELD()
{
/* Set a PendSV to request a context switch. */
portNVIC_INT_CTRL_REG = portNVIC_PENDSVSET_BIT;

/* Barriers are normally not required but do ensure the code is completely
within the specified behaviour for the architecture. */
__asm volatile( “dsb” ::: “memory” );
__asm volatile( “isb” );
}


### PendSV源码简析


#### PendSV中断服务函数


我这里使用的是M0 内核中 FreeRTOS的源码`xPortPendSVHandler`:



void xPortPendSVHandler( void )
{
/* This is a naked function.
1.产生PendSV中断,硬件自动保存栈帧到任务A的栈中
2.读取当前任务A的栈指针PSP,手动把一些寄存器压栈到当前任务栈。
3.把当前任务A栈顶指针保存到任务A的任务控制块中。
4.找到下一个任务B的任务控制块。(查找下一个优先级最高的就绪任务)
5.把任务B控制块的栈顶指针指向的数据弹出到寄存器中
6.更新PSP为任务B的栈顶指针。
7.跳出PendSV中断。
8.硬件自动弹出任务B栈中的栈帧。
*/
__asm volatile
(
" .syntax unified \n"
" mrs r0, psp \n"/*将psp值放到r0,此时sp得值为msp*/
" \n"
" ldr r3, pxCurrentTCBConst \n" /* Get the location of the current TCB. 获取当前任务控制块,其实就获取任务栈顶 */
" ldr r2, [r3] \n"/*将r3寄存器值作为指针取内容存到r2,此时r2保存的为任务控制块首地址*/
" \n"
" subs r0, r0, #32 \n" /* Make space for the remaining low registers. */
" str r0, [r2] \n" /* Save the new top of stack. */
" stmia r0!, {r4-r7} \n" /* Store the low registers that are not saved automatically. */
" mov r4, r8 \n" /* Store the high registers. */
" mov r5, r9 \n"
" mov r6, r10 \n"
" mov r7, r11 \n"
" stmia r0!, {r4-r7} \n"
" \n"
" push {r3, r14} \n"
" cpsid i \n"
" bl vTaskSwitchContext \n"/*执行上线文切换*/
" cpsie i \n"
" pop {r2, r3} \n" /* lr goes in r3. r2 now holds tcb pointer. */
" \n"
" ldr r1, [r2] \n"
" ldr r0, [r1] \n" /* The first item in pxCurrentTCB is the task top of stack. */
" adds r0, r0, #16 \n" /* Move to the high registers. */
" ldmia r0!, {r4-r7} \n" /* Pop the high registers. */
" mov r8, r4 \n"
" mov r9, r5 \n"
" mov r10, r6 \n"
" mov r11, r7 \n"
" \n"
" msr psp, r0 \n" /* Remember the new top of stack for the task.记住新的栈顶指针 */
" \n"
" subs r0, r0, #32 \n" /* Go back for the low registers that are not automatically restored. */
" ldmia r0!, {r4-r7} \n" /* Pop low registers. */
" \n"
" bx r3 \n"
" \n"
" .align 4 \n"
"pxCurrentTCBConst: .word pxCurrentTCB "
);
}


#### PendSV上下文切换函数


`xPortPendSVHandler`中调用的上下文切换`vTaskSwitchContext`,其核心任务就是找到当前处于就绪态的最高优先级的任务:



void vTaskSwitchContext( void )
{
if( uxSchedulerSuspended != ( UBaseType_t ) pdFALSE )
{
/* The scheduler is currently suspended - do not allow a context
switch.
标记调度器状态
*/
xYieldPending = pdTRUE;
}
else
{
xYieldPending = pdFALSE;
traceTASK_SWITCHED_OUT();

	#if ( configGENERATE\_RUN\_TIME\_STATS == 1 )
	{
	}
	#endif /\* configGENERATE\_RUN\_TIME\_STATS \*/

	/\* 

Check for stack overflow, if configured.
检查任务栈是否溢出
*/
taskCHECK_FOR_STACK_OVERFLOW();

	/\* 

Select a new task to run using either the generic C or port
optimised asm code.
选择优先级最高的任务,把当前的任务控制块进行赋值
*/
taskSELECT_HIGHEST_PRIORITY_TASK();
traceTASK_SWITCHED_IN();

	#if ( configUSE\_NEWLIB\_REENTRANT == 1 )
	{
	}
	#endif /\* configUSE\_NEWLIB\_REENTRANT \*/
}

}


#### 寻找最高优先级函数


上下文切换`vTaskSwitchContext`中调用了`taskSELECT_HIGHEST_PRIORITY_TASK()`寻找最高优先级的任务:


`taskSELECT_HIGHEST_PRIORITY_TASK()`的硬件方式:



/\*-----------------------------------------------------------\*/
/\*

这里注释解释
#define portGET_HIGHEST_PRIORITY( uxTopPriority, uxReadyPriorities ) uxTopPriority = ( 31UL - ( uint32_t ) ucPortCountLeadingZeros( ( uxReadyPriorities ) ) )

#define configASSERT( x ) if ((x) == 0) {taskDISABLE_INTERRUPTS(); for( ;; );}

#define listGET_OWNER_OF_NEXT_ENTRY( pxTCB, pxList )
{
List_t * const pxConstList = ( pxList );
// pxIndex 为上一个任务索引,下一个要执行的即pxNext
( pxConstList )->pxIndex = ( pxConstList )->pxIndex->pxNext;
if( ( void * ) ( pxConstList )->pxIndex == ( void * ) &( ( pxConstList )->xListEnd ) )
{
// 由于是环形列表,切默认有一个结束项(xListEnd),如果pxIndex刚好为最后一项,则再指向后面一项
( pxConstList )->pxIndex = ( pxConstList )->pxIndex->pxNext;
}
( pxTCB ) = ( pxConstList )->pxIndex->pvOwner;
}
由于相同优先级的任务可能会存在多个,需要从就绪任务列表中找到位于最前面的任务
将其赋值给pxCurrentTCB。
*/
#define taskSELECT_HIGHEST_PRIORITY_TASK()
{
UBaseType_t uxTopPriority;

/* Find the highest priority list that contains ready tasks. */
portGET_HIGHEST_PRIORITY( uxTopPriority, uxTopReadyPriority );
configASSERT( listCURRENT_LIST_LENGTH( &( pxReadyTasksLists[ uxTopPriority ] ) ) > 0 );
listGET_OWNER_OF_NEXT_ENTRY( pxCurrentTCB, &( pxReadyTasksLists[ uxTopPriority ] ) );
} /* taskSELECT_HIGHEST_PRIORITY_TASK() */


`taskSELECT_HIGHEST_PRIORITY_TASK()`的通用方式:



/\*-----------------------------------------------------------\*/

#define taskSELECT\_HIGHEST\_PRIORITY\_TASK() \

{
/* uxTopReadyPriority 在每次把任务添加到就绪列表的时候会更新*/
UBaseType_t uxTopPriority = uxTopReadyPriority;

/* Find the highest priority queue that contains ready tasks.
一个优先级一个列表,查看当前最高优先级就绪列表下是否有任务
*/
while( listLIST_IS_EMPTY( &( pxReadyTasksLists[ uxTopPriority ] ) ) )
{
configASSERT( uxTopPriority );
/* 如果当前最高优先级就绪列表没任务就查看下一个优先级列表 */
–uxTopPriority;
}

/* listGET_OWNER_OF_NEXT_ENTRY indexes through the list, so the tasks of
the same priority get an equal share of the processor time.
获取下一个优先级最高任务的任务控制块*/
listGET_OWNER_OF_NEXT_ENTRY( pxCurrentTCB, &( pxReadyTasksLists[ uxTopPriority ] ) );
uxTopReadyPriority = uxTopPriority;
} /* taskSELECT_HIGHEST_PRIORITY_TASK */


### SVC异常


SVC(请求管理调用),异常编号为11,可编程。SVC产生的中断必须立即得到相应,否则将触发硬Fault。


系统调用处理异常,用户与内核进行交互,用户想做一些内核相关功能的时候必须通过SVC异常,让内核处于异常模式,才能调用执行内核的源码。触发SVC异常,会立即执行SVC异常代码。


下面的启动源码简析中我们可以知道系统在启动调度器函数`vTaskStartSchedulerp`最后运行到 `rvPortStartFirstTask`中会调用SVC并启动第一个任务。


为什么要用SVC启动第一个任务?


因为使用了OS,任务都交给内核。总不能像裸机调用普通函数一样启动一个任务。


M4只在上电的触发SVC异常,在SVC异常中启动第一个任务,只上电运行一次,M0上没有。


![在这里插入图片描述](https://img-blog.csdnimg.cn/cb2e517006c34ed29fa443eef01e2c61.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA55-c6L6w5omA6Ie0,size_14,color_FFFFFF,t_70,g_se,x_16)


### SVC源码简析


M0上面没用,特意生成了一个M4的来看看源码`vPortSVCHandler`:



void vPortSVCHandler( void )
{
__asm volatile (
/* 获取当前任务控制块.
任务控制块的第一成员是------任务的栈顶
获取到栈顶之后,剩下的事就是出栈工作
出栈--------任务的堆栈
*/
" ldr r3, pxCurrentTCBConst2 \n" /* Restore the context. */
" ldr r1, [r3] \n" /* Use pxCurrentTCBConst to get the pxCurrentTCB address. */
" ldr r0, [r1] \n" /* The first item in pxCurrentTCB is the task top of stack. */
" ldmia r0!, {r4-r11, r14} \n" /* Pop the registers that are not automatically saved on exception entry and the critical nesting count. 出栈内核寄存器 R14其实就是异常返回值
表示异常退出后,使用PSP*/
" msr psp, r0 \n" /* Restore the task stack pointer.更新栈指针到PSP */
" isb \n"
" mov r0, #0 \n"
" msr basepri, r0 \n"/* 把basepri赋值为0,打开屏蔽中断 */
" bx r14 \n"
" \n"
" .align 4 \n"
“pxCurrentTCBConst2: .word pxCurrentTCB \n”
);
/*
为什么没有恢复其他寄存器????其他在出栈的时候就会自动恢复(由硬件处理)
最终跳转到任务的执行函数里面
*/
}


## FreeRTOS多任务启动源码简析


通过main.c中的main函数调用了`osKernelStart();`



osStatus osKernelStart (void)
{
vTaskStartScheduler();

return osOK;
}


### vTaskStartScheduler


创建空闲任务,启动任务调度器`vTaskStartScheduler`:



void vTaskStartScheduler( void )
{
BaseType_t xReturn;

/\* 

Add the idle task at the lowest priority.
下面部分根据配置的支持动态任何还是静态任务
创建一个优先级最低的空闲任务
*/
#if( configSUPPORT_STATIC_ALLOCATION == 1 )
{
StaticTask_t *pxIdleTaskTCBBuffer = NULL;

收集整理了一份《2024年最新物联网嵌入式全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升的朋友。
img
img

如果你需要这些资料,可以戳这里获取

需要这些体系化资料的朋友,可以加我V获取:vip1024c (备注嵌入式)

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人

都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

rnelStart();`

osStatus osKernelStart (void)
{
  vTaskStartScheduler();
  
  return osOK;
}

vTaskStartScheduler

创建空闲任务,启动任务调度器vTaskStartScheduler

void vTaskStartScheduler( void )
{
BaseType_t xReturn;

	/\* 
 Add the idle task at the lowest priority. 
 下面部分根据配置的支持动态任何还是静态任务
 创建一个优先级最低的空闲任务
 \*/
	#if( configSUPPORT\_STATIC\_ALLOCATION == 1 )
	{
		StaticTask_t \*pxIdleTaskTCBBuffer = NULL;


**收集整理了一份《2024年最新物联网嵌入式全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升的朋友。**
[外链图片转存中...(img-7g5yKOyK-1715874333021)]
[外链图片转存中...(img-2S5plIUb-1715874333021)]

**[如果你需要这些资料,可以戳这里获取](https://bbs.csdn.net/topics/618679757)**

**需要这些体系化资料的朋友,可以加我V获取:vip1024c (备注嵌入式)**

**一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人**

**都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**

  • 3
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值