FreeRTOS任务管理

news2024/11/24 2:00:14

RTOS 的核心是如果高效管理各个任务及任务之间通信,本章将向大家介绍 FreeRTOS 的任务管理,通过本章的学习,让大家对 RTOS 任务的理解更加深入, 为后面的学习做好铺垫。本章分为如下几部分内容: 1 任务管理介绍 2 常用任务 API 函数 3 任务的设计要点 4 硬件设计 5 软件设计 6 实 验现象

1 任务管理介绍

1 任务简介 从系统的角度看,任务是竞争系统资源的最小运行单元。FreeRTOS 是一个 支持多任务的操作系统。在 FreeRTOS 中,任务可以使用或等待 CPU、使用内存 空间等系统资源,并独立于其它任务运行,任何数量的任务可以共享同一个优先 级,如果宏 configUSE_TIME_SLICING 定义为 1,处于就绪态的多个相同优先级 任务将会以时间片切换的方式共享处理器。 简而言之:FreeRTOS 的任务可认为是一系列独立任务的集合。每个任务在自 己的环境中运行。在任何时刻,只有一个任务得到运行,FreeRTOS 调度器决定 运行哪个任务。调度器会不断的启动、停止每一个任务,宏观看上去所有的任务 都在同时在执行。作为任务,不需要对调度器的活动有所了解,在任务切入切出 时保存上下文环境(寄存器值、堆栈内容)是调度器主要的职责。为了实现这点, 每个 FreeRTOS 任务都需要有自己的栈空间。 当任务切出时,它的执行环境会被保存在该任务的栈空间中,这样当任务再 次运行时,就能从堆栈中正确的恢复上次的运行环境,任务越多,需要的堆栈空 间就越大,而一个系统能运行多少个任务,取决于系统的可用的 SRAM。 FreeRTOS 可以给用户提供多个任务单独享有独立的堆栈空间,系统可以决 定任务的状态,决定任务是否可以运行,同时还能运用内核的 IPC 通信资源, 实现了任务之间的通信,帮助用户管理业务程序流程。这样用户可以将更多的精 力投入到业务功能的实现中。 FreeRTOS 中的任务是抢占式调度机制,高优先级的任务可打断低优先级任 务,低优先级任务必须在高优先级任务阻塞或结束后才能得到调度。同时 FreeRTOS 也支持时间片轮转调度方式,只不过时间片的调度是不允许抢占任务 的 CPU 使用权。 任务通常会运行在一个死循环中,也不会退出,如果一个任务不再需要,可 以调用 FreeRTOS 中的任务删除 API 函数接口显式地将其删除

