Linux和,FreeRTOS 任务调度原理,r0-r15寄存器,以及移植freertos(一)

目录、
1、r0-r15寄存器,保护现场,任务切换的原理
2、freertos移植
3、freertos的任务管理。

一、前言

写这篇文章的目的,是之前面试官,刚好问到我,移植FreeRTOS 到mcu,需要做哪些步骤,当时回答的时候,我愣了一下。想了想
基本上说出来了。移植freertos,是五年前学习过,需要修改哪些文件,但是现在一直做的是,linux方向,早就不记得太底层的细节了
但是根据我对arm架构的了解,我还是大概说了步骤。面试过也比较满意。

操作系统是允许多个任务“同时运行”的,操作系统的这个特性被称为多任务。
然而实际上,一个 CPU 核心在某一时刻只能运行一个任务,而操作系统中任务调度器的责任,
就是决定在某一时刻 CPU 究竟要运行哪一个任务,任务调度器使得 CPU 在各个任务之间来回切换并处理任务

首先,我们回想一下,freertos是不是,实时抢占系统,创建的任务,一般有这些状态。多个任务,按时间片轮询,高优先级优先运行。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

有了上面这两张图,是不是,比较清晰了,当发生高优先级事件,中断时,就会发生任务切换,那rtos,要和linux系统一样,可以实现进程,任务的管理,是不是要能获得cpu的管理权,能操控底层的寄存器。

玩过arm架构的mcu还是soc,都知道,对进程管理,任务切换,是需要保存现场的(哪个函数被调用函数被执行到哪句代码,执行过程中,计算的局部变量的值),都是需要保存,才能返回的。

# 发生中断,或者线程上下文,进程上下文,我们需要,保存寄存器状态
pushq %rbx        # 将rbx寄存器的值压入堆栈
pushq %r12        # 将r12寄存器的值压入堆栈
pushq %r13        # 将r13寄存器的值压入堆栈# 执行一些操作,(进程切换,或线程切换,或中断处理)...# 返回,恢复寄存器状态
popq %r13         # 从堆栈中弹出值到r13寄存器
popq %r12         # 从堆栈中弹出值到r12寄存器
popq %rbx         # 从堆栈中弹出值到rbx寄存器# 继续执行,前面未完成的操作。

再来回想下这种图,是不是很熟悉,在学linux还是stm32,应该都见过这种图,cpu的寄存器图。
在这里插入图片描述

R15 PC程序计数器(Program Counter),存储下一条要执行的指令的地址。
R14 LR连接寄存器(Link Register ),保存函数返回地址,当通过BL或BLX指令调用函数时,硬件自动将函数返回地址保存在R14寄存器中。当函数完成时,将LR值传到PC,即可返回到被调用位置。
R13 SP 堆栈指针(Process Stack Pointer),保护现场和恢复现场要用,当发生异常的时候,硬件会把当前状态(使用到寄存器数值)保存在堆栈中,SP保存这个堆栈指针,异常处理完成,通过SP出栈,恢复到异常前的状态,有两种堆栈指针MSP、PSP。(Main_Stack_Pointer 和Process_Stack_Pointer)CPSR 程序状态寄存器(current program status register),CPSR和其他寄存器不一样,其他寄存器是用来存放数据的,都是整个寄存器具有一个含义.而CPSR寄存器是按位起作用的,也就是说,它的每一位都有专门的含义。
函数形参被放在R0-R3中,超过4个参数值传递则放栈里双堆栈指针双堆栈指针对于任务现场保护、恢复现场至关重要。
在这里插入图片描述

可以看下,汇编是如何实现的
在这里插入图片描述

在这里插入图片描述

看下freertos源码,是如何实现的。进入任务切换中断函数,挑选下一个最高优先级的任务(vTaskSwitchContext())去执行

