STM32 FreeRTOS多任务状态迁移、中断临界段和任务延时详解

freertos的作用


处理器运行模式与程序特权级别

arm中SP,LR,PC寄存器以及其它所有寄存器以及处理器运行模式介绍
特权级与用户级的区别主要是某些寄存器能不能访问与修改:
cortex M3/M4内核 特权级与用户级详解

Cortex-M3双堆栈MSP和PSP

Cortex-M3双堆栈MSP和PSP
M3内核何时使用MSP何时使用PSP?

特权级可以使用MSP 和PSP指针
用户级可以使用PSP,能不能使用MSP不太清楚。
在异常处理函数中只能使用MSP


用在裸机开发中
【线程模式特权级 与 Handler模式特权级 相互切换】
线程模式特权级,在异常处理的始末,只发生了处理器模式的切换,如:

在RTOS多任务切换
【线程模式非特权级 与 Handler模式特权级 相互切换】
当CONTROL[0]=1时,线程模式非特权级(用户级),则在异常处理的始末,处理器模式和特权等级都发生变化,如:

用在带有MTU的RTOS开发中,线程模式非特权级 与 线程模式特权级相当于Linux开发中的进程的用户态到内核态
【线程模式非特权级 与 线程模式特权级&Handler模式特权级 切换】

芯片复位后,进入线程模式特权级。 线程模式特权级与Handler模式特权级之间通过异常/中断的进出来切换。
在特权级下的代码可以通过置位CONTROL[0]来进入非特权级。
Handler模式永远都是特权级的。不管是任何原因产生了任何异常,处理器都将以特权级来运行其服务例程,异常返回后,系统将回到产生异常时所处的级别。
Handler模式通过异常/中断进入,退出即返回线程模式。从Handler模式异常返回时,也可以通过置位CONTROL[0](非特权级的线程模式)或清零CONTROL[0](特权级的线程模式)来改变返回线程模式的级别。
线程模式非特权级下的代码不能再试图修改CONTROL[0]来回到线程模式特权级。它必须通过一个异常进入到Handler模式,由那个异常Handler处理来修改,清零CONTROL[0]为线程模式特权级,才能在返回到线程模式后拿到特权级。

在异常Handler异常返回时,除了清零CONTROL[0],还可以修改LR寄存器(在中断函数中不能修改CONTROL[1]),使得返回线程模式变为特权级,且使用psp栈指针

一、xTaskCreate()


1、prvInitialiseNewTask()


2、prvAddNewTaskToReadyList()
(1):全局任务计时器uxCurrentNumberOfTasks加一操作。uxCurrentNumberOfTasks 是一个在task.c中定义的静态变量,默认初始化为0
(2):如果pxCurrentTCB为空,则将pxCurrentTCB指向新创建的任务。pxCurrentTCB 是一个在task.c定义的全局指针,用于指向当前正在运行或者即将要运行的任务的任务控制块,默认初始化为NULL。
(3):如果是第一次创建任务,则需要调用函数prvInitialiseTaskLists()初始化任 务相关的列表,目前只有就绪列表需要初始化,该函数在task.c中定义,

/* 初始化任务相关的列表 */
void prvInitialiseTaskLists( void )
{
    UBaseType_t uxPriority;

    for ( uxPriority = ( UBaseType_t ) 0U; uxPriority < ( UBaseType_t ) configMAX_PRIORITIES; uxPriority++ )
    {
        vListInitialise( &( pxReadyTasksLists[ uxPriority ] ) );
    }
}

vListInitialise()

里pxList->xListEnd.xItemValue = portMAX_DELAY;

列表、列表项

xListEnd的xItemValue是在初始化List时被初始化:void vListInitialise( List_t * const pxList )
ListItem2的xItemValue是在延时函数中prvAddCurrentTaskToDelayedList(),listSET_LIST_ITEM_VALUE被初始化

vListInsertEnd()插入方式:

pxIndex指向的是列表的头,整个列表是一个双向环形结构,从头这里断开拉直列表,最后的列表项就是尾部,该插入方式就是插入在尾部最后的位置



vListInsert()方式插入:

根据xItemValue的值进行排序,然后插入

详见如下链接分析:
从单片机到操作系统⑤——FreeRTOS列表&列表项的源码解读