.2 任务调度器简介 FreeRTOS 中提供的任务调度器是基于优先级的全抢占式调度:在系统中除 了中断处理函数、调度器上锁部分的代码和禁止中断的代码是不可抢占的之外, 系统的其他部分都是可以抢占的。系统理论上可以支持无数个优先级(0 ~ N, 优先级数值越小的任务优先级越低,0 为最低优先级,分配给空闲任务使用,一 般不建议用户来使用这个优先级。假如使能了 configUSE_PORT_OPTIMISED_TASK_SELECTION 这个宏(在 FreeRTOSConfig.h 文 件定义),一般强制限定最大可用优先级数目为 32。在一些资源比较紧张的系 统中,可以根据实际情况选择只支持 8 个或 32 个优先级的系统配置。在系统 中,当有比当前任务优先级更高的任务就绪时,当前任务将立刻被换出,高优先 级任务抢占处理器运行。 一个操作系统如果只是具备了高优先级任务能够“立即”获得处理器并得到 执行的特点,那么它仍然不算是实时操作系统。因为这个查找最高优先级任务的 过程决定了调度时间是否具有确定性,例如一个包含 n 个就绪任务的系统中, 如果仅仅从头找到尾,那么这个时间将直接和 n 相关,而下一个就绪任务抉择 时间的长短将会极大的影响系统的实时性。 FreeRTOS 内核中采用两种方法寻找最高优先级的任务,第一种是通用的方 法,在就绪链表中查找从高优先级往低查找 uxTopPriority,因为在创建任务的 时候已经将优先级进行排序,查找到的第一个 uxTopPriority 就是我们需要的 任务,然后通过 uxTopPriority 获取对应的任务控制块。第二种方法则是特殊 方法,利用计算前导零指令 CLZ,直接在 uxTopReadyPriority 这个 32 位的变 量中直接得出 uxTopPriority,这样子就知道哪一个优先级任务能够运行,这种 调度算法比普通方法更快捷,但受限于平台(在 STM32 中我们就使用这种方法)。 FreeRTOS 内核中也允许创建相同优先级的任务。相同优先级的任务采用时 间片轮转方式进行调度(也就是通常说的分时调度器),时间片轮转调度仅在当 前系统中无更高优先级就绪任务存在的情况下才有效。为了保证系统的实时性, 系统尽最大可能地保证高优先级的任务得以运行。任务调度的原则是一旦任务状 态发生了改变,并且当前运行的任务优先级小于优先级队列组中任务最高优先级 时,立刻进行任务切换(除非当前系统处于中断处理程序中或禁止任务切换的状 态)。

3 任务状态简介 FreeRTOS 系统中的每一任务都有多种运行状态。系统初始化完成后,创建 的任务就可以在系统中竞争一定的资源,由内核进行调度。 任务状态通常分为以下四种: 就绪(Ready):该任务在就绪列表中,就绪的任务已经具备执行的能力, 只等待调度器进行调度,新创建的任务会初始化为就绪态。 运行(Running):该状态表明任务正在执行,此时它占用处理器,FreeRTOS 调度器选择运行的永远是处于最高优先级的就绪态任务,当任务被运行的一刻, 它的任务状态就变成了运行态。 阻塞(Blocked):如果任务当前正在等待某个时序或外部中断,我们就说 这个任务处于阻塞状态,该任务不在就绪列表中。包含任务被挂起、任务被延时、 任务正在等待信号量、读写队列或者等待读写事件等。 挂起态(Suspended):处于挂起态的任务对调度器而言是不可见的,让一个 任务进入挂起状态的唯一办法就是调用 vTaskSuspend()函数;而把一个挂起状 态的任务恢复的唯一途径就是调用 vTaskResume() 或 vTaskResumeFromISR() 函数,我们可以这么理解挂起态与阻塞态的区别,当任务有较长的时间不允许运 行的时候,我们可以挂起任务,这样子调度器就不会管这个任务的任何信息,直 到我们调用恢复任务的 API 函数;而任务处于阻塞态的时候,系统还需要判断 阻塞态的任务是否超时,是否可以解除阻塞。 他们之间的转换关系是怎么样的呢?从运行态任务变成阻塞态,或者从阻塞 态变成就绪态,这些任务状态是如何进行迁移?下面就让我们一起了解任务状态 迁移,如下图所示。

图(1):创建任务→就绪态(Ready):任务创建完成后进入就绪态,表明任 务已准备就绪,随时可以运行,只等待调度器进行调度。 图(2):就绪态→运行态(Running):发生任务切换时,就绪列表中最高优 先级的任务被执行,从而进入运行态。 图(3):运行态→就绪态:有更高优先级任务创建或者恢复后,会发生任务 调度,此刻就绪列表中最高优先级任务变为运行态,那么原先运行的任务由运行 态变为就绪态,依然在就绪列表中,等待最高优先级的任务运行完毕继续运行原 来的任务(此处可以看做是 CPU 使用权被更高优先级的任务抢占了)。 图(4):运行态→阻塞态(Blocked):正在运行的任务发生阻塞(挂起、延 时、读信号量等待)时,该任务会从就绪列表中删除,任务状态由运行态变成阻 塞态,然后发生任务切换,运行就绪列表中当前最高优先级任务。 图(5):阻塞态→就绪态:阻塞的任务被恢复后(任务恢复、延时时间超时、 读信号量超时或读到信号量等),此时被恢复的任务会被加入就绪列表,从而由 阻塞态变成就绪态;如果此时被恢复任务的优先级高于正在运行任务的优先级, 则会发生任务切换,将该任务将再次转换任务状态,由就绪态变成运行态。 图(6)(7)(8):就绪态、阻塞态、运行态→挂起态(Suspended):任务可以 通过调用 vTaskSuspend() API 函数都可以将处于任何状态的任务挂起,被挂起 的任务得不到 CPU 的使用权,也不会参与调度,除非它从挂起态中解除。 图(9):挂起态→就绪态:把一个挂起状态的任务恢复的唯一途径就是调用 vTaskResume() 或 vTaskResumeFromISR() API 函数,如果此时被恢复任务的优 先级高于正在运行任务的优先级,则会发生任务切换,将该任务将再次转换任务 状态,由就绪态变成运行态

2 常用任务 API 函数

.1 任务挂起函数

1 vTaskSuspend() 挂起指定任务。被挂起的任务绝不会得到 CPU 的使用权,不管该任务具有 什么优先级。任务可以通过调用 vTaskSuspend()函数都可以将处于任何状态的 任务挂起,被挂起的任务得不到 CPU 的使用权,也不会参与调度,它相对于调 度器而言是不可见的,除非它从挂起态中解除

2 vTaskSuspendAll() 这个函数将所有的任务都挂起,其实源码很简单,也很有意思,不管三七二 十一将调度器锁定,并且这个函数是可以进行嵌套的,说白了挂起所有任务就是 挂起任务调度器。调度器被挂起后则不能进行上下文切换,但是中断还是使能的。 当调度器被挂起的时候,如果有中断需要进行上下文切换,那么这个任务将会被 挂起,在调度器恢复之后才执行切换任务。vTaskSuspendAll()源码具体代码如 下。调度器恢复可以调用 xTaskResumeAll() 函数,调用了多少次的 vTaskSuspendAll() 就要调用多少次 xTaskResumeAll()进行恢复, xTaskResumeAll()的源码会在恢复任务函数中讲解。

void vTaskSuspendAll( void )
{
++uxSchedulerSuspended;   // (1)
}

代码(1):uxSchedulerSuspended 用于记录调度器是否被挂起,该变量默认

初始值为 pdFALSE,表明调度器是没被挂起的,每调用一次 vTaskSuspendAll()

函数就将变量加一,用于记录调用了多少次 vTaskSuspendAll()函数。

.2 任务恢复函数

  1. vTaskResume()

既然有任务的挂起,自然有恢复,任务恢复就是让挂起的任务重新进入就绪 状态,恢复的任务会保留挂起前的状态信息,在恢复的时候根据挂起时的状态继 续运行。如果被恢复任务在所有就绪态任务中,处于最高优先级列表的第一位, 那么系统将进行任务上下文的切换

2. xTaskResumeFromISR()

xTaskResumeFromISR()与 vTaskResume()一样都是用于恢复被挂起的任务, 不一样的是 xTaskResumeFromISR()专门用在中断服务程序中。无论通过调用一 次或多次 vTaskSuspend()函数而被挂起的任务,也只需调用一次 xTaskResumeFromISR()函数即可解挂。要想使用该函数必须在 FreeRTOSConfig.h 中 把 INCLUDE_vTaskSuspend 和 INCLUDE_vTaskResumeFromISR 都定义为 1 才有效。任务还没有处于挂起态的时 候,调用 xTaskResumeFromISR()函数是没有任何意义的,xTaskResumeFromISR() ;

3. xTaskResumeFromISR() xTaskResumeFromISR()与 vTaskResume()一样都是用于恢复被挂起的任务, 不一样的是 xTaskResumeFromISR()专门用在中断服务程序中。无论通过调用一 次或多次 vTaskSuspend()函数而被挂起的任务,也只需调用一次 xTaskResumeFromISR()函数即可解挂。要想使用该函数必须在 FreeRTOSConfig.h 中 把 INCLUDE_vTaskSuspend 和 INCLUDE_vTaskResumeFromISR 都定义为 1 才有效。任务还没有处于挂起态的时 候,调用 xTaskResumeFromISR()函数是没有任何意义的,xTaskResumeFromISR() 源码如下

.3 任务删除函数 vTaskDelete(

vTaskDelete()用于删除一个任务。当一个任务删除另外一个任务时,形参 为要删除任务创建时返回的任务句柄,如果是删除自身,则形参为 NULL。 要想 使用该函数必须在 FreeRTOSConfig.h 中把 INCLUDE_vTaskDelete 定义为 1, 删除的任务将从所有就绪,阻塞,挂起和事件列表中删除,

4 任务延时函数

1 vTaskDelay()

vTaskDelay()在我们任务中用得非常之多,每个任务都必须是死循环,并且 是必须要有阻塞的情况,否则低优先级的任务就无法被运行了。要想使用 FreeRTOS 中的 vTaskDelay()函数必须在 FreeRTOSConfig.h 中把 INCLUDE_vTaskDelay 定义为 1 来使能。 void vTaskDelay( const TickType_t xTicksToDelay ) vTaskDelay()用于阻塞延时,调用该函数后,任务将进入阻塞状态,进入阻 塞态的任务将让出 CPU 资源。延时的时长由形参 xTicksToDelay 决定,单位为 系统节拍周期,比如系统的时钟节拍周期为 1ms,那么调用 vTaskDelay(1)的延 时时间则为 1ms。 vTaskDelay()延时是相对性的延时,它指定的延时时间是从调用 vTaskDelay()结束后开始计算的,经过指定的时间后延时结束。比如 vTaskDelay(100), 从调用 vTaskDelay()结束后,任务进入阻塞状态,经过 100 个系统时钟节拍周期后,任务解除阻塞。因此,vTaskDelay()并不适用与周期性 执行任务的场合。此外,其它任务和中断活动,也会影响到 vTaskDelay()的调 用(比如调用前高优先级任务抢占了当前任务),进而影响到任务的下一次执行 的时间,

.2 vTaskDelayUntil()

在 FreeRTOS 中,除了相对延时函数,还有绝对延时函数 vTaskDelayUntil(),这个绝对延时常用于较精确的周期运行任务,比如我有一 个任务,希望它以固定频率定期执行,而不受外部的影响,任务从上一次运行开 始到下一次运行开始的时间间隔是绝对的,而不是相对的,下面来学习一下 vTaskDelayUntil()函数的实现过程,函数原型具体如下。

#if ( INCLUDE_vTaskDelayUntil == 1 )
void vTaskDelayUntil( TickType_t * const pxPreviousWakeTime,
const TickType_t xTimeIncrement );

要想使用该函数必须在 FreeRTOSConfig.h 中把 INCLUDE_vTaskDelayUntil 定义为 1 来使能。 vTaskDelayUntil()与 vTaskDelay ()一样都是用来实现任务的周期性延时。 但 vTaskDelay ()的延时是相对的,是不确定的,它的延时是等 vTaskDelay () 调用完毕后开始计算的。并且 vTaskDelay ()延时的时间到了之后,如果有高优 先级的任务或者中断正在执行,被延时阻塞的任务并不会马上解除阻塞,所有每 次执行任务的周期并不完全确定。而 vTaskDelayUntil()延时是绝对的,适用于 周期性执行的任务。当(*pxPreviousWakeTime + xTimeIncrement)时间到达后, vTaskDelayUntil()函数立刻返回,如果任务是最高优先级的,那么任务会立马 解除阻塞,所以说 vTaskDelayUntil()函数的延时是绝对性的,

3 任务的设计要点

作为一个嵌入式开发人员,要对自己设计的嵌入式系统了如指掌,任务的优 先级信息,任务与中断的处理,任务的运行时间、逻辑、状态等都要知道,才能 设计出好的系统,所以,在设计的时候需要根据需求制定框架。在设计之初就应 该考虑下面几点因素:任务运行的上下文环境、任务的执行时间合理设计。 FreeRTOS 中程序运行的上下文包括: ①中断服务函数。 ②普通任务。 ③空闲任务。 ④任务执行时间。 ①中断服务函数: 中断服务函数是一种需要特别注意的上下文环境,它运行在非任务的执行环 境下(一般为芯片的一种特殊运行模式(也被称作特权模式)),在这个上下文 环境中不能使用挂起当前任务的操作,不允许调用任何会阻塞运行的 API 函数 接口。另外需要注意的是,中断服务程序最好保持精简短小,快进快出,一般在 中断服务函数中只做标记事件的发生,然后通知任务,让对应任务去执行相关处 理,因为中断服务函数的优先级高于任何优先级的任务,如果中断处理时间过长, 将会导致整个系统的任务无法正常运行。所以在设计的时候必须考虑中断的频 率、中断的处理时间等重要因素,以便配合对应中断处理任务的工作。 ②任务: 任务看似没有什么限制程序执行的因素,似乎所有的操作都可以执行。但是 做为一个优先级明确的实时系统,如果一个任务中的程序出现了死循环操作(此 处的死循环是指没有阻塞机制的任务循环体),那么比这个任务优先级低的任务 都将无法执行,当然也包括了空闲任务,因为死循环的时候,任务不会主动让出 CPU,低优先级的任务是不可能得到 CPU 的使用权的,而高优先级的任务就可以 抢占 CPU。这个情况在实时操作系统中是必须注意的一点,所以在任务中不允许 出现死循环。如果一个任务只有就绪态而无阻塞态,势必会影响到其他低优先级 任务的执行,所以在进行任务设计时,就应该保证任务在不活跃的时候,任务可 以进入阻塞态以交出 CPU 使用权,这就需要我们自己明确知道什么情况下让任 务进入阻塞态,保证低优先级任务可以正常运行。在实际设计中,一般会将紧急 的处理事件的任务优先级设置得高一些。 ③空闲任务: 空闲任务(idle 任务)是 FreeRTOS 系统中没有其他工作进行时自动进入 的系统任务。因为处理器总是需要代码来执行——所以至少要有一个任务处于运 行态。FreeRTOS 为了保证这一点,当调用 vTaskStartScheduler()时,调度器 会自动创建一个空闲任务,空闲任务是一个非常短小的循环。用户可以通过空闲 任务钩子方式,在空闲任务上钩入自己的功能函数。通常这个空闲任务钩子能够 完成一些额外的特殊功能,例如系统运行状态的指示,系统省电模式等。除了空 闲任务钩子,FreeRTOS 系统还把空闲任务用于一些其他的功能,比如当系统删 除一个任务或一个动态任务运行结束时,在执行删除任务的时候,并不会释放任 务的内存空间,只会将任务添加到结束列表中,真正的系统资源回收工作在空闲 任务完成,空闲任务是唯一一个不允许出现阻塞情况的任务,因为 FreeRTOS 需 要保证系统永远都有一个可运行的任务。 对于空闲任务钩子上挂接的空闲钩子函数,它应该满足以下的条件: ·永远不会挂起空闲任务; ·不应该陷入死循环,需要留出部分时间用于系统处理系统资源回收。 ④任务的执行时间: 任务的执行时间一般是指两个方面,一是任务从开始到结束的时间,二是任 务的周期。 在系统设计的时候这两个时间候我们都需要考虑,例如,对于事件 A 对应 的服务任务 Ta,系统要求的实时响应指标是 10ms,而 Ta 的最大运行时间是 1ms,那么 10ms 就是任务 Ta 的周期了,1ms 则是任务的运行时间,简单来说 任务 Ta 在 10ms 内完成对事件 A 的响应即可。此时,系统中还存在着以 50ms 为周期的另一任务 Tb,它每次运行的最大时间长度是 100us。在这种情况下, 即使把任务 Tb 的优先级抬到比 Ta 更高的位置,对系统的实时性指标也没什么 影响,因为即使在 Ta 的运行过程中,Tb 抢占了 Ta 的资源,等到 Tb 执行完 毕,消耗的时间也只不过是 100us,还是在事件 A 规定的响应时间内(10ms), Ta 能够安全完成对事件 A 的响应。但是假如系统中还存在任务 Tc,其运行时 间为 20ms,假如将 Tc 的优先级设置比 Ta 更高,那么在 Ta 运行的时候,突 然间被 Tc 打断,等到 Tc 执行完毕,那 Ta 已经错过对事件 A(10ms)的响应 了,这是不允许的。所以在我们设计的时候,必须考虑任务的时间,一般来说处 理时间更短的任务优先级应设置更高一些

整体代码

#include "system.h"
#include "SysTick.h"
#include "led.h"
#include "usart.h"
#include "FreeRTOS.h"
#include "task.h"
#include "key.h"


//任务优先级
#define START_TASK_PRIO        1
//任务堆栈大小    
#define START_STK_SIZE         128  
//任务句柄
TaskHandle_t StartTask_Handler;
//任务函数
void start_task(void *pvParameters);

//任务优先级
#define LED1_TASK_PRIO        2
//任务堆栈大小    
#define LED1_STK_SIZE         50  
//任务句柄
TaskHandle_t LED1Task_Handler;
//任务函数
void led1_task(void *pvParameters);

//任务优先级
#define LED2_TASK_PRIO        3
//任务堆栈大小    
#define LED2_STK_SIZE         50  
//任务句柄
TaskHandle_t LED2Task_Handler;
//任务函数
void led2_task(void *pvParameters);

//任务优先级
#define KEY_TASK_PRIO        4
//任务堆栈大小    
#define KEY_STK_SIZE         50  
//任务句柄
TaskHandle_t KEYTask_Handler;
//任务函数
void key_task(void *pvParameters);


/*******************************************************************************
* 函 数 名         : main
* 函数功能           : 主函数
* 输    入         : 无
* 输    出         : 无
*******************************************************************************/
int main()
{
    SysTick_Init(72);
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);//设置系统中断优先级分组4
    LED_Init();
    KEY_Init();
    USART1_Init(115200);
    
    //创建开始任务
    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();           //进入临界区
      
    //创建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); 
                
    //创建LED2任务
    xTaskCreate((TaskFunction_t )led2_task,     
                (const char*    )"led2_task",   
                (uint16_t       )LED2_STK_SIZE, 
                (void*          )NULL,
                (UBaseType_t    )LED2_TASK_PRIO,
                (TaskHandle_t*  )&LED2Task_Handler);

    //创建KEY任务
    xTaskCreate((TaskFunction_t )key_task,     
                (const char*    )"key_task",   
                (uint16_t       )KEY_STK_SIZE, 
                (void*          )NULL,
                (UBaseType_t    )KEY_TASK_PRIO,
                (TaskHandle_t*  )&KEYTask_Handler);
                
    vTaskDelete(StartTask_Handler); //删除开始任务
    taskEXIT_CRITICAL();            //退出临界区
} 

//LED1任务函数
void led1_task(void *pvParameters)
{
    while(1)
    {
        LED1=0;
        vTaskDelay(200);
        LED1=1;
        vTaskDelay(800);
    }
}

//LED2任务函数
void led2_task(void *pvParameters)
{
    while(1)
    {
        LED2=0;
        vTaskDelay(800);
        LED2=1;
        vTaskDelay(200);
    }
}

//KEY任务函数
void key_task(void *pvParameters)
{
    u8 key=0;
    
    while(1)
    {
        key=KEY_Scan(0);
        if(key==KEY_UP_PRESS)
        {
            printf("挂起LED2任务!\n");
            vTaskSuspend(LED2Task_Handler);/* 挂起LED任务 */
            printf("挂起LED2任务成功!\n");
        }
        else if(key==KEY1_PRESS)
        {
            printf("恢复LED2任务!\n");
            vTaskResume(LED2Task_Handler);/* 恢复LED任务!*/
            printf("恢复LED2任务成功!\n");
        }
        vTaskDelay(20);
    }
}

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

ue4c++日记7(动画蓝图)

FVector Speed Pawn->GetVelocity();//获取方向向量FVector xyspeed FVector(Speed.X, Speed.Y,0);//不要z方向MovementSpeed xyspeed.Size();//xy取长//角色是否处于下落状态IsJumping Pawn->GetMovementComponent()->IsFalling();//#include "GameFramewor…

FreeRTOS中的信号量实验

信号量是操作系统中重要的一部分,信号量一般用来进行资源管理和任务同 步,FreeRTOS 中信号量又分为二值信号量、计数型信号量、互斥信号量和递归 互斥信号量。不同的信号量其应用场景不同,但有些应用场景是可以互换着使用。 本章要实现的功能…

【数据结构从0到1之树的初识】

目录 1.树的表达方式 1.1 树的定义 1.2树的相关概念 1.3树的存储结构 1.3.1 双亲表示法 1.3.2 孩子表示法 1.3.3 孩子兄弟表示法 1.4树在实际中的应用 后记: 🕺作者: 迷茫的启明星 😘欢迎关注:👍点…

Lua 迭代器

Lua 迭代器 参考文章: 菜鸟教程。 https://cloud.tencent.com/developer/article/2203215 迭代器(iterator)是一种对象,它能够用来遍历标准模板库容器中的部分或全部元素,每个迭代器对象代表容器中的确定的地址。 在 L…

23种设计模式之七种结构型模式

23种设计模式之七种结构型模式1. 设计模式概述1.1 什么是设计模式1.2 设计模式的好处2. 设计原则分类3. 详解3.1 单一职责原则3.2 开闭原则3.3 里氏代换原则3.4 依赖倒转原则3.5 接口隔离原则3.6 合成复用原则3.7 迪米特法则4. Awakening1. 设计模式概述 我们的软件开发技术也包…

[Python从零到壹] 番外篇之可视化利用D3库实现CSDN博客每日统计效果(类似github)

欢迎大家来到“Python从零到壹”,在这里我将分享约200篇Python系列文章,带大家一起去学习和玩耍,看看Python这个有趣的世界。所有文章都将结合案例、代码和作者的经验讲解,真心想把自己近十年的编程经验分享给大家,希望…

关于对公司做项目的一些想法

项目管理法则里面最重要的是如下的三角形:基于一定的范围、合理的时间和足够的成本下实现项目完成,并保证质量。项目中最重要的是质量,质量不行就意味着项目失败,请参考大跃进时期的大炼钢铁(多快好省大炼钢&#xff0…

是什么影响了 MySQL 索引 B + 树的高度?

提到 MySQL,想必大多后端同学都不会陌生,提到 B 树,想必还是有很大部分都知道 InnoDB 引擎的索引实现,利用了 B 树的数据结构。 那 InnoDB 的一棵 B 树可以存放多少行数据?它又有多高呢? 到底是哪些因…

WebRTC → 信令服务器

相关简介 信令:驱动系统运转。控制各个模块的前后调用关系;业务不同,逻辑不同,信令也会千差万别 要实现一对一通信,驱动系统的核心就是信令。信令控制着系统各个模块之间的前后调用关系,比如当收到用户成功加入房间后…

3D模型在线查看利器【多种格式】

BimAnt 3DViewer网站可以 打开多种 3D 文件格式并在你的浏览器中可视化展示3D模型,支持 obj、3ds、stl、ply、gltf、glb、off、 3dm、fbx 等等。 1、支持的3D模型格式 BimAnt 3DViewer网站支持多种文件格式的导入和导出。 如果文件格式有文本和二进制版本&#x…

Minecraft 1.19.2 Fabric模组开发 09.Mixin

我们今天用mixin在1.19.2 fabric中实现一个望远镜 1.由于fabric已经自动配置好了mixin,所以我们无需配置mixin,先在ItemInit中新建一个我们的望远镜物品: ItemInit.java public static final Item BIRDWATCHER registerItem("birdwat…

Smart-doc的脚本生成在线文档(精简官方文档描述)

Smart-doc优点: 无侵入的接口文档、在线文档生成器。三种生成文档方式。对于程序代码开发中只需要加注释(符合一定的语法,五分钟可掌握)就能生成在线文档。可以支持c、java、php、node等等常见的主流语言。 如何使用: …

47.Isaac教程--ORB

ORB ISAAC教程合集地址: https://blog.csdn.net/kunhe0512/category_12163211.html 文章目录ORBGem 提供的类型关键点描述符如何使用 Gem(界面)构建包Isaac Codelets示例应用程序主机设备嵌入式 Jetson 设备这个 gem 提供了一个特征检测器和描述符提取器…

2011年专业408算法题

文章目录0 结果1 题目2 思路2.1 思路1(暴力解:排序)2.2 思路2(较优解:归并合并数组)2.3 思路3(较优解:数组指针后移)2.4 思路4(最优解:两个数组的…

webpack是如何进行依赖图谱收集的?

我自己学习webpack已有很长时间了,但是经常会遇到这样的问题: 可以熟练配置webpack的一些常用配置,但是对一些不常见的api或者概念总是云里雾里。因此,对着网上资料手写了一个简易版的webpack,现在对其中的依赖图谱收集部分进行梳…

Numpy(7)—字节交换、NumPy 副本和视图、深浅拷贝、矩阵库、NumPy 线性代数、NumPy IO(读写)、NumPy Matplotlib

1.字节交换 import numpy as npA np.array([1, 256, 8755], dtypenp.int16) print(A) print(list(map(hex, A))) print(A.byteswap(inplaceTrue)) print(list(map(hex, A)))2.NumPy 副本和视图 副本是一个数据的完整的拷贝,如果我们对副本进行修改,它不…

【MyBatis 持久层框架】Mapper代理开发详细解读

文章目录1. 前言2. Mapper 代理开发3. 过程剖析4. 总结1. 前言 前面在 MyBatis 快速入门篇中,我们使用了 MyBatis 原生的开发方式操作数据库,解决了 JDBC 操作数据库时的硬编码和操作繁琐的问题。实际上,在 Java 项目中,我们更常…

python3——函数

目录 一、函数定义 二、函数调用 1.打印Hello World 2.判断最大值 3.计算矩形面积 4.help说明文档 三、参数传递 (一)位置参数 (二)关键字参数 (三)默认参数(缺省参数) (四)可变参数(收集参数) 1.位置可变参数(接收所有的位置参数,返回一个元组) 2.关键…

高通开发系列 - MSM8909 lk aboot阶段点灯操作

By: fulinux E-mail: fulinux@sina.com Blog: https://blog.csdn.net/fulinus 喜欢的盆友欢迎点赞和订阅! 你的喜欢就是我写作的动力! 目录 第一种LK提供的接口实现第二种直接操作寄存器这篇文章之前请参考下:高通开发系列 - MSM8909指示灯操作 在LK中点灯有两种方式,一种…

JAVA-定位排查bug

在开发过程中难免会遇到bug,理解bug的含义,定位bug的位置,对于解决bug至关重要!掌握高效的排错技巧,对于程序员来说必不可少。 目录 一、错误异常的分类 二、常见报错信息及原因(持续更新中)…