FreeRTOS面试笔记

4 篇文章 0 订阅
4 篇文章 0 订阅

更多资料连接:
链接: https://pan.baidu.com/s/1v7nRKZH4yTyGd4LVUJRaMg?pwd=kw7i 提取码: kw7i 
--来自百度网盘超级会员v5的分享

基础知识

抢占式多任务系统

完成多任务的功能在RTOS系统中叫做任务调度器,任务调度器也是抢占式的,其运行过程如下图示

任务可以通过调用 vTaskSuspend() API 函数都可以将处于任何状态的任务挂起,被挂起的任务得不到CPU 的使用权,也不会参与调度,除非它从挂起态中解除。

1.任务

1.1任务状态

  • 运行态:当任务正在运行时,处于运行态;处于运行态的任务就是当前正在使用处理器的任务

  • 就绪态:已经准备就绪,可以运行的任务;有同优先级或更高优先级的任务正在运行占用CPU

  • 阻塞态:任务正在等待某个外部事件即处于阻塞态;阻塞态有超时时间,若超时会退出阻塞态

  • 挂起态:进入挂起态后也不能被调度器调用进入运行态;挂起态的任务没有超时时间

1:创建任务→就绪态(Ready):任务创建完成后进入就绪态,表明任务已准备就绪,随时可以运行,只等待调度器进行调度。

2:就绪态→运行态(Running):发生任务切换时,就绪列表中最高优先级的任务被执行,从而进入运行态。

3:运行态→就绪态:有更高优先级任务创建或者恢复后,会发生任务调度,此刻就绪列表中最高优先级任务变为运行态,那么原先运行的任务由运行态变为就绪态,依然在就绪列表中,等待最高优先级的任务运行完毕继续运行原来的任务(此处可以看做是 CPU 使用权被更高优先级的任务抢占了)。

4:运行态→阻塞态(Blocked):正在运行的任务发生阻塞(挂起、延时、读信号量等待)时,该任务会从就绪列表中删除,任务状态由运行态变成阻塞态,然后发生任务切换,运行就绪列表中当前最高优先级任务。

5:阻塞态→就绪态:阻塞的任务被恢复后(任务恢复、延时时间超时、读信号量超时或读到信号量等),此时被恢复的任务会被加入就绪列表,从而由阻塞态变成就绪态;如果此时被恢复任务的优先级高于正在运行任务的优先级,则会发生任务切换,\n将该任务将再次转换任务状态,由就绪态变成运行态。

6、7、8:就绪态、阻塞态、运行态→挂起态(Suspended):任务可以通过调用 vTaskSuspend() API 函数都可以将处于任何状态的任务挂起,被挂起的任务得不到CPU 的使用权,也不会参与调度,除非它从挂起态中解除。

9:挂起态→就绪态:把一个挂起状态的任务恢复的唯一途径就是调用 vTaskResume() 或 vTaskResumeFromISR() API函数,如果此时被恢复任务的优先级高于正在运行任务的优先级,则会发生任务切换,将该任务将再次转换任务状态,由就绪态变成运行态。 

1.2.任务优先级

每个任务都可以分配一个从0 ~ (configMAX_PRIORITIES - 1)的优先级,configMAX_PRIORITIES在文件FreeRTOSConfig.h中定义,优先级数字越低表示任务的优先级越低,0的优先级最低,configMAX_PRIORITIES - 1的优先级最高。空闲任务的优先级最低,为0

内核控制函数

任务相关 API

1.3.任务实现

FreeRTOS官方给出的任务函数模板如下:

void vATaskFunction(void *pvParameters)     //根据实际情况定义任务名;返回类型一定要为void类型
{
    for(;;)     //代表一个循环
    {
        任务应用程序; //任务代码
        vTaskDelay();   //延时函数,不一定会用到
    }
    vTaskDelete(NULL);  //任务函数一般不允许跳出循环,如果一定要跳出的话在跳出循环后一定要删除此任务
}

1.4.任务控制块

FreeRTOS把这些属性集合到一起用一个结构体来表示,这个结构体叫做任务控制块:TCB_t,在使用函数xTaskCreate()创建任务的时候就会自动的给每个任务分配一个任务控制块。此结构体在文件task.c中有定义

1.5. 任务堆栈

FreeRTOS之所以能正确的恢复一个任务的运行就是因为有任务堆栈在保驾护航,任务调度器在进行任务切换的时候会将当前任务的现场(CPU寄存器值等)保存在此任务的任务堆栈中,等到此任务下次运行的时候就会先用堆栈中保存的值来恢复现场,恢复现场以后任务就会接着从上次中断的地方开始运行

创建任务的时候需要给任务指定堆栈,若使用xTaskCreate()动态创建任务,任务堆栈会由函数xTaskCreate()自动创建;若使用xTaskCreateStatic()静态创建任务,就需要自行定义任务堆栈,将堆栈首地址作为函数的参数puxStackBuffer传递给函数,如下:

TaskHandle_t xTaskCreateStatic( TaskFunction_t pxTaskCode,
                                const char * const pcName,
                                const uint32_t ulStackDepth,
                                void * const pvParameters,
                                UBaseType_t uxPriority,
                                StackType_t * const puxStackBuffer,     //需要自定义,并将堆栈首地址传递给此参数
                                StaticTask_t * const pxTaskBuffer )

不论是使用动态还是静态方法创建任务,创建任务都需要指定任务堆栈大小。任务堆栈的数据类型为StackType_t,其本质是uint32_t,在portmacro.h文件中由定义,如下:

#define portSTACK_TYPE  uint32_t
....
typedef portSTACK_TYPE StackType_t;

StackType_t类型的变量为4个字节,因此任务的实际堆栈大小就是我们所定义的4倍

任务挂起和恢复

FreeRTOS多任务调度

任务切换场合