优先级位图表uxTopReadyPriority
支持多优先级

FreeRTOS任务调度器

1、启动任务调度器的初始化和调用第一个任务

各函数的详细代码分析可以参考如下链接:
09_FreeRTOS任务调度器

【FreeRTOS】2. SVC系统调用

**vTaskStartScheduler ##
该函数内部实现,如下步骤:

1.创建空闲任务

2.如果使能软件定时器,则创建定时器任务

3.关闭中断,防止调度器开启之前或过程中,受中断干扰,会在运行第一个任务时打开中断

4.初始化全局变量,并将任务调度器的运行标志设置为已运行

5.初始化任务运行时间统计功能的时基定时器

6.调用函数xPortStartScheduler()

xPortStartScheduler
作用:该函数用于完成启动任务调度器中与硬件架构相关的配置部分,以及启动第一个任务

该函数内部实现,如下:

1.检测用户在FreeRTOSConfig.h文件中对中断的相关配置是否有误

2.配置PendSV和SysTick的中断优先级为最低优先级

3.调用函数vPortSetupTimerlnterrupt()配置SysTick

4.初始化临界区嵌套计数器为0

5.调用函数prvEnableVFP()使能FPU6(M3没有)、调用函数prvStartFirstTask()启动第一个任务

void prvStartFirstTask
prvStartFirstTask开启第一个任务函数

用于初始化启动第一个任务前的环境,主要是重新设置MSP指针,并使能全局中断。

1.什么是MSP指针?

程序在运行过程中需要一定的栈空间来保存局部变量等一些信息。当有信息保存到栈中时,MCU会自动更新SP(R13)指针, ARM
Cortex-M内核提供了两个栈空间:

主堆栈指针(MSP):它由OS内核、异常服务例程以及所有需要特权访问的应用程序代码(复位后的应用程序代码)来使用。

进程堆栈指针(PSP):用于常规的应用程序代码(不处于异常服务例程中时)。

在FreeRTOS中,中断handle模式使用MSP (主堆栈) ,复位后处于线程模式特权级,默认使用MSP,以外使用PSP (进程堆栈)
MSP指针赋值的是什么?参考如下链接:
FreeRTOS启动第一个任务接口prvPortStartFirstTask实现分析

vPortSVCHandler

注意:SVC中断只在启动第一次任务时会调用一次,以后均不调用

当使能了全局中断,并且手动触发SVC中断后,就会进入到SVC的中断服务函数中

1.通过 pxCurrentTCB 获取优先级最高的就绪态任务的任务栈地址,优先级最高的就绪态任务是系统将要运行的任务。

2.通过任务的栈顶指针,将任务栈中的内容出栈到CPU寄存器中,任务栈中的内容在调用任务创建函数的时候,已初始化,然后设置 PSP 指针。

3.通过往 BASEPRI 寄存器中写 0,允许中断。

  1. R14是链接寄存器LR,在ISR中(此刻我们在SVC的ISR中) ,它记录了异常返回值EXC_RETURN而EXC_RETURN只有6个合法的值(M4、M7),如下表所示:

在vPortSVCHandler函数里使用如下代码的就是上图最后一行最后一列的情况
以下代码的详细分析:FreeRTOS —— prvStartFirstTask 和 vPortSVCHandler

orr r14, #0xd		//R14|=0X0D使得硬件在退出时使用PSP完成出栈操作并返回后进入任务模式Thumb状态,在SVC中断服务里面使用的是MSP,ARM状态 
    bx r14		//异常返回,由于上一句指令切换到PSP,返回时会自动出栈。因此PSP指针自动将栈中剩下的内容加载到CPU寄存器:xPSR, PC(任务入口地址),R14,R12,R3,R2,R1,R0(任务形参)。出栈操作完成后,PSP指向任务栈的栈顶。

2、任务切换:主动和被动

被动:系统节拍时钟中断(SysTick定时器),在中断里调用pendSVC
主动:执行系统调用代码。普通任务使用taskYIELD()强制任务切换;中断服务程序使用portYIELD_FROM_ISR()强制任务切换;在应用程序里也可以通过设置xYieldPending的值来通知调度器进行任务切换。


可以发现不论是主动还是被动,最终都是通过设置NVIC的pendSVC中断寄存器产生pendSVC中断