void xPortPendSVHandler( void )
{/* This is a naked function. */__asm volatile("      .syntax unified                               \n""      mrs r0, psp                                    \n""      ldr    r3, pxCurrentTCBConst                   \n" /* Get the location of the current TCB. */"      ldr    r2, [r3]                                \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""      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. */"      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""      msr psp, r0                                   \n" /* Remember the new top of stack for the task. */"      subs r0, r0, #32                              \n" "      ldmia r0!, {r4-r7}                            \n" /* Pop low registers.  */"      bx r3                                          \n""      .align 4                                       \n""pxCurrentTCBConst: .word pxCurrentTCB   ");
}

有了上面这些知识,就很容易了,知道移植free时,需要干些什么事情了。

所以我需要,将这些寄存器,给到freertos系统,让它能够管理mcu,除了这些,还需要设置栈空间大小,时钟等等。

二、移植freertos
在这里插入图片描述
重要的文件如下
在这里插入图片描述

找一个工程文件,开始移植。
在这里插入图片描述
修改完如下
在这里插入图片描述

在这里插入图片描述

移植完这些还不够,还需要如下,
在这里插入图片描述

添加完,需要设置一下kell的环境
在这里插入图片描述

接下来,就可以开始编译了
在这里插入图片描述

#if defined(__ICCARM__) || defined(__CC_ARM) || defined(__GNUC__)#include <stdint.h>extern uint32_t SystemCoreClock;
#endif

2、中断文件修改

此时在编译发现还是有错,分别是SysTick 中断、SVC 中断、PendSV 中断报错,这是因为 FreeRTOS里的port.c文件里已经实现同样功能的函数
如下,freertos实现的。
在这里插入图片描述

前面我们讲过,arm架构,有这些异常模式,7种模式,stm32就这这几种,所以,我们找到stm32f4xx_it.c中断文件,先将SVC_Handler、PendSV_Handler屏蔽掉
用freertos的。
在这里插入图片描述
继续往下找,找到SysTick_Handler,SysTick就是FreeRTOS的一个心跳时钟,FreeRTOS 帮我们实现了 SysTick 的启动的配置:

在 port.c文件中已经实现 vPortSetupTimerInterrupt()函数,并且 FreeRTOS 通用的 SysTick 中断服务函数也实现了:
在 port.c 文件中已经实现xPortSysTickHandler()函数,
所以移植的时候只需要我们在 stm32f10x_it.c文件中,实现我们对应(STM32)平台上的 SysTick_Handler()函数即可。按照以下修改,需要添加头文件FreeRTOS.h和task.h:

#include "FreeRTOS.h"
#include "task.h"extern void xPortSysTickHandler(void);
//systick中断服务函数
void SysTick_Handler(void)
{	if (xTaskGetSchedulerState() != taskSCHEDULER_NOT_STARTED){xPortSysTickHandler();}
}

最后到FreeRTOSConfig.h,最下面找到 #define xPortSysTickHandler SysTick_Handler将其注释掉,这样就解决了3个重复定义的报错
在这里插入图片描述

所以上面报错,就是重定义而已。

3、钩子函数修改

修改好之后继续编译,发现以下与Hook有关的报错,这是因为FreeRTOS定义了,但是没用到
在这里插入图片描述
我们找到FreeRTOSConfig.h将上面4个定义为0
在这里插入图片描述
此时再编译发现没有错误了。

这是我以前企业级项目,移植后的目录。
在这里插入图片描述

三、任务管理

3.1.1 多任务系统
  
  首先对比不使用RTOS,即裸机编程方式,这种编程方式的框架一般都是在 main()函数中使用一个大循环,在循环中顺序地调用相应的函数以处理相应的事务,这个大循环的部分可以视为应用程序的后台,而应用程序的前台,则是各种中断的中断服务函数。因此单任务系统也叫做前后台系统,前后台系统的运行示意图,如下图所示:
在这里插入图片描述

而加上RTOS之后就变成多任务系统,多任务系统的任务调度器会根据相关的任务调度算法,将 CPU 的使用权分配给任务,在任务获取 CPU 使用权之后的极短时间(宏观角度)后,任务调度器又会将 CPU 的使用权分配给其他任务,如此往复,在宏观的角度看来,就像是多个任务同时运行了一样。
在这里插入图片描述
如上图,发现freertos是可以,支持,中断,高优先级抢占的,linux有多种模式,如果要设置为实时,抢占时,需要设置编译内核。
从上图可以看出,相较于单任务系统而言,多任务系统的任务也是具有优先级的,高优先级的任务可以像中断的抢占一样,抢占低优先级任务的 CPU 使用权;优先级相同的任务则各自轮流运行一段极短的时间(宏观角度),从而产生“同时”运行的错觉。以上就是抢占式调度和时间片调度的基本原理。在任务有了优先级的多任务系统中,用户就可以将紧急的事务放在优先级高的任务中进行处理,那么整个系统的实时性就会大大地提高。

我们来看下freertos系统,设置task的结构体,需要比较关注的是,taskname,栈大小,优先级,以及中断函数。

typedef struct
{void (*TaskSetupFunction)(void);TaskFunction_t  task_func;const char*	    task_name;uint16_t		task_statcksize;void *		    pvParameters;UBaseType_t     uxPriority;TaskHandle_t	taskhandle;uint8_t         queue_size;QueueHandle_t   queue_handle;
}task_info_st;
static task_info_st usrtask[]={{MainTaskSetup, 	DoMainTask,         "man", 	1024*4, NULL, 8, NULL, 160, NULL},{NULL,				gps_task,           "gps",  1024*3, NULL, 3, NULL, 80, NULL},{NULL, 				com_task,          	"com", 	1024,   NULL, 7, NULL, 10, NULL},{NbTaskSetup,		nb_task,          	"nbi", 	1024,   NULL, 4, NULL, 50, NULL},{WifiTaskSetup,		wifi_task,          "wifi", 1024,   NULL, 6, NULL, 8, NULL},{RadioTaskSetup,    RadioTask,          "bt",   1024,   NULL, 5, NULL, 8, NULL},{NULL, 				heart_rate_task,    "hrs", 	512 , 	NULL, 2, NULL, 10, NULL},{isrTaskSetup,		isr_task,			"hisr_task",	512 ,	NULL, configMAX_PRIORITIES-1, NULL, 0, NULL},{ATTaskSetup,		readerLoop, 		"AT_task",	512 ,	NULL, configMAX_PRIORITIES-2, NULL, 0, NULL},
};

xTaskCreate函数原型。

BaseType_t xTaskCreate(	TaskFunction_t pxTaskCode,const char * const pcName,const uint16_t usStackDepth,void * const pvParameters,UBaseType_t uxPriority,TaskHandle_t * const pxCreatedTask ) /*lint !e971 Unqualified char types are allowed for strings and */{TCB_t *pxNewTCB;BaseType_t xReturn;#if( portSTACK_GROWTH > 0 )     // if stack grows up{pxNewTCB = ( TCB_t * ) pvPortMalloc( sizeof( TCB_t ) );if( pxNewTCB != NULL ){pxNewTCB->pxStack = ( StackType_t * ) pvPortMalloc( ( ( ( size_t ) usStackDepth ) * sizeof( StackType_t ) ) ); if( pxNewTCB->pxStack == NULL ){/* Could not allocate the stack.  Delete the allocated TCB. */vPortFree( pxNewTCB );pxNewTCB = NULL;}}}#else /* portSTACK_GROWTH */ // Stack grows DOWN on M4F{StackType_t *pxStack;/* Allocate space for the stack used by the task being created. */pxStack = ( StackType_t * ) pvPortMalloc( ( ( ( size_t ) usStackDepth ) * sizeof( StackType_t ) ) ); /*lint !e961 M */if( pxStack != NULL ){/* Allocate space for the TCB. */pxNewTCB = ( TCB_t * ) pvPortMalloc( sizeof( TCB_t ) ); /*lint !e961 MISRA exceptioredundant for some paths. */if( pxNewTCB != NULL ){/* Store the stack location in the TCB. */pxNewTCB->pxStack = pxStack;}else{/* The stack cannot be used as the TCB was not created.  Freeit again. */vPortFree( pxStack );}}else{pxNewTCB = NULL;}}#endif /* portSTACK_GROWTH */if( pxNewTCB != NULL ){#if( tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE != 0 ){/* Tasks can be created statically or dynamically, so note thistask was created dynamically in case it is later deleted. */pxNewTCB->ucStaticallyAllocated = tskDYNAMICALLY_ALLOCATED_STACK_AND_TCB;}#endif /* configSUPPORT_STATIC_ALLOCATION */prvInitialiseNewTask( pxTaskCode, pcName, ( uint32_t ) usStackDepth, pvParameters, uxPriority, pxCreatedTask, pxNewTCB, NULL );prvAddNewTaskToReadyList( pxNewTCB );xReturn = pdPASS;}else{xReturn = errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY;}return xReturn;}

FreeRTOS 任务状态

FreeRTOS 中任务存在四种任务状态,分别为运行态、就绪态、阻塞态和挂起态。
运行态:如果一个任务得到 CPU 的使用权,即任务被实际执行时,那么这个任务处于运行态。如果
运行 RTOS 的 MCU 只有一个处理器核心,那么在任务时刻,都只能有一个任务处理运行态。就绪态:如果一个任务已经能够被执行(不处于阻塞态后挂起态),但当前还未被执行(具有相同优先级或更高优先级的任务正持有 CPU 使用权),那么这个任务就处于就绪态。阻塞态:如果一个任务因延时一段时间或等待外部事件发生,那么这个任务就处理阻塞态。例如任务调用了函数 vTaskDelay(),进行一段时间的延时,那么在延时超时之前,这个任务就处理阻塞态。任务也可以处于阻塞态以等待队列、信号量、事件组、通知或信号量等外部事件。通常情况下,处于阻塞态的任务都有一个阻塞的超时时间,在任务阻塞达到或超过这个超时时间后,即使任务等待的外部事件还没有发生,任务的阻塞态也会被解除。要注意的是,处于阻塞态的任务是无法被运行的。挂起态:任务一般通过函数 vTaskSuspend()和函数 vTaskResums()进入和退出挂起态与阻塞态一样,处于挂起态的任务也无法被运行。

在这里插入图片描述

3.1.3 任务调度器

FreeRTOS一共支持三种任务调度方式,分别为
抢占式调度、
时间片调度
协程式调度。
协程式调度FreeRTOS 官方对协程式调度的特殊说明,翻译过来就是“协程式调度是用于一些资源非常少的设备上的,但是现在已经很少用到了。虽然协程式调度的相关代码还没有被删除,但是今后也不打算继续开发协程式调度。抢占式调度抢占式调度主要时针对优先级不同的任务,每个任务都有一个优先级,优先级高的任务可以抢占优先级低的任务,只有当优先级高的任务发生阻塞或者被挂起,低优先级的任务才可以运行。时间片调度时间片调度主要针对优先级相同的任务,当多个任务的优先级相同时,任务调度器会在每一次系统时钟节拍到的时候切换任务,也就是说 CPU 轮流运行优先级相同的任务,每个任务运行的时间就是一个系统时钟节拍。有关系统时钟节拍的相关内容,在下文讲解 FreeRTOS 系统时钟节拍的时候会具体分析。

在这里插入图片描述
config配置项

configUSE_PREEMPTION:此宏用于设置系统的调度方式。当宏 configUSE_PREEMPTION设置为1时,系统使用抢占式调度;当宏configUSE_PREEMPTION设置为0时,系统使用协程式调度。configUSE_PORT_OPTIMISED_TASK_SELECTION:当宏 configUSE_PORT_OPTIMISED_TASK_SELECTION 设置为 0 时,使用通用方法。通用方法是完全使用 C 实现的软件算法,因此支持所用硬件,并且不限制任务优先级的最大值,但效率相较于特殊方法低。当宏 configUSE_PORT_OPTIMISED_TASK_SELECTION 设置为 1 时,使用特殊方法。特殊方法的效率相较于通用方法高,但是特殊方法依赖于一个或多个特定架构的汇编指令,因此特殊方法并不支持所有硬件,并且对任务优先级的最大值一般也有限制,通常为 32。configUSE_TICKLESS_IDLE:当宏 configUSE_TICKLESS_IDLE 设置为 1 时,使能 tickless 低功耗模式;设置为 0 时,tick 中断则会移植运行。tickless 低功耗模式并不适用于所有硬件。configCPU_CLOCK_HZ:此宏应设置为 CPU 的内核时钟频率,单位为 Hz。configSYSTICK_CLOCK_HZ:此宏应设置为 SysTick 的时钟频率,当 SysTick 的时钟源频率与内核时钟频率不同时才可以定义,单位为 Hz。configTICK_RATE_HZ:此宏用于设置 FreeRTOS 系统节拍的中断频率,单位为HzconfigMAX_PRIORITIES:此宏用于定义系统支持的最大任务优先级 数 量 , 最 大 任 务 优 先 级 数 值 为configMAX_PRIORITIES-1。configMINIMAL_STACK_SIZE:此宏用于设置空闲任务的栈空间大小,单位为 wordconfigMAX_TASK_NAME_LEN:此宏用于设置任务名的最大字符数。configUSE_16_BIT_TICKS:此宏用于定义系统节拍计数器的数据类型,当宏 configUSE_16_BIT_TICKS 设置为 1 时,系统节拍计数器的数据类型为 16 位无符号整形;当宏 configUSE_16_BIT_TICKS 设置为 0 时,系统节拍计数器的数据类型为 32 为无符号整型。configIDLE_SHOULD_YIELD:当宏 configIDLE_SHOULD_YIELD 设置为 1 时,在抢占调度下,同等优先级的任务可抢占空闲任务,并延用空闲任务剩余的时间片。configUSE_TASK_NOTIFICATIONS:当宏 configUSE_TASK_NOTIFICATIONS 设置为 1 时,开启任务通知功能。当开启任务通知功能后,每个任务将多占用 8 字节的内存空间。configTASK_NOTIFICATION_ARRAY_ENTRIES:此宏用于定义任务通知数组的大小。configUSE_MUTEXES:此宏用于使能互斥信号量,当宏 configUSE_MUTEXS 设置为 1 时,使能互斥信号量;当宏configUSE_MUTEXS 设置为 0 时,则不使能互斥信号量。configUSE_RECURSIVE_MUTEXES:此宏用于使能递归互斥信号量,当宏 configUSE_RECURSIVE_MUTEXES 设置为 1 时,使能递归互斥信号量;当宏 configUSE_RECURSIVE_MUTEXES 设置为 0 时,则不使能递归互斥信号量。configUSE_COUNTING_SEMAPHORES:此宏用于使能计数型信号量,当宏 configUSE_COUNTING_SEMAPHORES 设置为 1 时,使能计数型信号量;当宏 configUSE_COUNTING_SEMAPHORES 设置为 0 时,则不使能计数型信号量。configUSE_ALTERNATIVE_API:此宏在 FreeRTOS V9.0.0 之后已弃用。configQUEUE_REGISTRY_SIZE:此宏用于定义可以注册的队列和信号量的最大数量。此宏定义仅用于调试使用。configUSE_QUEUE_SETS:此宏用于使能队列集,当宏 configUSE_QUEUE_SETS 设置为 1 时,使能队列集;当宏configUSE_QUEUE_SETS 设置为 0 时,则不使能队列集。configUSE_TIME_SLICING:此宏用于使能时间片调度,当宏 configUSE_TIMER_SLICING 设置为 1 且使用抢占式调度时,使能时间片调度;当宏 configUSE_TIMER_SLICING 设置为 0 时,则不使能时间片调度。configUSE_NEWLIB_REENTRANT:此宏用于为每个任务分配一个 NewLib 重 入 结 构 体 , 当 宏configUSE_NEWLIB_REENTRANT 设置为 1 时,FreeRTOS 将为每个创建的任务的任务控制块中分配一个 NewLib 重入结构体。configENABLE_BACKWARD_COMPATIBILITY:此宏用于兼容 FreeRTOS 老版本的 API 函数。configNUM_THREAD_LOCAL_STORAGE_POINTERS:此宏用于在任务控制块中分配一个线程本地存储指着数组,当此宏被定义为大于 0 时,configNUM_THREAD_LOCAL_STORAGE_POINTERS 为线程本地存储指针数组的元素个数;当宏 configNUM_THREAD_LOCAL_STORAGE_POINTERS 为 0 时,则禁用线程本地存储指针数组。configSTACK_DEPTH_TYPE:此宏用于定义任务堆栈深度的数据类型,默认为 uint16_t。configMESSAGE_BUFFER_LENGTH_TYPE:此宏用于定义消息缓冲区中消息长度的数据类型,默认为 size_t。configSUPPORT_STATIC_ALLOCATION:当宏 configSUPPORT_STSTIC_ALLOCATION 设置为 1 时,FreeRTOS 支持使用静态方式管理内存,此宏默认设置为 0。如果将configSUPPORT_STATIC_ALLOCATION 设置为 1,用户还 需 要 提 供 两 个 回 调 函 数 : vApplicationGetIdleTaskMemory()vApplicationGetTimerTaskMemory()configSUPPORT_DYNAMIC_ALLOCATION:当宏 configSUPPORT_DYNAMIC_ALLOCATION 设置为 1 时,FreeRTOS 支持使用动态方式管理内存,此宏默认设置为 1。configTOTAL_HEAP_SIZE:此宏用于定义用于 FreeRTOS 动态内存管理的内存大小,即 FreeRTOS 的内存堆,单位为Byte。configAPPLICATION_ALLOCATED_HEAP:此宏用于自定义 FreeRTOS 的内存堆,当宏 configAPPLICATION_ALLOCATED_HEAP 设置为 1 时,用户需要自行创建 FreeRTOS 的内存堆,否则 FreeRTOS 的内存堆将由编译器进行分配。利用此宏定义,可以使用 FreeRTOS 动态管理外扩内存。configSTACK_ALLOCATION_FROM_SEPARATE_HEAP:此宏用于自定义动态创建和删除任务时,任务栈内存的申请与释放函数pvPortMallocStack()vPortFreeStack(),当宏configSTACK_ALLOCATION_FROM_SEPARATE_HEAP 设置为1是,用户需提供 pvPortMallocStack()vPortFreeStack()函数。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

1、函数xTaskCreate()
  此函数用于使用动态的方式创建任务,任务的任务控制块以及任务的栈空间所需的内存,均由 FreeRTOS 从FreeRTOS管理的堆中分配,若使用此函数,需要在FreeRTOSConfig.h文件中将宏configSUPPORT_DYNAMIC_ALLOCATION配置为 1。此函数创建的任务会立刻进入就绪态,由任务调度器调度运行。函数原型如下所示:

BaseType_t xTaskCreate( TaskFunction_t pxTaskCode,const char * const pcName, const configSTACK_DEPTH_TYPE usStackDepth,void * const pvParameters,UBaseType_t uxPriority,TaskHandle_t * const pxCreatedTask)

在这里插入图片描述
在这里插入图片描述

2、函数xTaskCreateStatic()

此函数用于使用静态的方式创建任务,任务的任务控制块以及任务的栈空间所需的内存,需要由用户分配提供, 若使用此函数,需 要 在FreeRTOSConfig.h文件中将宏configSUPPORT_STATIC_ALLOCATION配置为 1。此函数创建的任务会立刻进入就绪态,由任务调度器调度运行。函数原型如下所示:

TaskHandle_t xTaskCreateStatic( TaskFunction_t pxTaskCode,const char * const pcName, /*lint !e971 Unqualified char types are allowed for strings and single characters only. */const uint32_t ulStackDepth,void * const pvParameters,UBaseType_t uxPriority,StackType_t * const puxStackBuffer,StaticTask_t * const pxTaskBuffer )

函数xTaskCreateStatic()的形参描述,如下表所示:
在这里插入图片描述

3、函数vTaskDelete()

此函数用于删除已被创建的任务,被删除的任务将被从就绪态任务列表、阻塞态任务列表、挂起态任务列表和事件列表中移除,要注意的是,空闲任务会负责释放被删除任务中由系统分配的内存,但是由用户在任务删除前申请的内存,则需要由用户在任务被删除前提前释放,否则将导致内存泄露。若使用此函数,需要在FreeRTOSConfig.h文件中将宏INCLUDE_vTaskDelete配置为 1。函数原型如下所示:

void vTaskDelete(TaskHandle_t xTaskToDelete);

4、 挂起和恢复任务相关函数

有时候需要暂停某个任务的运行,如果使用任务删除,那么任务变量的值就丢了。FreeRTOS使用任务挂起和恢复来达到对应效果。FreeRTOS 中用于挂起和恢复任务的 API 函数如下表所示:
在这里插入图片描述
4、函数vTaskSuspend()
  此函数用于 挂 起 任务, 若使用此函数,需 要 在 FreeRTOSConfig.h 文件中将宏INCLUDE_vTaskSuspend配置为 1。无论优先级如何,被挂起的任务都将不再被执行,直到任务被恢复。此函数并不支持嵌套,不论使用此函数重复挂起任务多少次,只需调用一次恢复任务的函数,那么任务就不再被挂起。函数原型如下所示:

void vTaskSuspend(TaskHandle_t xTaskToSuspend)

形参xTaskToSuspend:待挂起任务的任务句柄

5、函数vTaskResume()
  此函数用于在任务中恢复被挂起的任务,若使用此函数,需要在 FreeRTOSConfig.h 文件中将宏INCLUDE_vTaskSuspend配置为 1。不论一个任务被函数 vTaskSuspend()挂起多少次,只需要使用函数 vTakResume()恢复一次,就可以继续运行。函数原型如下所示:
形参xTaskToResume:待恢复任务的任务句柄

6、函数xTaskResumeFromISR()
  此函数用于在中断中恢复被挂起的任务,若使用此函数,需要在 FreeRTOSConfig.h 文件中将宏INCLUDE_xTaskResumeFromISR配置为 1。不论一个任务被函数 vTaskSuspend()挂起多少次,只需要使用函数 vTakResumeFromISR()恢复一次,就可以继续运行。函数原型如下所示:

BaseType_t xTaskResumeFromISR(TaskHandle_t xTaskToResume)

形参xTaskToResume:待恢复任务的任务句柄

四、内核控制函数
  FreeRTOS 内核的控制函数描述,如下表所示:

在这里插入图片描述在这里插入图片描述

在任务创建好之后就开启任务调度。临界段代码也叫做临界区,是指那些必须完整运行,不能被打断的代码段,比如有的外设的初始化需要严格的时序,初始化过程中不能被打断。FreeRTOS在进入临界段代码的时候需要关闭中断,当处理完临界段代码以后再打开中断。FreeRTOS系统本身就有很多的临界段代码,这些代码都加了临界段代码保护,我们在写自己的用户程序的时候有些地方也需要添加临界段代码保护。

4 示例应用程序主要对main.c文件修改,加了一些FreeRTOS任务函数#include "delay.h"
#include "usart.h"
#include "led.h"
#include "FreeRTOS.h"
#include "task.h"//任务优先级
#define START_TASK_PRIO		1
//任务堆栈大小	
#define START_STK_SIZE 		128  
//任务句柄
TaskHandle_t StartTask_Handler;
//任务函数
void start_task(void *pvParameters);//任务优先级
#define LED0_TASK_PRIO		2
//任务堆栈大小	
#define LED0_STK_SIZE 		50  
//任务句柄
TaskHandle_t LED0Task_Handler;
//任务函数
void led0_task(void *pvParameters);//任务优先级
#define LED1_TASK_PRIO		3
//任务堆栈大小	
#define LED1_STK_SIZE 		50  
//任务句柄
TaskHandle_t LED1Task_Handler;
//任务函数
void led1_task(void *pvParameters);//任务优先级
#define FLOAT_TASK_PRIO		4
//任务堆栈大小	
#define FLOAT_STK_SIZE 		128
//任务句柄
TaskHandle_t FLOATTask_Handler;
//任务函数
void float_task(void *pvParameters);int main(void)
{ NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);//设置系统中断优先级分组4delay_init(168);		//初始化延时函数uart_init(115200);     	//初始化串口LED_Init();		        //初始化LED端口//创建开始任务xTaskCreate((TaskFunction_t )start_task,            //任务函数(const char*    )"start_task",          //任务名称(uint16_t       )START_STK_SIZE,        //任务堆栈大小(void*          )NULL,                  //传递给任务函数的参数(UBaseType_t    )START_TASK_PRIO,       //任务优先级(TaskHandle_t*  )&StartTask_Handler);   //任务句柄              vTaskStartScheduler();          //开启任务调度
}//开始任务任务函数
void start_task(void *pvParameters)
{taskENTER_CRITICAL();           //进入临界区//创建LED0任务xTaskCreate((TaskFunction_t )led0_task,     	(const char*    )"led0_task",   	(uint16_t       )LED0_STK_SIZE, (void*          )NULL,				(UBaseType_t    )LED0_TASK_PRIO,	(TaskHandle_t*  )&LED0Task_Handler);   //创建LED1任务xTaskCreate((TaskFunction_t )led1_task,     (const char*    )"led1_task",   (uint16_t       )LED1_STK_SIZE, (void*          )NULL,(UBaseType_t    )LED1_TASK_PRIO,(TaskHandle_t*  )&LED1Task_Handler);        //浮点测试任务xTaskCreate((TaskFunction_t )float_task,     (const char*    )"float_task",   (uint16_t       )FLOAT_STK_SIZE, (void*          )NULL,(UBaseType_t    )FLOAT_TASK_PRIO,(TaskHandle_t*  )&FLOATTask_Handler);  vTaskDelete(StartTask_Handler); //删除开始任务taskEXIT_CRITICAL();            //退出临界区
}//LED0任务函数 
void led0_task(void *pvParameters)
{while(1){LED0=~LED0;vTaskDelay(500);}
}   //LED1任务函数
void led1_task(void *pvParameters)
{while(1){LED1=0;vTaskDelay(200);LED1=1;vTaskDelay(800);}
}//浮点测试任务
void float_task(void *pvParameters)
{static float float_num=0.00;while(1){float_num+=0.01f;printf("float_num的值为: %.4f\r\n",float_num);vTaskDelay(1000);}
}

参考了一些,别人的文章,我自己能分析写出来,但是懒,不想造轮子,以前研究过源码,对进程管理,有比较深刻的认识。
原文链接:https://blog.csdn.net/weixin_44567668/article/details/135419275

这是我的实战项目,我把它简化了,具体任务,没有,只有任务名

int main(void)
{// Set the clock frequency// Set the default cache configuration// Enable the floating point module, and configure the core for lazy// stacking.// Flash bank power set.// Enable printing to the console.// Initialize plotting interface.//initalize gpio// Run the application.run_tasks();// We shouldn't ever get here.while (1){}
}void run_tasks(void)
{// Set some interrupt priorities before we create tasks or start the scheduler.// Create essential tasks.xTaskCreate(setup_task, "Setup", 512, 0, 3, &xSetupTask);// Start the scheduler.vTaskStartScheduler();
}void setup_task(void *pvParameters)
{uint16_t i;// Run setup functions.for(i = 0; i < sizeof(usrtask) / sizeof(usrtask[0]); i++){if(usrtask[i].TaskSetupFunction != NULL){usrtask[i].TaskSetupFunction(); //在启动任务时,如果需要先初始化,比如硬件,比如gpio等等。}if(usrtask[i].queue_size != 0){usrtask[i].queue_handle = xQueueCreate(usrtask[i].queue_size, sizeof(wolf_evt_t));}}// Create the functional tasksfor(i = 0; i < sizeof(usrtask)/sizeof(usrtask[0]); i ++){if(i == Mod_Bt){usrtask[i].taskhandle = radio_task_handle;continue;}xTaskCreate(usrtask[i].task_func, usrtask[i].task_name, usrtask[i].task_statcksize, usrtask[i].pvParameters, usrtask[i].uxPriority, &usrtask[i].taskhandle);usrtask[i].task_func, usrtask[i].task_name);usrtask[i].task_statcksize, usrtask[i].pvParameters, usrtask[i].uxPriority,usrtask[i].taskhandle);}// The setup operations are complete, so suspend the setup task now.vTaskSuspend(NULL);while (1);
}//启动创建下面这些任务
static task_info_st usrtask[]={{MainTaskSetup, 	DoMainTask,         "man", 	1024*4, NULL, 8, NULL, 160, NULL},{NULL,				gps_task,           "gps",  1024*3, NULL, 3, NULL, 80, NULL},{NULL, 				com_task,          	"com", 	1024,   NULL, 7, NULL, 10, NULL},{NbTaskSetup,		nb_task,          	"nbi", 	1024,   NULL, 4, NULL, 50, NULL},{WifiTaskSetup,		wifi_task,          "wifi", 1024,   NULL, 6, NULL, 8, NULL},{NULL, 				heart_rate_task,    "hrs", 	512 , 	NULL, 2, NULL, 10, NULL},{ATTaskSetup,	readerLoop, 		"AT_task",	512 ,	NULL, configMAX_PRIORITIES-2, NULL, 0, NULL},
};void MainTaskSetup(void)
{
}static void main_init(void)
{wolf_evt_t ev = {0,};//start appev.evtId = REVT_ADP_APP_START;SendEvtToMod(Mod_App, &ev);
}void GetEvtForMod (wolf_module_e mod, wolf_evt_t* pev)
{if(mod >= Mod_Num) return ;while(!xQueueReceive(usrtask[mod].queue_handle, pev, portMAX_DELAY));//轮询队列,是否就绪,就绪,就执行
}void DoMainTask(void *pvParameters)
{wolf_evt_t ev = {0,};gsnsr_init();vibmotor_start_default();InitProtocolEventHandler();initKey();GUI_Init();GUI_UC_SetEncodeUCS2();setLanguage(1); // 0 ÖÐÎÄ£¬1 Ó¢ÎÄdrv_rtc_enable_alm_1sec();main_init();while(1){GetEvtForMod(Mod_App, &ev);app_dbgEvt(1, ev.evtId);do{if(app_dispNbMsg(&ev)) break;//类似状态机if(app_dispAppMsg(&ev)) break;if(app_dispGpsMsg(&ev)) break;if(app_dispBtMsg(&ev)) break;if(app_dispWifiMsg(&ev)) break;if(app_dispMMIMsg(&ev)) break;continue;}while(0);app_dbgEvt(0, ev.evtId);}
}static void app_dbgEvt (uint8_t start, uint32_t evtId)
{if (start) {wolf_dbgEvt(0x3d3d3d10);wolf_dbgEvt(evtId);}else wolf_dbgEvt(0x3d3d3d11);
}uint8_t app_dispAppMsg (wolf_evt_t* pev)
{uint8_t ret = 1;switch (pev->evtId){case REVT_ADP_APP_START:app_sys_startInd(pev);break;case REVT_COM_APP_UART_SENDDATA_RSP:break;case REVT_COM_APP_UART_RECVDATA:break;case REVT_COM_APP_UART_IND:break;case REVT_COM_APP_GSENSOR_IND:break;case REVT_COM_APP_MISC_IND:app_sys_miscInd(pev);break;case REVT_APP_APP_TIMEOUT_IND:break;case REVT_ADP_APP_GSNSR_IND:app_gsnsr_ind(pev);break;case REVT_ISR_APP_TOUCH_START:watchserver_first_socket();break;case REVT_HRS_APP_HRS_GET_DATA:call_Run_Report_CB_HR(pev->p1);break;default:ret = 0;break;}return ret;
}

ok,主线终于领情了,还好几个nb,gps等等的,任务,在别的地方。想要研究透freertos,其实并不难,但是,我的方向,暂时linux方向
,这个系统,要是能研究透,年薪40w,真不难。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/464247.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

如果 MySQL 主库出现了问题,从库该何去何从呢?

🚀 博主介绍:大家好,我是无休居士!一枚任职于一线Top3互联网大厂的Java开发工程师! 🚀 🌟 在这里,你将找到通往Java技术大门的钥匙。作为一个爱敲代码技术人,我不仅热衷于探索一些框架源码和算法技巧奥秘,还乐于分享这些宝贵的知识和经验。 💡 无论你是刚刚踏…

基于Matlab 模拟停车位管理系统【源码 GUI】

系统对进入停车位的车辆进行车牌识别&#xff0c;将识别出来的车牌号显示出来&#xff1b;然后对车主进行人脸识别&#xff0c;框出车主照片的人脸部分作为车主信息的标记&#xff0c;记录在系统库中。车辆在库期间&#xff0c;系统使用者可以随意查看车辆与车主信息的获取过程…

【docker】docker 环境配置及安装

本文介绍基于 官方存储库 docker 的环境配置、安装、代理配置、卸载等相关内容。 官方安装文档说明&#xff1a;https://docs.docker.com/engine/install/ubuntu/ 主机环境 宿主机环境 Ubuntu 20.04.6 LTS 安装步骤 添加相关依赖 sudo apt-get update sudo apt-get install…

【论文阅读笔记】Wavelet Convolutions for Large Receptive Fields

1.论文介绍 Wavelet Convolutions for Large Receptive Fields 大感受野的小波卷积 2024 EECV Paper Code 2.摘要 近年来&#xff0c;人们试图通过增加卷积神经网络&#xff08;ConvolutionalNeuralNets&#xff0c;CNNs&#xff09;的核尺寸来模拟视觉变换器&#xff08;V…

DFS求解迷宫最长移动路线

来源:十四届蓝桥杯STEMA考试Python真题试卷第二套编程第五题 本文给出了C++实现代码,介绍了 STL 中容器vector,pair,unordered_set 的应用,供信奥选手参考。迷宫类问题适合用DFS算法解决,本文最后总结了DFS算法的两种常见实现方式——递归实现、栈实现,应用场景——迷宫…

【react使用AES对称加密的实现】

react使用AES对称加密的实现 前言使用CryptoJS库密钥存放加密方法解密方法结语 前言 项目中要求敏感信息怕被抓包泄密必须进行加密传输处理&#xff0c;普通的md5加密虽然能解决传输问题&#xff0c;但是项目中有权限的用户是需要查看数据进行查询的&#xff0c;所以就不能直接…

SpringBoot新闻稿件管理系统:架构与实现

3系统分析 3.1可行性分析 通过对本新闻稿件管理系统实行的目的初步调查和分析&#xff0c;提出可行性方案并对其一一进行论证。我们在这里主要从技术可行性、经济可行性、操作可行性等方面进行分析。 3.1.1技术可行性 本新闻稿件管理系统采用SSM框架&#xff0c;JAVA作为开发语…

光耦合器的关键作用和创新---腾恩科技

光耦合器或光隔离器已成为电路中必不可少的器件&#xff0c;它允许信号在无需直接电接触的情况下跨不同电压域传输。这种隔离能力对于保护低压元件免受高压电路的潜在损坏至关重要。本文将仔细研究光耦合器在当今技术中发挥的独特作用&#xff0c;并探讨其在各种应用中不断扩展…

HbuildderX运行到手机或模拟器的Android App基座识别不到设备 mac

寻找模拟器 背景&#xff1a; 运行的是h5&#xff0c;模拟器是网易MuMu。 首先检查一下是否配置dab环境&#xff0c;adb version 配置一下hbuilderX的adb&#xff1a; 将命令输出的路径配置到hbuilderx里面去&#xff0c;然后重启下HbuilderX。 开始安装基座…一直安装不…

使用 Spring Boot 搭建 WebSocket 服务器实现多客户端连接

在 Web 开发中&#xff0c;WebSocket 为客户端和服务端之间提供了实时双向通信的能力。本篇博客介绍如何使用 Spring Boot 快速搭建一个 WebSocket 服务器&#xff0c;并支持多客户端的连接和消息广播。 1. WebSocket 简介 WebSocket 是 HTML5 的一种协议&#xff0c;提供了客…

C# 日志框架 NLog、log4net 和 Serilog对比

文章目录 前言NLog、log4net 和 Serilog 三个框架的详细对比:一、NLog优点:缺点:二、 log4net优点缺点三、Serilog优点缺点四、Serilog使用举例总结前言 NLog、log4net 和 Serilog 三个框架的详细对比: NLog、log4net 和 Serilog 是三个非常流行的 .NET 日志框架,它们各自…

从0开始本地部署大模型

这就开始从0开始本地部署大模型 下载Ollama 下载地址&#xff1a;https://ollama.com/download/windows 适用于MacOS、Linux和Windows&#xff0c;这里我下载Windows的安装包。 直接打开安装包&#xff0c;点击install即可&#xff0c;安装完成后可以在任务栏中看到Ollama程…

RHCSA课后练习3(网络与磁盘)

1、配置网络&#xff1a;为网卡添加一个本网段IPV4地址&#xff0c;x.x.x.123 涉及的知识点 配置网络&#xff1a; ens160&#xff1a;en---表示以太网 wl---表示无线局域网 ww---表示无线广域网 注意&#xff1a;一个网络接口&#xff0c;可以有多个网络连接&#xff0c;但…

Linux:网络协议socket

我们之前学的通信是本地进程间通信&#xff0c;如果我们想在网络间通信的话&#xff0c;就需要用到二者的ip地址&#xff0c;分别被称为源IP地址和目的IP地址&#xff0c;被存入ip数据包中&#xff0c;其次我们还需要遵循一些通信协议。 TCP协议&#xff1a;传输层协议&#x…

相机硬触发

PLC 接线图 通过使用PNP光电感应器 实现相机的硬触发 流程&#xff1a;触发相机拍照 然后相机控制光源触发 完成线路连接后 使用MVS 配置相机硬触发参数 通过 pnp传感器控制 硬触发拍照 检测 在2开项目中 不用在点击执行流程 通过PNP传感器就能触发 扩展&#xff1a; 在VP…

浅谈UI自动化

⭐️前言⭐️ 本篇文章围绕UI自动化来展开&#xff0c;主要内容包括什么是UI自动化&#xff0c;常用的UI自动化框架&#xff0c;UI自动化原理等。 &#x1f349;欢迎点赞 &#x1f44d; 收藏 ⭐留言评论 &#x1f349;博主将持续更新学习记录收获&#xff0c;友友们有任何问题…

儿童安全座椅行业全面深入分析

儿童安全座椅就是一种专为不同体重&#xff08;或年龄段&#xff09;的儿童设计&#xff0c;将孩子束缚在安全座椅内&#xff0c;能有效提高儿童乘车安全的座椅。欧洲强制性执行标准ECE R44/03的定义是&#xff1a;能够固定到机动车辆上&#xff0c;带有ISOFIX接口、LATCH接口的…

net core Autofac 替换默认的服务容器 DI,微软自动的容器 不支持命名选项的

微软默认的容器&#xff0c;不支持命名选项&#xff0c;同一接口&#xff0c;多个实现。 就不支持了。 配置core 支持Autofac 容器 using Autofac; using Autofac.Extensions.DependencyInjection;namespace WebApplication13 {public interface IMyService{string GetData()…

架构系列---高并发

目录标题 前言宏观架构细节解读第一层 &#xff1a;DNS第二层 &#xff1a; LVS 负载第三层 &#xff1a; Nginx第四层 &#xff1a; Gateway Application并发上限更多方案 业务扩展从域名角度如何承受更大的流量从业务的角度看如何分流大的流量 总结 前言 年轻的时候看到文章…

植被遥感常用反射特征表达

Figure: HDRF Let Ω ′ \Omega Ω′ be the incident solid angle, Ω \Omega Ω is leaving solid angle. Consider the BRDF of a Lamvertian target is 1 π \frac{1}{\pi} π1​, the BRF is 1. The HDRF of a target is defined as: R h e m ( Ω ) Φ r Φ r l a …