FreeRTOS系统时钟是由滴答定时器来提供,任务切换也会用到PendSV中断,RTOS系统的核心是任务管理,而任务管理的核心是任务切换,任务切换决定了任务的执行顺序。上下文(任务)切换被触发的场合可以是系统滴答定时器(SysTick)中断。

典型的嵌入式OS系统中,处理器被划分为多个时间片。若系统中只有两个任务,这两个任务会交替执行,任务切换都是在SysTick中断中执行

在一些OS设计中,为了解决SysTick和IRQ的冲突问题,PendSV异常将上下文切换请求延迟到所有其他IRQ处理都已经完成后,在PendSV异常内执行上下文切换。如下图示:

PendSV(可挂起的系统调用)异常对OS操作非常重要,其优先级可通过编程设置。可通过将中断控制和状态寄存器ICSR的bit28(挂起位)置1来触发PendSV中断。上面提到过上下文切换被触发的两个场合:SysTick中断和执行一个系统调用

1.SysTick中断函数:

//滴答定时器中断服务函数
void SysTick_Handler(void){
  if(xTaskGetSchedulerState()!=taskSCHEDULER_NOT_STARTED){ //系统已经运行
    xPortSysTickHandler();
  }
}

void xPortSysTickHandler( void ){
  vPortRaiseBASEPRI();  //关闭中断
  {
    if( xTaskIncrementTick() != pdFALSE ){ //增加时钟计数器xTickCount的值                        
      /* 通过向中断控制和状态寄存器的bit28位写入1挂起PendSV来启动PendSV中断 */
      portNVIC_INT_CTRL_REG = portNVIC_PENDSVSET_BIT; 
    }
  }
  vPortClearBASEPRIFromISR();   //打开中断
}

2.执行一个系统调用

//以任务切换函数taskYIELD()为例
#define taskYIELD()  portYIELD()
#define portYIELD() 
{ 
  /* 通过向中断控制和状态寄存器的bit28位写入1挂起PendSV来启动PendSV中断 */
  portNVIC_INT_CTRL_REG = portNVIC_PENDSVSET_BIT; 
  __dsb( portSY_FULL_READ_WRITE ); 
  __isb( portSY_FULL_READ_WRITE ); 
}

FreeRTOS任务切换的具体过程是在PendSV中断服务函数中完成的,

FreeRTOS时间管理

在使用FreeRTOS的过程中经常会在一个任务中使用延时函数对该任务延时,当执行延时函数的时候就会进行任务切换,并且此任务就会进入阻塞态,直到延时完成,任务重新进入就绪态。

1 相对延时函数

相对延时函数vTaskDelay()在文件task.c中定义,功能是使任务进入阻塞态,根据传入的参数延时多少个tick(系统节拍),其函数原型如下:

函数原型:void vTaskDelay(TickType_t xTicksToDelay)
传 入 值:xTicksToDelay 延时周期
         系统节拍周期为1000Hz,延时周期时基就是1ms;
         系统节拍周期为100Hz,延时周期时基就是10ms;

2 绝对延时函数

绝对延时函数vTaskDelayUntil()在文件task.c中定义,功能是使任务进入阻塞态,直到一个绝对延时时间到达,其函数原型如下:

函数原型:void vTaskDelayUntil(TickType_t *pxPreviousWakeTime,TickType_t xTimeIncrement)
传 入 值:pxPreviousWakeTime 记录任务上一次唤醒系统节拍值
         xTimeIncrement 相对于pxPreviousWakeTime,本次延时的节拍数

3 调度器挂起和恢复

调度器挂起函数:vTaskSuspendAll()

FreeRTOS中断管理和临界段

Cortex-M处理器还把256个优先级按位分为高低两段:抢占优先级(分组优先级)和亚优先级(子优先级),通过NVIC中的AIRCR寄存器的PRIGROUP(优先级分组)位段来配置分组。

FreeRTOS的STM32只使用了4位,因此STM32最多只有5组优先级分组设置。而移植FreeRTOS到STM32时,一般都是配置组4,也就是4位优先级全都是抢占优先级了,没有亚优先级,因此就有0~15共16个优先级。

中断管理函数

打开中断:

void taskENABLE_INTERRUPTS (void)

关闭中断

void taskDISABLE_INTERRUPTS(void)

临界段

代码的临界段也称为临界区,是指那些必须完整运行,不能被打断的代码段,比如有的外设的初始化需要严格的时序,初始化过程中不能被打断。为确保临界段代码的执行不被中断,在进入临界段之前须关中断,而临界段代码执行完毕后,要立即打开中断

临界段相关函数

进入/退出临界段:任务级临界段代码保护

/*********** 进入临界段 ***********/
函数原型:void taskENTER_CRITICAL(void)
    
    /*********** 退出临界段 ***********/
函数原型:void taskEXIT_CRITICAL(void)


 

处理器利用率统计API函数

函数vTaskGetRunTimeStats( )会将统计到的信息填充到一个表里,表里面提供了每个任务的运行时间和其所占总时间的百分比

FreeRTOS任务相关API函数

任务相关API函数详解

1.uxTaskPriorityGet():查询某个任务的优先级

/*****************************相关宏的配置*****************************/
#define INCLUDE_uxTaskPriorityGet            必须置为1
/*******************************函数原型*******************************/
函数原型:UBaseType_t uxTaskPriorityGet(TaskHandle_t xTask)
传 入 值:xTask 要查找的任务的任务句柄
返 回 值:获取到的对应的任务的优先级

2.vTaskPrioritySet() :改变某个任务的优先级

/*****************************相关宏的配置*****************************/
#define INCLUDE_vTaskPrioritySet             必须置为1
/*******************************函数原型*******************************/
函数原型:void vTaskPrioritySet(TaskHandle_t xTask,UBaseType_t uxNewPriority)
传 入 值:xTask 要更改优先级的任务的任务句柄
         uxNewPriority 任务要使用的新的优先级