参考链接如下:
FreeRTOS原理剖析:任务切换过程
任务调度原理 通俗详解(FreeRTOS)

3. 任务切换过程

void xPortPendSVHandler( void )

说明:

在Cortex-M处理器中有两个栈指针,一个是主栈指针(Main Stack Pointer,即MSP),它可用于线程模式,在中断模式下只能用MSP;另一个是进程堆栈指针(Processor Stack Pointer,即PSP),PSP总是用于线程模式。在任何时刻只能使用到其中一个。
复位后处于线程模式特权级,默认使用MSP。在FreeRTOS中,MSP用于OS内核和异常处理,PSP用于应用任务。
通过设置CONTROL寄存器的bit[1]选择使用哪个堆栈指针。CONTROL[1]=0选择主堆栈指针;CONTROL[1]=1选择进程堆栈指针。

vTaskSwitchContext()

该函数会更新当前任务运行时间,检查任务堆栈使用是否溢出,然后调用宏
taskSELECT_HIGHEST_PRIORITY_TASK()获取更高优先级的任务。

寻找下一个任务的方式
PendSV中会调用vTaskSwitchContext(),最后调用函数taskSELECT_HIGHEST_PRIORITY_TASK()寻找优先级最高的任务。
对于FreeRTOS的调度器,它有两种方式寻找下一个最高优先级的任务,分别为特殊方式和常用方式,在FreeRTOSConfig.h中可通过宏定义设置,如下:

/* 0:使用常用方式来选择下一个要运行的任务;1:使用特殊方法来选择下一个要运行的任务 */
#define configUSE_PORT_OPTIMISED_TASK_SELECTION	1

在FreeRTOS中,通用方法不依赖某些硬件等限制,适用于多种MCU中。特殊方式是使用了某些硬件的特性,只针对部分MCU而使用。

常用方法
uxTopReadyPriority 记录就绪态中最高优先级值,创建任务时会更新值,有任务添加到就绪表时也会更新值。这种方法对任务的数量无限制。

#define taskSELECT_HIGHEST_PRIORITY_TASK()												\
{																						\
	UBaseType_t uxTopPriority = uxTopReadyPriority;										\
																						\
	while( listLIST_IS_EMPTY( &( pxReadyTasksLists[ uxTopPriority ] ) ) )				\
	{																					\
		configASSERT( uxTopPriority );													\
		--uxTopPriority;																\
	}																					\
																						\
	/* 获取优先级最高任务的任务控制块 */														\
	listGET_OWNER_OF_NEXT_ENTRY( pxCurrentTCB, &( pxReadyTasksLists[ uxTopPriority ] ) );\
	
	uxTopReadyPriority = uxTopPriority;	 												\
}

特殊方式
使用此方法,uxTopReadyPriority 每个bit位表示一个优先级,bit0表示优先级0,bit31表示优先级31,使用此方式优先级最大只能是32个。

#define taskSELECT_HIGHEST_PRIORITY_TASK()														\
{																								\
	UBaseType_t uxTopPriority;																		\
																								\
	/* 获取优先级最高的任务 */								\
	portGET_HIGHEST_PRIORITY( uxTopPriority, uxTopReadyPriority );								\
	configASSERT( listCURRENT_LIST_LENGTH( &( pxReadyTasksLists[ uxTopPriority ] ) ) > 0 );		\
	
	/* 获取优先级最高任务的任务控制块 */
	listGET_OWNER_OF_NEXT_ENTRY( pxCurrentTCB, &( pxReadyTasksLists[ uxTopPriority ] ) );		\
} 


其中:

#define portGET_HIGHEST_PRIORITY( uxTopPriority, uxReadyPriorities ) uxTopPriority = ( 31UL - ( uint32_t ) __clz( ( uxReadyPriorities ) ) )