3.uxTaskGetSystemState() :获取系统中所有任务的任务状态,每个任务的状态信息保存在一个TaskStatus_t类型的结构体里,这个结构体包含了任务句柄、任务名称、堆栈、优先级等信息

uxTaskGetSystemState()函数

/*****************************相关宏的配置*****************************/
#define configUSE_TRACE_FACILITY             必须置为1
/*******************************函数原型*******************************/
函数原型:UBaseType_t uxTaskGetSystemState(TaskStatus_t *const pxTaskStatusArray,
                                          const UBaseType_t uxArraySize,
                                          uint32_t *const pulTotalRunTime)
传 入 值:pxTaskStatusArray 指向TaskStatus_t结构体类型的数组首地址
         uxArraySize       保存任务状态数组的大小
         pulTotalRunTime   若开启了系统运行时间统计,则用来保存系统总的运行时间
返 回 值:统计到的任务状态的个数,也就是填写到数组pxTaskStatusArray中的个数

TaskStatus_t结构体

/*****TaskStatus_t结构体*****/
typedef struct xTASK_STATUS{
    TaskHandle_t    xHandle;    //任务句柄
    const char*     pcTaskName; //任务名字
    UBaseType_t     xTaskNumber;    //任务编号
    eTaskState      eCurrentState;  //当前任务状态
    UBaseType_t     uxCurrentPriority;  //任务当前的优先级
    UBaseType_t     uxBasePriority;     //任务基础优先级
    uint32_t        ulRunTimeCounter;   //任务运行的总时间
    StackType_t*    pxStackBase;    //堆栈基地址
    uint16_t        usStackHighWaterMark;//任务创建依赖任务堆栈剩余的最小值
}TaskStatus_t;

4.vTaskGetInfo() :获取某个指定的单个任务的任务信息

/*****************************相关宏的配置*****************************/
#define configUSE_TRACE_FACILITY             必须置为1
/*******************************函数原型*******************************/
函数原型:void vTaskGetInfo(TaskHandle_t xTask,
                           TaskStatus_t* pxTaskStatus,
                           BaseType_t xGetFreeStackSpace,
                           eTaskState eState)
传 入 值:xTask 要查找的任务的任务句柄
         pxTaskStatus 指向类型为TaskStatus_t结构体变量
         xGetFreeStackSpace 堆栈剩余的历史最小值
         eState 保存任务运行状态

5.xTaskGetApplicationTaskTag() :获取某个任务的标签值

/*****************************相关宏的配置*****************************/
#define configUSE_APPLICATION_TASK_TAG   必须置为1
/*******************************函数原型*******************************/
函数原型:void xTaskGetApplicationTaskTag(TaskHandle_t xTask)
传 入 值:xTask 要获取标签值的任务的任务句柄,若为NULL表示获取当前任务的标签值
返 回 值:任务的标签值

6.xTaskGetCurrentTaskHandle() :获取当前正在运行任务的任务句柄,其实获取到的就是任务控制块

/*****************************相关宏的配置*****************************/
#define INCLUDE_xTaskGetCurrentTaskHandle   必须置为1
/*******************************函数原型*******************************/
函数原型:TaskHandle_t xTaskGetCurrentTaskHandle(void)
返 回 值:当前任务的任务句柄

7.xTaskGetHandle() :根据任务名字查找某个任务的句柄

/*****************************相关宏的配置*****************************/
#define INCLUDE_xTaskGetHandle  必须置为1
/*******************************函数原型*******************************/
函数原型:TaskHandle_t xTaskGetHandle(const char* pcNameToQuery)
传 入 值:pcNameToQuery 任务名
返 回 值:任务名所对应的任务句柄;返回NULL表示没有对应的任务

8.xTaskGetIdleTaskHandle() :获取空闲任务的句柄

/*****************************相关宏的配置*****************************/
#define INCLUDE_xTaskGetIdleTaskHandle  必须置为1
/*******************************函数原型*******************************/
函数原型:TaskHandle_t xTaskGetIdleTaskHandle(void)
返 回 值:空闲任务的任务句柄

9.uxTaskGetStackHighWaterMark() :每个任务在创建的时候就确定了堆栈大小,此函数用于检查任务从创建好到现在的历史剩余最小值,这个值越小说明任务堆栈溢出的可能性就越大

/*****************************相关宏的配置*****************************/
#define INCLUDE_uxTaskGetStackHighWaterMark 必须置为1
/*******************************函数原型*******************************/
函数原型:UBaseType_t uxTaskGetStackHighWaterMark(TaskHandle_t xTask)
传 入 值:xTask 要查询的任务的任务句柄,若为NULL表示查询自身任务的高水位线
返 回 值:任务堆栈的高水位线值,即堆栈的历史剩余最小值

10.eTaskGetState() :获取某个任务的状态,比如:运行态、阻塞态、挂起态、就绪态等

/*****************************相关宏的配置*****************************/
#define INCLUDE_eTaskGetState   必须置为1
/*******************************函数原型*******************************/
函数原型:eTaskState eTaskGetState(TaskHandle_t xTask)
传 入 值:xTask 要查询的任务的任务句柄
返 回 值:返回eTaskState枚举类型值


11.pcTaskGetName() :根据任务句柄来获取该任务的任务名字

/*******************************函数原型*******************************/
函数原型:char* pcTaskGetName(TaskHandle_t xTaskToQuery)
传 入 值:xTaskToQuery 要查询的任务的任务句柄,若为NULL表示查询自身任务名
返 回 值:返回任务所对应的任务名

12.xTaskGetTickCount() :用于查询任务调度器从启动到现在时间计数器xTickCount的值。xTickCount是系统的时钟节拍值,并不是真实的时间值。每个滴答定时器中断xTickCount就会加一,中断周期取决于系统时钟节拍数

/*******************************函数原型*******************************/
函数原型:TickType_t xTaskGetTickCount(void)
返 回 值:时间计数器xTickCount的值

13.xTaskGetTickCountFromISR() :在中断服务函数中获取时间计数器值

/*******************************函数原型*******************************/
函数原型:TickType_t xTaskGetTickCountFromISR(void)
返 回 值:时间计数器xTickCount的值

14.xTaskGetSchedulerState() :获取任务调度器的状态,开启、关闭还是挂起

*****************************相关宏的配置*****************************/
#define INCLUDE_xTaskGetSchedulerState  必须置为1
/*******************************函数原型*******************************/
函数原型:BaseType_t xTaskGetSchedulerState(void)
返 回 值:taskCHEDULER_NOT_STARTED 调度器未启动
         taskCHEDULER_RUNNING 调度器正在运行
         taskCHEDULER_SUSPENDED 调度器挂起

15.uxTaskGetNumberOfTask() :获取当前系统中存在的任务数量

/*******************************函数原型*******************************/
函数原型:UBaseType_t uxTaskGetNumberOfTask(void)
返 回 值:当前系统中存在的任务数量,此值包含各种状态下的任务数

16.vTaskList() :以表格的形式输出当前系统中所有任务的详细信息

/*******************************函数原型*******************************/
函数原型:void vTaskList(char *pcWriteBuffer)
传 入 值:pcWriteBuffer 保存任务状态信息表的存储区

17.vTaskGetRunTimeStats() :获取每个任务的运行时间

/*****************************相关宏的配置*****************************/
#define configGENERATE_RUN_TIME_STATS            必须置为1
#define configUSE_STATS_FORMATTING_FUNCTIONS     必须置为1
/*******************************函数原型*******************************/
函数原型:void vTaskGetRunTimeStats(char *pcWriteBuffer)
传 入 值:pcWriteBuffer 保存任务时间信息的存储区

18.vTaskSetApplicationTaskTag() :用于设置某个任务的标签值

/*****************************相关宏的配置*****************************/
#define configUSE_APPLICATION_TASK_TAG   必须置为1
/*******************************函数原型*******************************/
函数原型:void vTaskSetApplicationTaskTag(TaskHandle_t xTask,
                                   TaskHookFunction_t pxHookFunction)
传 入 值:xTask 要要设置标签值的任务的任务句柄
         pxHookFunction 要设置的标签值

列表和列表项

列表是FreeRTOS中的一个数据结构,与链表类似,列表被用来跟踪FreeRTOS中的任务。其结构体 List_t 在 list.h 文件中被定义

列表项就是存放在列表中的项目,FreeRTOS提供两种类型的列表项:列表项和迷你列表项。列表项的结构体 ListItem_t 在 list.h 文件中被定义

有些情况下不需要列表项这么全的功能,为了避免造成内存浪费,定义了迷你列表项。迷你列表项的结构体 MiniListItem_t 在 list.h 文件中被定义

列表的初始化:vListInitialise()函数来完成,该函数在list.c文件中定义

列表项的初始化:vListInitialiseItem()函数来完成,该函数在list.c文件中定义

列表项的插入:将指定列表项插入到列表中,通过vListInsert()函数来完成

下图演示了向一个空列表中依次插入40、60和50三个列表项的插入过程:

列表项的末尾插入:将指定列表项插入到列表末尾,通过vListInsertEnd()函数来完成

下图演示了向一个列表末尾插入列表项的插入过程

列表项的删除:从列表中删除指定的列表项,通过uxListRemove()函数来完成

列表项的遍历:List_t中的成员变量pxIndex是用来遍历列表的,FreeRTOS使用如下函数(宏)来完成列表的遍历,每调用一次这个函数,列表的pxIndex变量就会指向下一个列表项,并返回这个列表项的pvOwner变量值

队列简介

队列是为了任务与任务、任务与中断之间的通信而准备的,可以在任务与任务、任务与中

断之间传递消息,队列中可以存储有限的、大小固定的数据项目。任务与任务、任务与中断之间要交流的数据保存在队列中,叫做队列项目。队列所能保存的最大数据项目数量叫做队列的长度,创建队列的时候会指定数据项目的大小和队列的长度。由于队列用来传递消息的,所以也称为消息队列。FreeRTOS 中的信号量的也是依据队列实现的。

消息队列简介

消息队列可以在任务与任务、任务与中断之间传递消息,队列可以保存有限个具有确定长度的数据单元。队列可保存的最大单元数目被称为队列的长度,在队列创建时需要指定其长度和每个单元(队列项或消息)的大小。通常情况下,队列被作为FIFO(先进先出)使用,即数据由队尾写入,从队首读出。当然由队列首写入也是可能的

队列的结构体 Queue_t 如下示,该结构体在queue.c文件中定义

typedef struct QueueDefinition

消息队列的函数

创建队列,有动态和静态创建队列两种方法

/********************动态创建队列**********************************************************************/
QueueHandle_t xQueueCreate(UBaseType_t uxQueueLength, //要创建的队列长度(队列的项目数)
                           UBaseType_t uxItemSize); //队列项(消息)的长度(字节)
/********************动态创建队列**********************************************************************/
QueueHandle_t xQueueGenericCreate(const UBaseType_t uxQueueLength,//要创建的队列长度(队列的项目数)
                                  const UBaseType_t uxItemSize, //队列项(消息)的长度(字节)
                                  const uint8_t ucQueueType); //队列类型
/********************静态创建队列**********************************************************************/
QueueHandle_t xQueueCreateStatic(UBaseType_t uxQueueLength,//要创建的队列长度(队列的项目数) 
                                 UBaseType_t uxItemSize, //队列项(消息)的长度(字节)
                                 uint8_t *pucQueueStorage, //指向队列项的存储区
                                 StaticQueue_t *pxStaticQueue);//用来保存队列结构体