__clz( ( uxReadyPriorities ) 是计算uxReadyPriorities 的前导零个数,如:
二进制0001 1010 0101 1111的前导零个数为3,可以知道,最高优先级uxTopPriority 等于 31减去前导零个数。
知道最高优先级的优先级,则通过listGET_OWNER_OF_NEXT_ENTRY()对应最高优先级的列表项,将pxCurrentTCB指向对应的控制块。

寻找下一个任务的方式详细参考链接如下:
FreeRTOS原理剖析:任务切换过程
支持多优先级

FreeRTOS时间片调度


详细参考链接:FreeRTOS任务切换

STM32的PendSV和SVC

xPSR寄存器

参考:《Cortex-M3 权威指南》

LR寄存器

1、调用子函数时,LR存放的是返回地址

2、当异常中断发生时,存放的时异常中断码


因为中断发生时会进行现场保护,中断出的PC,LR会自动入栈,所以不需要再在LR里写入返回地址
而是在LR写入了异常码,不同的异常码有不同的用处


vTaskDelay()、vTaskDelayUntil() 任务延时

FreeRTOS原理剖析:任务延时

手把手教你FreeRTOS源码详解(二)——任务管理
关于任务调度器的一些解释:

FreeRTOS高级篇9—FreeRTOS系统延时分析
相对延时:

绝对延时:

任务延时列表的实现对延时中断节拍数有详解:

/* 计算任务延时到期时,系统时基计数器xTickCount的值是多少 */(3)
    xTimeToWake = xConstTickCount + xTicksToWait;

vPortSetupTimerInterrupt()设置systick节拍中断

延时函数中有一个重要的变量:xTickCount
该变量每次到达systick设置的节拍中断就会加1,在函数xTaskIncrementTick()中实现(任务延时列表的实现)

FreeRTOS按照教程移植好之后就可以使用了。需要注意的是从官网下载的针对keil环境下STM32F103的FreeRTOS文件,使用systick时钟作为rtos的时钟,而这个systick的时钟源会被默认选择为STM32的内核时钟,而不能选择外部时钟。原因就是当rtos启动时(vTaskStartScheduler();这个函数开始被调用的时候),systick就会被按默认的参数配置好,而不是需要用户自己去配置,这些参数在FreeRTOSconfig.h文件里面

88,89,90行,这几行的宏定义决定了systick的初始化参数,其中configCPU_CLOCK_HZ是cpu时钟频率,也是systic的时钟源,如果不打算自己从头到尾来配置systick,一定不要修改这个参数。configTICK_RATE_HZ是systick中断的频率,上图里面是1000HZ。而第88行是我自己加的,它的原型隐藏在port.c文件里面

如果你不去额外定义它一下,它就会被定义成0,这个参数在什么地方用到了呢?


同样是port.c文件,有上面这么一段代码,如果configOVERRIDE_DEFAULT_TICK_CONFIGURATION 被定义成0,就去定义一个名为vPortSetupTimerInterrupt()的函数,这个函数的作用是初始化systick定时器。注意这个函数里面的最后2行,首先它的reload值是直接根据stm32的内核时钟去计算的。另外,在这个函数里面的最后一行,portNVIC_SYSTICK_CTRL_REG与stm32库里面的SysTick->CTRL是一个意思,表示的systick的CTRL控制寄存器。CM3参考手册里面,描述里这个CTRL寄存器的各个位的作用

可以看出,最后一行的目的有3个:选择时钟源为内核时钟;选择计数值到达0时会引起中断;开启计数器的连拍模式(使能计数器) 现在终于可以看出来configOVERRIDE_DEFAULT_TICK_CONFIGURATION到底是干什么的了:如果用户没有把这个东西定义成一个非0的量,它就会被默认定义为0,这样的结果就是默认把systick的时钟源设置为stm32的内核时钟。 一种很可能的误会就是用户在启动RTOS之前已经配置好了systick,时钟源设置为系统时钟的1/8,并把它用于软件延时。结果并没有发现FreeRTOS启动的时候把systick又重新配置了一遍,导致延时不准了。然后用户去freeRTOS留给用户的配置文件里去找,发现了这里有个东西还没改呢#define configCPU_CLOCK_HZ ( ( unsigned long ) 72000000 )就自作聪明的改成这样(我自己开始就是这么干的)#define configCPU_CLOCK_HZ ( ( unsigned long ) 72000000/8 )那就踩到坑了。如果用户使用不依赖中断的查询式的软件延时,还是会出现延时不准的后果。因为FreeRTOS启动的时候还是帮你把systick的时钟源改成了内核时钟(72M),而不是预期的9M,所以计数器的频率是72M,但是计算reload值是用的9M时钟源,导致中断的周期却是正确的。 正确的配置并使用systick的方案有2个:一.就使用内核时钟作为时钟源。延时什么的也都按照这个计算。二.如果确实想使用1/8的时钟源,就要这样配置1.在FreeRTOSconfig.h文件里面加上这个宏定义,定义成非0的数#define configOVERRIDE_DEFAULT_TICK_CONFIGURATION 1 [size=10.5000pt]2.自己在需要的地方定义好systick的初始化函数,并把它命名为 void vPortSetupTimerInterrupt( void )这样会带来一个问题就是tickless idle的功能不能用了,也希望有前辈指点一下这个问题怎么解决 3.至于这个宏定义就不需要改动了因为它已经不会被使用了#define configCPU_CLOCK_HZ ( ( unsigned long ) 72000000 )

taskSELECT_HIGHEST_PRIORITY_TASK()

pxTCB = listGET_OWNER_OF_HEAD_ENTRY( pxDelayedTaskList )

listGET_OWNER_OF_NEXT_ENTRY( pxCurrentTCB, &( pxReadyTasksLists[ uxTopPriority ] ) );

heap堆内存分配

详情参考如下链接:
FreeRTOS的内存分配heap_1/2/3/4
freeRTOS中heap_1/2/3/4/5.c的对比
下面只对heap_2.c再做一遍梳理

2、heap_2.c

1、初始化链表

static void prvHeapInit( void )
{
    BlockLink_t * pxFirstFreeBlock;
    uint8_t * pucAlignedHeap;

    /* Ensure the heap starts on a correctly aligned boundary. */
    pucAlignedHeap = ( uint8_t * ) ( ( ( portPOINTER_SIZE_TYPE ) & ucHeap[ portBYTE_ALIGNMENT ] ) & ( ~( ( portPOINTER_SIZE_TYPE ) portBYTE_ALIGNMENT_MASK ) ) );
//获取到地址对齐后的内存堆首地址
    /* xStart is used to hold a pointer to the first item in the list of free
     * blocks.  The void cast is used to prevent compiler warnings. */
//对空闲块链表进行初始化。留意,xStart是链表头,并不表示一个空闲块,xEnd是链表尾,也不表示一个空闲块,但记录着整个堆的大小。
    xStart.pxNextFreeBlock = ( void * ) pucAlignedHeap;
    xStart.xBlockSize = ( size_t ) 0;

    /* xEnd is used to mark the end of the list of free blocks. */
    xEnd.xBlockSize = configADJUSTED_HEAP_SIZE;
    xEnd.pxNextFreeBlock = NULL;

    /* To start with there is a single free block that is sized to take up the
     * entire heap space. */
    pxFirstFreeBlock = ( void * ) pucAlignedHeap;
    pxFirstFreeBlock->xBlockSize = configADJUSTED_HEAP_SIZE;
    pxFirstFreeBlock->pxNextFreeBlock = &xEnd;
}

经过上面的初始化流程后,整个链表如下图所示。注意,xStart和xEnd是存在于静态存储区中,并不在FreeRTOS申请的内存堆数组中,但初始时第一个节点却在内存堆数组中。我用的编译器是Keil MDK 5.11,并且将FreeRTOS移植到STM32上,因此一个A_BLOCK_LINK的大小为8个字节。

2、确定申请内存大小并对齐

/* The wanted size is increased so it can contain a BlockLink_t 
structure in addition to the requested amount of bytes. */  
if( xWantedSize > 0 )  
{  
 xWantedSize += heapSTRUCT_SIZE;  
 //这里用到的空闲块头部并不是直接sizeof(BlockLink_t),而是heapSTRUCT_SIZE,这个常量也定义在Heap_2.c中,这是对空闲块头部的大小再进行了一次大小对齐。
  
 /* Ensure that blocks are always aligned to the required number of bytes. */  
 if( ( xWantedSize & portBYTE_ALIGNMENT_MASK ) != 0 )  
 {  
  /* Byte alignment required. */  
  xWantedSize += ( portBYTE_ALIGNMENT - ( xWantedSize & portBYTE_ALIGNMENT_MASK ) );  
 }  
}  

在真正开始分配内存时,用vTaskSuspendAll()挂起所有任务,防止分配内存的过程被中断,确保操作的原子性。紧接着,如果是第一次调用pvPortMalloc(),则调用prvHeapInit()对内存堆和空闲块链表进行初始化。由于在Heap_2中将内存堆用空闲块处理,因此用户每申请一次内存,FreeRTOS都会在申请的空间前加上空闲块头部BlockLink_t,用于记录分配出去的空间的大小。因此,真正要分配的内存空间大小就等于用户申请的内存空间大小加上空闲块头部的大小。加上头部之后,还要对整个大小进行对齐。因此,在真正分配空间之前,FreeRTOS都对用户申请的空间大小进行了调整。如上面的代码所示。

if( ( xWantedSize > 0 ) && ( xWantedSize <= xFreeBytesRemaining ) )
//初始化的时候xFreeBytesRemaining  = configADJUSTED_HEAP_SIZE,后续等于剩下空闲的内存大小
        {
            /* Blocks are stored in byte order - traverse the list from the start
             * (smallest) block until one of adequate size is found. */
            pxPreviousBlock = &xStart; //1.
            pxBlock = xStart.pxNextFreeBlock;//2.

            while( ( pxBlock->xBlockSize < xWantedSize ) && ( pxBlock->pxNextFreeBlock != NULL ) )
            {
            /*best fit algorithm的实现了。在这里,空闲块的大小是按从小到大的顺序排列的。因此,遍历链表,
找到第一块比申请空间大的空闲块即为最合适的空闲块。然后返回这个空闲块头后的首地址。
*/
                pxPreviousBlock = pxBlock;
                pxBlock = pxBlock->pxNextFreeBlock;
            }

            /* If we found the end marker then a block of adequate size was not found. */
            if( pxBlock != &xEnd )
            {
                /* Return the memory space - jumping over the BlockLink_t structure
                 * at its start. */
         //注意,一定是空闲块头后的首地址哦,要是直接将空闲块的首地址返回的话,那用户就会将空闲块头修改了。
                pvReturn = ( void * ) ( ( ( uint8_t * ) pxPreviousBlock->pxNextFreeBlock ) + heapSTRUCT_SIZE );//3.

                /* This block is being returned for use so must be taken out of the
                 * list of free blocks. */
			//当前空闲的内存块分了出去,那么当前的空闲内存块的链表节点就不在该链表
                pxPreviousBlock->pxNextFreeBlock = pxBlock->pxNextFreeBlock;//4.

                /* If the block is larger than required it can be split into two. */
                if( ( pxBlock->xBlockSize - xWantedSize ) > heapMINIMUM_BLOCK_SIZE )
                {
                /*分配出去的空闲块的剩余空间要是比两倍的空闲块头还要大,则将分配出去的这个空闲块分割剩余
                的空间出来,重新放到空闲块链表中。例如,初始化后只有一个空闲块,这个空闲块大小为17KB。
                经过调整后的用户申请空间大小为1KB,则FreeRTOS就从这个空闲块靠近块首的地方分割出1KB出
                来分配出去,剩余的16KB则重新放回空闲块链表里。以便下一次继续分配。
                */
                    /* This block is to be split into two.  Create a new block
                     * following the number of bytes requested. The void cast is
                     * used to prevent byte alignment warnings from the compiler. */
                    pxNewBlockLink = ( void * ) ( ( ( uint8_t * ) pxBlock ) + xWantedSize );//5.

                    /* Calculate the sizes of two blocks split from the single
                     * block. */
                    pxNewBlockLink->xBlockSize = pxBlock->xBlockSize - xWantedSize;//6.
                    pxBlock->xBlockSize = xWantedSize;//7.

                    /* Insert the new block into the list of free blocks. */
                    prvInsertBlockIntoFreeList( ( pxNewBlockLink ) );
                }

                xFreeBytesRemaining -= pxBlock->xBlockSize;
            }
        }



#define prvInsertBlockIntoFreeList( pxBlockToInsert )                                                                               \
    {                                                                                                                               \
        BlockLink_t * pxIterator;                                                                                                   \
        size_t xBlockSize;                                                                                                          \
                                                                                                                                    \
        xBlockSize = pxBlockToInsert->xBlockSize;                                                                                   \
                                                                                                                                    \
        /* Iterate through the list until a block is found that has a larger size */                                                \
        /* than the block we are inserting. */                                                                                      \       //1.
        for( pxIterator = &xStart; pxIterator->pxNextFreeBlock->xBlockSize < xBlockSize; pxIterator = pxIterator->pxNextFreeBlock ) \
        {                                                                                                                           \
            /* There is nothing to do here - just iterate to the correct position. */                                               \
        }                                                                                                                           \
                                                                                                                                    \
        /* Update the list to include the block being inserted in the correct */                                                    \
        /* position. */                                                                                                             \
        pxBlockToInsert->pxNextFreeBlock = pxIterator->pxNextFreeBlock;        //2.                                                     \
        pxIterator->pxNextFreeBlock = pxBlockToInsert;                   //3.                                                           \
    }

多态

中断

详细参考链接:
临界段的保护

#define portDISABLE_INTERRUPTS() vPortRaiseBASEPRI()
#define portENABLE_INTERRUPTS() vPortSetBASEPRI( 0 )

/* 不带中断保护的关/开中断函数 */

进入后屏蔽掉内核层调度器相关的中断,systick、pendsvc,调度器不再调度,任务不再切换 退出后,所有中断都开启

当在中断函数里处理边界代码段时,因为已经进入到中断函数了,所以肯定有某些中断没有被屏蔽,那么在退出该边界代码段时就需要将basepri寄存器恢复到进入该中断前的状态(某部分中断不会被屏蔽,我们也不需要知道具体屏蔽的细节),使用的宏如下:

/* 带返回值的关中断函数,可以嵌套,可以在中断里面使用 */(2)
#define portSET_INTERRUPT_MASK_FROM_ISR() ulPortRaiseBASEPRI()
#define portCLEAR_INTERRUPT_MASK_FROM_ISR(x) vPortSetBASEPRI(x)

进入后屏蔽掉内核层调度器相关的中断,systick、pendsvc,调度器不再调度,任务不再切换 退出后,恢复到进入中断前的中断屏蔽状态

这两种方法的更上层封装如下:

#define taskENTER_CRITICAL()                portENTER_CRITICAL()
#define taskENTER_CRITICAL_FROM_ISR() portSET_INTERRUPT_MASK_FROM_ISR()

#define taskEXIT_CRITICAL()         portEXIT_CRITICAL()
#define taskEXIT_CRITICAL_FROM_ISR( x ) portCLEAR_INTERRUPT_MASK_FROM_ISR( x )

上述方法最终都是把寄存器basepri值设置为 configMAX_SYSCALL_INTERRUPT_PRIORITY

#define configMAX_SYSCALL_INTERRUPT_PRIORITY 	( 5 << 4 ) 

configMAX_SYSCALL_INTERRUPT_PRIORITY的时候(该宏在F reeRTOSConfig.h中定义,现在配置为5),会让系统不响应比该优先级低的中断,而优先级比之更高的中断则不受影响。就是说当这个宏定义配置为5的时候,中断优先级数值在0、1、2、3、4的这些中断是不受FreeRTOS管理的,不可被屏蔽,而中断优先级在5到15的中断是受到系统管理,可用被屏蔽的。
当调度器启用时,systick和pendsvc的优先级都被置为最低240,240大于11,当优先级值大于5的,中断会被屏蔽
优先级值小于5的中断会被响应

任务多态






临界段与互斥量

临界段用一句话概括就是一段在执行的时候不能被中断的代码段。(系统中断pendSV不行,外部中断可以)

互斥量又称互斥信号量(本质是信号量),是一种特殊的二值信号量,它和信号量不
同的是,它支持互斥量所有权、递归访问以及防止优先级翻转的特性,用于实现对临界资
源的独占式处理。

两者有相似之处:

都是用来保护一段代码,形式上也就是都在一段代码的前后

不同之处:

1、 临界段不会被系统调用打断,完整执行这段段代码

2、任务获取到互斥信号量后执行共享资源代码时,是会被系统调用打断从而执行其它任务的,如果其它任务同级,且没有获取互斥信号量也就是其它任务不争抢该资源的话,该任务是可以执行时间片时间
只有当其它任务也要获取该互斥信号量,也就是争抢该资源,其它任务就会先阻塞等待另一个任务使用完共享资源并释放互斥信号量后才会唤醒

物联沃分享整理
物联沃-IOTWORD物联网 » STM32 FreeRTOS多任务状态迁移、中断临界段和任务延时详解

发表评论