/********************静态创建队列**********************************************************************/
QueueHandle_t xQueueGenericCreateStatic(const UBaseType_t uxQueueLength,//要创建的队列长度(队列的项目数)
                                        const UBaseType_t uxItemSize,//队列项(消息)的长度(字节) 
                                        uint8_t *pucQueueStorage,//指向队列项的存储区 
                                        StaticQueue_t *pxStaticQueue,//用来保存队列结构体
                                        const uint8_t ucQueueType);//队列类型
/*****************************************************************************************************/
返回值:创建成功返回队列句柄;失败返回NULL

向队列发送消息

任务级入队函数

/**********发送消息到队尾********************************************************************************/
BaseType_t xQueueSend(QueueHandle_t xQueue,//队列句柄,指明向哪个队列发送数据
                      const void *pvItemToQueue,//指向要发送的消息
                      TickType_t xTicksToWait);//队列满时,任务进入阻塞态等待队列空闲的最大时间(阻塞时间)
/**********发送消息到队尾********************************************************************************/
BaseType_t xQueueSendToBack(QueueHandle_t xQueue,//队列句柄,指明向哪个队列发送数据
                      const void *pvItemToQueue,//指向要发送的消息
                      TickType_t xTicksToWait);//阻塞时间
/**********发送消息到队头********************************************************************************/
BaseType_t xQueueSendToFront(QueueHandle_t xQueue,//队列句柄,指明向哪个队列发送数据
                      const void *pvItemToQueue,//指向要发送的消息
                      TickType_t xTicksToWait);//阻塞时间
/**********发送消息到队列(带覆写功能,即队列满了以后会覆写掉旧的数据)*************************************/
BaseType_t xQueueOverwrite(QueueHandle_t xQueue,//队列句柄,指明向哪个队列发送数据
                      const void *pvItemToQueue);//指向要发送的消息
/**********任务级通用入队函数***************************************************************************/
BaseType_t xQueueGenericSend(QueueHandle_t xQueue, //队列句柄,指明向哪个队列发送数据
                             const void * const pvItemToQueue, //指向要发送的消息
                             TickType_t xTicksToWait,//阻塞时间
                             const BaseType_t xCopyPosition);//入队方式(后向/前向/覆写)
/******************************************************************************************************/
返回值:发送消息成功,返回pdPASS;队列满消息发送失败,返回errQUEUE_FULL

中断级入队函数

/**********发送消息到队尾*****************************************************************************/
BaseType_t xQueueSendFromISR(QueueHandle_t xQueue,//队列句柄,指明向哪个队列发送数据 
                             const void * pvItemToQueue,//指向要发送的消息 
                             BaseType_t * pxHigherPriorityTaskWoken);//标记退出此函数后是否进行任务切换
/**********发送消息到队尾****************************************************************************/
BaseType_t xQueueSendToBackFromISR(QueueHandle_t xQueue, 
                                   const void * pvItemToQueue, 
                                   BaseType_t * pxHigherPriorityTaskWoken);
/**********发送消息到队头****************************************************************************/
BaseType_t xQueueSendToFrontFromISR(QueueHandle_t xQueue, 
                                    const void * pvItemToQueue, 
                                    BaseType_t * pxHigherPriorityTaskWoken);
/**********发送消息到队列(带覆写功能,即队列满了以后会覆写掉旧的数据)********************************/
BaseType_t xQueueOverwriteFromISR(QueueHandle_t xQueue, 
                                  const void * pvItemToQueue, 
                                  BaseType_t * pxHigherPriorityTaskWoken);
/**********中断级通用入队函数***********************************************************************/
BaseType_t xQueueGenericSendFromISR(QueueHandle_t xQueue, 
                                    const void * pvItemToQueue, 
                                    BaseType_t * pxHigherPriorityTaskWoken, 
                                    BaseType_t xCopyPosition);//入队方式(后向/前向/覆写)
/*************************************************************************************************/
返回值:发送消息成功,返回pdPASS;队列满消息发送失败,返回errQUEUE_FULL

队列上锁和解锁

队列上锁 prvLockQueue():本质上是一个宏:

//就是将队列中的成员变量cRxLock和cTxLock设置为queueLOCKED_UNMODIFIED
#define prvLockQueue( pxQueue )                             \
    taskENTER_CRITICAL();                                   \
    {                                                       \
        if( ( pxQueue )->cRxLock == queueUNLOCKED )         \
        {                                                   \
            ( pxQueue )->cRxLock = queueLOCKED_UNMODIFIED;  \
        }                                                   \
        if( ( pxQueue )->cTxLock == queueUNLOCKED )         \
        {                                                   \
            ( pxQueue )->cTxLock = queueLOCKED_UNMODIFIED;  \
        }                                                   \
    }                                                       \
    taskEXIT_CRITICAL()

队列解锁

//上锁计数器cRxLock和cTxLock记录了队列上锁期间,入队或出队的数量
static void prvUnlockQueue( Queue_t * const pxQueue ){

从队列读取消息

任务级出队函数

/**********从队列中读取队列项,读取完后删除队列项**************************************************/
BaseType_t xQueueReceive(QueueHandle_t xQueue,//队列句柄 
                         void * const pvBuffer,//保存数据的缓冲区
                         TickType_t xTicksToWait);//阻塞时间,表示队列空时进入阻塞态等待数据的最大时间
/**********从队列中读取队列项,读取完后不删除队列项************************************************/
BaseType_t xQueuePeek(QueueHandle_t xQueue, 
                      void * const pvBuffer, 
                      TickType_t xTicksToWait);
/**********从队列中读取队列项通用函数************************************************************/
BaseType_t xQueueGenericReceive(QueueHandle_t xQueue, 
                                void * const pvBuffer, 
                                TickType_t xTicksToWait, 
                                const BaseType_t xJustPeeking);//标记读取成功后是否需要删除队列项
/**********************************************************************************************/
返回值:读取数据成功,返回pdTRUE;读取失败,返回pdFALSE

中断级出队函数

/**********从队列中读取队列项,读取完后删除队列项*******************************************/
BaseType_t xQueueReceiveFromISR(QueueHandle_t xQueue,//队列句柄 
                                void * const pvBuffer,//保存数据的缓冲区
                                BaseType_t * pxTaskWoken);//标记退出函数后是否进行任务切换
/**********从队列中读取队列项,读取后不删除队列项*******************************************/
BaseType_t xQueuePeekFromISR(QueueHandle_t xQueue,//队列句柄 
                             void * const pvBuffer,//保存数据的缓冲区);
返回值:读取数据成功,返回pdTRUE;读取失败,返回pdFALSE

FreeRTOS 信号量

信号量是操作系统中重要的一部分,信号量一般用来进行资源管理和任务同步,FreeRTOS中信号量又分为二值信号量、计数型信号量、互斥信号量和递归互斥信号量。

信号量常常用于控制对共享资源的访问和任务同步。信号量用于控制共享资源访问的场景相当于一个上锁机制,代码只有获得了这个锁的钥匙才能够执行。

上面我们讲了信号量在共享资源访问中的使用,信号量的另一个重要的应用场合就是任务

同步,用于任务与任务或中断与任务之间的同步。在执行中断服务函数的时候可以通过向任务发送信号量来通知任务它所期待的事件发生了,当退出中断服务函数以后在任务调度器的调度下同步的任务就会执行。

裸机编写中断服务函数的时候一般都只是在中断服务函数中打个标记,然后在其他的地方根据标记来做具体的处理过程。在使用 RTOS 系统的时候我们就可以借助信号量完成此功能,当中断发生的时候就释放信号量,中断服务函数不做具体的处理。具体的处理过程做成一个任务,这个任务会获取信号量,如果获取到信号量就说明中断发生了

二值信号量

二值信号量即任务与中断间或者两个任务间的标志,该标志非“满”即“空”

二值信号量的使用方法如下图所示:

二值信号量的函数

1 创建二值信号量()

/********************动态创建二值信号量**********************************************/
SemaphoreHandle_t xSemaphoreCreateBinary(void);
/********************静态创建二值信号量**********************************************/
SemaphoreHandle_t xSemaphoreCreateBinaryStatic(StaticSemaphore_t *pxSemaphoreBuffer);
参数:pxSemaphoreBuffer指向一个StaticSemaphore_t类型的变量,用来保存信号量结构体
/***********************************************************************************/
返回值:创建成功返回二值信号量句柄;失败返回NULL

二值信号量创建函数是一个宏,最终是通过xQueueGenericCreate()函数来完成,源码

/*其实就是创建了一个长度为1、队列项长度为0、类型为二值信号量的队列*/
#if( configSUPPORT_DYNAMIC_ALLOCATION == 1 )
#define xSemaphoreCreateBinary()                        \
xQueueGenericCreate((UBaseType_t) 1,                    \
                    semSEMAPHORE_QUEUE_ITEM_LENGTH,     \
                    queueQUEUE_TYPE_BINARY_SEMAPHORE)   \
#endif

2 释放信号量

/********************任务级信号量释放**********************************************/
BaseType_t xSemaphoreGive(SemaphoreHandle_t  xSemaphore)
/********************中断级信号量释放**********************************************/
BaseType_t xSemaphoreGiveFromISR( SemaphoreHandle_t xSemaphore,//要释放的信号量句柄
                         BaseType_t *pxHigherPriorityTaskWoken)//标记退出后是否切换任务
/***********************************************************************************/
返回值:释放成功返回pdPASS;释放失败返回errQUEUE_FULL


二值信号量释放函数xSemaphoreGive()是一个宏,其实就是向队列发送消息,其源码如下

/*其实就是没有具体消息、阻塞时间为0、后向入队的入队过程*/
#define xSemaphoreGive(xSemaphore)              \
xQueueGenericSend((QueueHandle_t) (xSemaphore), \
                  NULL,                         \
                  semGIVE_BLOCK_TIME,           \
                  queueSEND_TO_BACK)            \

获取信号量

/********************任务级信号量获取**********************************************/
BaseType_t xSemaphoreTake(SemaphoreHandle_t  xSemaphore//要获取的信号量句柄
                          TickType_t xBlockTime)//阻塞时间
/********************中断级信号量获取**********************************************/
BaseType_t xSemaphoreTakeFromISR(SemaphoreHandle_t xSemaphore,//要获取的信号量句柄
                         BaseType_t *pxHigherPriorityTaskWoken)//标记退出后是否切换任务
/***********************************************************************************/
返回值:获取成功返回pdPASS;释放失败返回pdFALSE

获取信号量函数也是一个宏,其实就是读取队列的过程,其源码如下

#define xSemaphoreTake(xSemaphore, xBlockTime)      \
xQueueGenericReceive((QueueHandle_t) (xSemaphore),  \
                     NULL,                          \
                     (xBlockTime),                  \
                     pdFALSE)                       \

计数信号量

计数信号量有释放信号量操作和获取信号量操作,释放信号量操作的时候计数器的值会加一,获取信号操作,计数器的值减一,如果减到0任务会进入到等待状态;具体操作方式有两种

计数信号量的API函数

1 创建计数信号量

/********************动态创建计数信号量**********************************************/
SemaphoreHandle_t xSemaphoreCreateCounting(UBaseType_t uxMaxCount,
                                           UBaseType_t uxInitialCount)
/********************静态创建计数信号量**********************************************/
SemaphoreHandle_t xSemaphoreCreateCountingStatic(UBaseType_t uxMaxCount,//信号量最大计数值
                                             UBaseType_t uxInitialCount,//计数信号量初始值
                                  StaticSemaphore_t * pxSemaphoreBuffer)//保存信号量结构体
/***********************************************************************************/
返回值:创建成功返回计数信号量句柄;失败返回NULL

动态计数信号量创建函数是一个宏,最终是通过xQueueCreateCountingSemaphore()函数来完成

QueueHandle_t xQueueCreateCountingSemaphore(const UBaseType_t uxMaxCount,const UBaseType_t uxInitialCount){

2 释放和获取计数信号量

计数型信号量的释放与获取与二值信号量相同

互斥信号量

先级翻转是使用二值信号量时常遇见的问题,在可剥夺内核中非常常见,但是在实时系统中不允许出现这种现象,因为会破坏任务的预期顺序,可能会导致严重后果。

互斥信号量其实就是一个拥有优先级继承的二值信号量。二值信号适合于同步应用中,互斥信号适用于需要互斥访问的应用中。互斥访问中互斥信号量相当于一个钥匙,当任务想要使用资源的时候就必须先获得这个钥匙,使用完资源后必须归还这个钥匙

当一个互斥信号量正被一个低优先级任务使用时,若有高优先级的任务也尝试获取该互斥信号量的话就会被阻塞。不过此时高优先级任务会将低优先级任务的优先级提升到与与自已相同的等级(即优先级继承)。优先级继承只是尽可能降低优先级翻转带来的影响,并不能完全消除优先级翻转

1 创建互斥信号量

/********************动态创建互斥信号量**********************************************/
SemaphoreHandle_t xSemaphoreCreateMutex(void)
/********************静态创建互斥信号量**********************************************/
SemaphoreHandle_t xSemaphoreCreateMutexStatic(StaticSemaphore_t * pxSemaphoreBuffer)
//参数:pxSemaphoreBuffer 指向一个StaticSemaphore_t类型的变量,用来保存信号量结构体
/***********************************************************************************/
返回值:创建成功返回互斥信号量句柄;失败返回NULL

动态互斥信号量创建函数是一个宏,最终是通过xQueueCreateMutex()函数来完成

2 释放互斥信号量

放互斥信号量函数与二值信号量、计数信号量释放函数是一样的。但是由于互斥信号涉及到优先级继承的问题,所以处理过程会有一些区别。

号量释放函数xSemaphoreGive()实际上调用xQueueGenericSend()函数,函数会调用prvCopyDataToQueue()函数。互斥信号量的优先级继承就是在prvCopyDataToQueue()函数中完成的

/**************prvCopyDataToQueue源码分析*********************/                       
static BaseType_t prvCopyDataToQueue(Queue_t * const pxQueue,
                                   const void *pvItemToQueue,
                                  const BaseType_t xPosition)

优先级处理函数xTaskPriorityDisinherit():

BaseType_t xTaskPriorityDisinherit(TaskHandle_t const pxMutexHolder)

3 获取互斥信号量

互斥信号量获取函数与二值信号量、计数信号量获取函数是一样的,都是xSemaphoreTake()函数,最终调用xQueueGenericReceive();获取互斥信号量的过程也需要处理优先级继承的问题,

BaseType_t xQueueGenericReceive(QueueHandle_t xQueue, 
                                void * const pvBuffer, 
                                TickType_t xTicksToWait, 
                                const BaseType_t xJustPeeking)

递归互斥信号量

递归互斥信号量是一种特殊的互斥信号量,已经获取了互斥信号量的任务不能再次获取这个互斥信号量,但是递归互斥信号量不同;已经获取了递归互斥信号量的任务可以再次获取这个递归互斥信号量(即可以嵌套使用),且次数不限。

FreeRTOS 软件定时器

FreeRTOS 事件 标志组

事件位用来表明某个事件是否发生,事件位通常用作事件标志.

信号量只能实现任务与单个事件或任务间的同步。但是某些任务可能会需要与多个事件或任务进行同步,此时就可以使用事件标志组来解决。事件标志组能够实现某个任务与多个事件或任务间的同步.

  • 事件位:用来表明某个事件是否发生,通常用作事件标志

  • 事件组:一组事件位组成一个事件组,事件组中的事件位通过编号来访问

事件标志组的数据类型为 EventGroupHandle_t,事件标志组中的所有事件位都存储在一个无符号的 EventBits_t 类型的变量中;

事件标志组API函数

1 创建事件标志组

/********************动态创建事件标志组**********************************************/
EventGroupHandle_t xEventGroupCreate(void)
/********************静态创建事件标志组**********************************************/
EventGroupHandle_t xEventGroupCreateStatic(StaticEventGroup_t * pxEventGroupBuffer)
//参数:pxEventGroupBuffer指向一个StaticEventGroup_t类型的变量,用来保存事件标志组结构体
/***********************************************************************************/
返回值:创建成功返回事件标志组句柄;失败返回NULL

2 设置事件位

/****************将指定的事件位清零,用在任务中***************************************/
EventBits_t xEventGroupClearBits(EventGroupHandle_t xEventGroup,//要操作的事件标志组句柄
                                 const EventBits_t uxBitsToClear)//要清零的事件位
/****************将指定的事件位置1,用在任务中***************************************/
EventBits_t xEventGroupSetBits(EventGroupHandle_t xEventGroup,//要操作的事件标志组句柄
                               const EventBits_t uxBitsToSet)//要置1的事件位
返回值:将指定事件位清零之前的事件组值;将指定事件位置1后的事件组值
/****************将指定的事件位清零,用在中断服务函数中********************************/
BaseType_t xEventGroupClearBitsFromISR(EventGroupHandle_t xEventGroup,
                                       const EventBits_t uxBitsToClear)//要清零的事件位                           
/****************将指定的事件位置1,用在中断服务函数中********************************/
//#define configUSE_TRACE_FACILITY需要配置为1
BaseType_t xEventGroupSetBitsFromISR(EventGroupHandle_t xEventGroup, 
                                     const EventBits_t uxBitsToSet,//要置1的事件位
                            BaseType_t *pxHigherPriorityTaskWoken)//标记退出后是否切换任务
返回值:清零或置1成功返回pdPASS;清零或置1失败返回pdFALSE

定事件位清零函数xEventGroupClearBits()

EventBits_t xEventGroupClearBits(EventGroupHandle_t xEventGroup,
                                const EventBits_t uxBitsToClear){
    EventGroup_t *pxEventBits = ( EventGroup_t * ) xEventGroup;
    EventBits_t uxReturn;
    taskENTER_CRITICAL();//进入临界段
    {
        /* 获取当前事件标志位 */
        uxReturn = pxEventBits->uxEventBits;
        /* 清除要设置的事件标志位 */
        pxEventBits->uxEventBits &= ~uxBitsToClear;
    }
    taskEXIT_CRITICAL();//退出临界段
    return uxReturn;//返回事件标志组值
}

定事件位置1函数xEventGroupSetBits()

EventBits_t xEventGroupSetBits(EventGroupHandle_t xEventGroup,
                                const EventBits_t uxBitsToSet)

3 获取事件标志组值

/****************获取当前事件标志组的值,用在任务中***********************************/
EventBits_t xEventGroupGetBits(EventGroupHandle_t xEventGroup)//要操作的事件标志组句柄
/****************获取当前事件标志组的值,用在中断服务函数中****************************/
EventBits_t xEventGroupGetBitsFromISR(EventGroupHandle_t xEventGroup)
返回值:当前事件标志组的值

用在任务中获取当前事件标志组的值函数是一个宏定义,

#define xEventGroupGetBits(xEventGroup)    xEventGroupClearBits(xEventGroup, 0)

用在中断服务函数获取当前事件标志组的值函数源码

EventBits_t xEventGroupGetBitsFromISR(EventGroupHandle_t xEventGroup){
    UBaseType_t uxSavedInterruptStatus;
    EventGroup_t *pxEventBits = ( EventGroup_t * ) xEventGroup;
    EventBits_t uxReturn;   
    /* 禁止中断,带返回值 */
    uxSavedInterruptStatus = portSET_INTERRUPT_MASK_FROM_ISR();
    {       
        uxReturn = pxEventBits->uxEventBits;//获取事件标志位
    }
    /* 恢复中断,在进入禁止之前的状态 */
    portCLEAR_INTERRUPT_MASK_FROM_ISR( uxSavedInterruptStatus );
    return uxReturn;
}

4 等待指定的事件位

/****************等待指定的事件位***************************************************/
EventBits_t xEventGroupWaitBits(EventGroupHandle_t xEventGroup, //要等待的事件标志组
                             const EventBits_t uxBitsToWaitFor, //要等待的事件位
                                 const BaseType_t xClearOnExit, //退出是否要清除位
                              const BaseType_t xWaitForAllBits, //与逻辑还是或逻辑
                                       TickType_t xTicksToWait) //阻塞等待时间
参  数:xClearOnExit若为pdTURE,则表示设置的位在函数退出时会被清零;
        xWaitForAllBits若为pdTURE,则表示所有要设置的位都置1或阻塞时间到,函数才会返回
        若为pdFALSE,则表示只要要设置的某一位置1或阻塞时间到,函数就会返回
返回值:返回所等待的事件位置1后的事件标志组的值,或返回阻塞时间到

FreeRTOS 任务通知

可以使用任务通知来代替信号量、消息队列、事件标志组等这些东西。使用任务通知的话效率会更高

reeRTOS 的每个任务都有一个 32 位的通知值,任务控制块中的成员变量 ulNotifiedValue

就是这个通知值。任务通知是一个事件,假如某个任务通知的接收任务因为等待任务通知而阻

塞的话,向这个接收任务发送任务通知以后就会解除这个任务的阻塞状态。也可以更新接收任

务的任务通知值,任务通知可以通过如下方法更新接收任务的通知值:

● 不覆盖接收任务的通知值(如果上次发送给接收任务的通知还没被处理)。

● 覆盖接收任务的通知值。

● 更新接收任务通知值的一个或多个 bit。

● 增加接收任务的通知值。

空闲任务详解

FreeRTOS 的调度器启动以后就会自动的创建一个空闲任务,这样就可以确保至少有一

任务可以运行。但是这个空闲任务使用最低优先级,

FreeRTOS 内存管理

FreeRTOS 中大量使用到了内存管理,比如创建任务、

信号量、队列等会自动从堆中申请内存。用户应用层代码也可以 FreeRTOS 提供的内存管理函

数来申请和释放内存,

20.3 heap_1 内存分配方法

20.4 heap_2 内存分配方法

20.5 heap_3 内存分配方法

20.6 heap_4 内存分配方法

20.7 heap_5 内存分配方法

【学习FreeRTOS】第20章——FreeRTOS内存管理-CSDN博客

FreeRTOS 创建任务、队列、信号量等的时候有两种方法,一种是动态的申请所需的 RAM。

一种是由用户自行定义所需的 RAM,这种方法也叫静态方法,使用静态方法的函数一般以

“Static”结尾,比如任务创建函数 xTaskCreateStatic(),使用此函数创建任务的时候需要由用户

定义任务堆栈,

二值信号量

//二值信号量句柄

SemaphoreHandle_t BinarySemaphore;其中SemaphoreHandle_t的类型是void *,即之前定义了:typedef void * QueueHandle_t;

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值