FreeRTOS中的信号量实验

news2024/11/24 2:05:01

信号量是操作系统中重要的一部分,信号量一般用来进行资源管理和任务同 步,FreeRTOS 中信号量又分为二值信号量、计数型信号量、互斥信号量和递归 互斥信号量。不同的信号量其应用场景不同,但有些应用场景是可以互换着使用。 本章要实现的功能是:创建 3 个任务,分别为 LED、二值信号量释放、二值信号 量获取任务,LED 任务控制 DS0 指示灯闪烁,KEY_UP 和 KEY1 键控制二值信号量 释放,获取二值信号量成功,DS1 指示灯状态翻转,同时通过串口输出提示信息。 本章分为如下几部分内容: 7.1 信号量简介 7.2 常用信号量 API 函数 7.3 硬件设计 7.4 软件设计 7.5 实验现 象

7.1 信号量简介

信号量(Semaphore)是一种实现任务间通信的机制,可以实现任务之间同 步或临界资源的互斥访问,常用于协助一组相互竞争的任务来访问临界资源。在 多任务系统中,各任务之间需要同步或互斥实现临界资源的保护,信号量功能可 以为用户提供这方面的支持。 抽象的来讲,信号量是一个非负整数,所有获取它的任务都会将该整数减一 (获取它当然是为了使用资源),当该整数值为零时,所有试图获取它的任务都 将处于阻塞状态。通常一个信号量的计数值用于对应有效的资源数,表示剩下的 可被占用的互斥资源数。其值的含义分两种情况: ●0:表示没有积累下来的释放信号量操作,且有可能有在此信号量上阻塞 的任务。 ●正值,表示有一个或多个释放信号量操作。

7.1.1 二值信号量

二值信号量既可以用于临界资源访问也可以用于同步功能。 二值信号量和互斥信号量(以下使用互斥量表示互斥信号量)非常相似,但 是有一些细微差别:互斥量有优先级继承机制,二值信号量则没有这个机制。这 使得二值信号量更偏向应用于同步功能(任务与任务间的同步或任务和中断间同 步),而互斥量更偏向应用于临界资源的访问。 用作同步时,信号量在创建后应被置为空,任务 1 获取信号量而进入阻塞, 任务 2 在某种条件发生后,释放信号量,于是任务 1 获得信号量得以进入就绪 态,如果任务 1 的优先级是最高的,那么就会立即切换任务,从而达到了两个 任务间的同步。同样的,在中断服务函数中释放信号量,任务 1 也会得到信号 量,从而达到任务与中断间的同步。 还记得我们经常说的中断要快进快出吗,在裸机开发中我们经常是在中断中 做一个标记,然后在退出的时候进行轮询处理,这个就是类似我们使用信号量进 行同步的,当标记发生了,我们再做其他事情。在 FreeRTOS 中我们用信号量用 于同步,任务与任务的同步,中断与任务的同步,可以大大提高效率。 可以将二值信号量看作只有一个消息的队列,因此这个队列只能为空或满 (因此称为二值),我们在运用的时候只需要知道队列中是否有消息即可,而无 需关注消息是什么。

7.1.2 计数信号量

二进制信号量可以被认为是长度为 1 的队列,而计数信号量则可以被认为 长度大于 1 的队列,信号量使用者依然不必关心存储在队列中的消息,只需关 心队列是否有消息即可。 顾名思义,计数信号量肯定是用于计数的,在实际的使用中,我们常将计数 信号量用于事件计数与资源管理。每当某个事件发生时,任务或者中断将释放一 个信号量(信号量计数值加 1),当处理事件时(一般在任务中处理),处理任 务会取走该信号量(信号量计数值减 1),信号量的计数值则表示还有多少个事 件没被处理。此外,系统还有很多资源,我们也可以使用计数信号量进行资源管 理,信号量的计数值表示系统中可用的资源数目,任务必须先获取到信号量才能 获取资源访问权,当信号量的计数值为零时表示系统没有可用的资源,但是要注 意,在使用完资源的时候必须归还信号量,否则当计数值为 0 的时候任务就无 法访问该资源了。 计数型信号量允许多个任务对其进行操作,但限制了任务的数量。比如有一 个停车场,里面只有 100 个车位,那么能停的车只有 100 辆,也相当于我们的 信号量有 100 个,假如一开始停车场的车位还有 100 个,那么每进去一辆车就 要消耗一个停车位,车位的数量就要减一,对应的,我们的信号量在使用之后也 需要减一,当停车场停满了 100 辆车的时候,此时的停车位为 0,再来的车就 不能停进去了,否则将造成事故,也相当于我们的信号量为 0,后面的任务对这 个停车场资源的访问也无法进行,当有车从停车场离开的时候,车位又空余出来 了,那么,后面的车就能停进去了,我们信号量的操作也是一样的,当我们释放 了这个资源,后面的任务才能对这个资源进行访问。

7.1.3 互斥信号量

互斥信号量其实是特殊的二值信号量,由于其特有的优先级继承机制从而使 它更适用于简单互锁,也就是保护临界资源(什么是优先级继承在后续详细讲 解)。 用作互斥时,信号量创建后可用信号量个数应该是满的,任务在需要使用临 界资源时,(临界资源是指任何时刻只能被一个任务访问的资源),先获取互斥 信号量,使其变空,这样其他任务需要使用临界资源时就会因为无法获取信号量 而进入阻塞,从而保证了临界资源的安全。 在操作系统中,我们使用信号量的很多时候是为了给临界资源建立一个标 志,信号量表示了该临界资源被占用情况。这样,当一个任务在访问临界资源的 时候,就会先对这个资源信息进行查询,从而在了解资源被占用的情况之后,再 做处理,从而使得临界资源得到有效的保护。

7.1.4 递归信号量

递归信号量,见文知义,递归嘛,就是可以重复获取调用的,本来按照信号 量的特性,每获取一次可用信号量个数就会减少一个,但是递归则不然,对于已 经获取递归互斥量的任务可以重复获取该递归互斥量,该任务拥有递归信号量的 所有权。任务成功获取几次递归互斥量,就要返还几次,在此之前递归互斥量都 处于无效状态,其他任务无法获取,只有持有递归信号量的任务才能获取与释放。

7.1.5 二值信号量运作机制

创建信号量时,系统会为创建的信号量对象分配内存,并把可用信号量初始 化为用户自定义的个数,二值信号量的最大可用信号量个数为 1。 二值信号量获取,任何任务都可以从创建的二值信号量资源中获取一个二值 信号量,获取成功则返回正确,否则任务会根据用户指定的阻塞超时时间来等待 其它任务/中断释放信号量。在等待这段时间,系统将任务变成阻塞态,任务将 被挂到该信号量的阻塞等待列表中。 在二值信号量无效的时候,假如此时有任务获取该信号量的话,那么任务将 进入阻塞状态,具体如下。

假如某个时间中断/任务释放了信号量,那么,由于获取无效信号量而进入 阻塞态的任务将获得信号量并且恢复为就绪态,其过程具体如下图

7.1.7 计数信号量运作机制

计数信号量可以用于资源管理,允许多个任务获取信号量访问共享资源,但 会限制任务的最大数目。访问的任务数达到可支持的最大数目时,会阻塞其他试 图获取该信号量的任务,直到有任务释放了信号量。这就是计数型信号量的运作 机制,虽然计数信号量允许多个任务访问同一个资源,但是也有限定,比如某个 资源限定只能有 3 个任务访问,那么第 4 个任务访问的时候,会因为获取不到 信号量而进入阻塞,等到有任务(比如任务 1)释放掉该资源的时候,第 4 个 任务才能获取到信号量从而进行资源的访问,其运作的机制具体如下

7.2 常用信号量 API 函数

7.2.1 创建信号量函数

7.2.1.1 创建二值信号量 xSemaphoreCreateBinary() xSemaphoreCreateBinary()用于创建一个二值信号量,并返回一个句柄。其 实二值信号量和互斥量都共同使用一个类型 SemaphoreHandle_t 的句柄,该句 柄的原型是一个 void 型的指针。使用该函数创建的二值信号量是空的,在使用 函数 xSemaphoreTake()获取之前必须先调用函数 xSemaphoreGive()释放后才 可以获取。如果是使用老式的函数 vSemaphoreCreateBinary()创建的二值信号 量,则为 1,在使用之前不用先释放。要想使用该函数必须在 FreeRTOSConfig.h 中把宏 configSUPPORT_DYNAMIC_ALLOCATION 定义为 1,即开启动态内存分配。 其实该宏在 FreeRTOS.h 中默认定义为 1,即所有 FreeRTOS 的对象在创建的时 候都默认使用动态内存分配方案,xSemaphoreCreateBinary()函数原型具体代码 如下。

#if( configSUPPORT_DYNAMIC_ALLOCATION == 1 )
#define xSemaphoreCreateBinary()
xQueueGenericCreate( ( UBaseType_t ) 1, (1)
semSEMAPHORE_QUEUE_ITEM_LENGTH, (2)
queueQUEUE_TYPE_BINARY_SEMAPHORE ) (3)
#endif

从这个函数原型我们就可以知道二值信号量的创建实际使用的函数就是 xQueueGenericCreate()函数,是不是很熟悉,这就是消息队列的创建使用的函 数,但是参数不一样,根据 xQueueGenericCreate()函数原型来讲解一下参数的 作用。 代码(1):uxQueueLength 为 1 表示创建的队列长度为 1,其实用作信号量 就表示信号量的最大可用个数,从前面的知识点我们就知道,二值信号量的非空 即满,长度为 1 不正是这样子的表示吗。 代码(2):semSEMAPHORE_QUEUE_ITEM_LENGTH 其实是一个宏定义,其值为 0, 见文知义,它表示创建的消息空间(队列项)大小为 0,因为这个所谓的“消息 队列”其实并不是用于存储消息的,而是被用作二值信号量,因为我们根本无需 关注消息内容是什么,只要知道有没有信号量就行了。 代码(3):ucQueueType 表示的是创建消息队列的类型,在 queue.h 中有定 义,具体代码如下,现在创建的是二值信号量,其类型就是 queueQUEUE_TYPE_BINARY_SEMAPHORE

可能很多人会问了,创建一个没有消息存储空间的队列,信号量用什么表 示?其实二值信号量的释放和获取都是通过操作队列结控制块构体成员 uxMessageWaiting 来实现的,它表示信号量中当前可用的信号量个数。在信号 量创建之后,变量 uxMessageWaiting 的值为 0,这说明当前信号量处于无效状 态,此时的信号量是无法被获取的,在获取信号之前,应先释放一个信号量。后 面讲到信号量释放和获取时还会详细介绍

7.2.1.2 创建计数信号量 xSemaphoreCreateCounting()

xSemaphoreCreateCounting()用于创建一个计数信号量。要想使用该函数必 须在 FreeRTOSConfig.h 中把宏 configSUPPORT_DYNAMIC_ALLOCATION 定义为 1,即开启动态内存分配。其实该宏在 FreeRTOS.h 中默认定义为 1,即所有 FreeRTOS 的对象在创建的时候都默认使用动态内存分配方案。 其实计数信号量跟二值信号量的创建过程都差不多,其实也是间接调用 xQueueGenericCreate()函数进行创建,xSemaphoreCreateCounting()函数说明 具体如下

7.2.2 信号量删除函数 vSemaphoreDelete()

vSemaphoreDelete()用于删除一个信号量,包括二值信号量,计数信号量, 互斥量和递归互斥量。如果有任务阻塞在该信号量上,那么不要删除该信号量。 该函数的具体如下。

7.2.3 信号量释放函 数

7.2.3.1 xSemaphoreGive()

xSemaphoreGive()是一个用于释放信号量的宏,真正的实现过程是调用消息 队列通用发送函数,xSemaphoreGive()函数原型具体代码如下。释放的信号量对 象必须是已经被创建的,可以用于二值信号量、计数信号量、互斥量的释放,但 不能释放由函数 xSemaphoreCreateRecursiveMutex()创建的递归互斥量。此外 该函数不能在中断中使用

#define xSemaphoreGive( xSemaphore )
xQueueGenericSend( ( QueueHandle_t ) ( xSemaphore ),
NULL,
semGIVE_BLOCK_TIME,
queueSEND_TO_BACK )

从该宏定义可以看出释放信号量实际上是一次入队操作,并且是不允许入队 阻塞,因为阻塞时间为 semGIVE_BLOCK_TIME,该宏的值为 0。 通过消息队列入队过程分析,我们可以将释放一个信号量的过程简化:如果 信号量未满,控制块结构体成员 uxMessageWaiting 就会加 1,然后判断是否有 阻塞的任务,如果有的话就会恢复阻塞的任务,然后返回成功信息(pdPASS); 如果信号量已满,则返回错误代码(err_QUEUE_FULL)

7.2.3.2 xSemaphoreGiveFromISR()

用于释放一个信号量,带中断保护。被释放的信号量可以是二进制信号量和 计数信号量。和普通版本的释放信号量 API 函数有些许不同,它不能释放互斥 量,这是因为互斥量不可以在中断中使用,互斥量的优先级继承机制只能在任务 中起作用,而在中断中毫无意义。带中断保护的信号量释放其实也是一个宏,真 正调用的函数是 xQueueGiveFromISR (),宏定义如下

#define xSemaphoreGiveFromISR( xSemaphore, pxHigherPriorityTaskWoken )
xQueueGiveFromISR( ( QueueHandle_t ) ( xSemaphore ),
( pxHigherPriorityTaskWoken ) 

7.2.4 信号量获取函 数

7.2.4.1 xSemaphoreTake()

xSemaphoreTake()函数用于获取信号量,不带中断保护。获取的信号量对象 可以是二值信号量、计数信号量和互斥量,但是递归互斥量并不能使用这个 API 函数获取。其实获取信号量是一个宏,真正调用的函数是 xQueueGenericReceive ()。该宏不能在中断使用,而是必须由具体中断保护功能的 xQueueReceiveFromISR()版本代替。该函数的具体说明如下。

7.2.4.2 xSemaphoreTakeFromISR()

xSemaphoreTakeFromISR()是函数 xSemaphoreTake()的中断版本,用于获取 信号量,是一个不带阻塞机制获取信号量的函数,获取对象必须由是已经创建的 信号量,信号量类型可以是二值信号量和计数信号量,它与 xSemaphoreTake() 函数不同,它不能用于获取互斥量,因为互斥量不可以在中断中使用,并且互斥 量特有的优先级继承机制只能在任务中起作用,而在中断中毫无意义。该函数的 具体说明如下。

整体代码

二值信号量整体代码

#include "system.h"
#include "SysTick.h"
#include "led.h"
#include "usart.h"
#include "FreeRTOS.h"
#include "task.h"
#include "key.h"
#include "semphr.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 RECEIVE_TASK_PRIO        3
//任务堆栈大小    
#define RECEIVE_STK_SIZE         50  
//任务句柄
TaskHandle_t ReceiveTask_Handler;
//任务函数
void receive_task(void *pvParameters);

//任务优先级
#define SEND_TASK_PRIO        4
//任务堆栈大小    
#define SEND_STK_SIZE         50  
//任务句柄
TaskHandle_t SendTask_Handler;
//任务函数
void send_task(void *pvParameters);


SemaphoreHandle_t BinarySem_Handle =NULL;


/*******************************************************************************
* 函 数 名         : main
* 函数功能           : 主函数
* 输    入         : 无
* 输    出         : 无
*******************************************************************************/
int main()
{
    SysTick_Init(72);
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);//设置系统中断优先级分组4
    LED_Init();
    KEY_Init();
    USART1_Init(115200);
    printf("FreeRTOS二值信号量实验\r\n");
    printf("按下KEY_UP或者KEY1进行任务与任务间的同步\r\n");
    printf("Receive任务接收到消息在串口回显\r\n");
    //创建开始任务
    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();           //进入临界区
     
    /* 创建 二值信号量 */
    BinarySem_Handle = xSemaphoreCreateBinary();     
    
    //创建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 )receive_task,     
                (const char*    )"receive_task",   
                (uint16_t       )RECEIVE_STK_SIZE, 
                (void*          )NULL,
                (UBaseType_t    )RECEIVE_TASK_PRIO,
                (TaskHandle_t*  )&ReceiveTask_Handler);

    //创建发送任务
    xTaskCreate((TaskFunction_t )send_task,     
                (const char*    )"send_task",   
                (uint16_t       )SEND_STK_SIZE, 
                (void*          )NULL,
                (UBaseType_t    )SEND_TASK_PRIO,
                (TaskHandle_t*  )&SendTask_Handler);
                
    vTaskDelete(StartTask_Handler); //删除开始任务
    taskEXIT_CRITICAL();            //退出临界区
} 

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

//接收任务函数
void receive_task(void *pvParameters)
{
    BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为pdPASS */
    
    while(1)
    {
        //获取二值信号量 xSemaphore,没获取到则一直等待
        xReturn = xSemaphoreTake(BinarySem_Handle,/* 二值信号量句柄 */
                                portMAX_DELAY); /* 等待时间 */
        if(pdTRUE == xReturn)
            printf("BinarySem_Handle二值信号量获取成功!\n\n");
        LED2=!LED2;
    }
}

//发送任务函数
void send_task(void *pvParameters)
{
    BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为pdPASS */
    u8 key=0;
    
    while(1)
    {
        key=KEY_Scan(0);
        if(key==KEY_UP_PRESS)
        {
            xReturn = xSemaphoreGive( BinarySem_Handle );//给出二值信号量
            if( xReturn == pdTRUE )
                printf("BinarySem_Handle二值信号量释放成功!\r\n");
            else
                printf("BinarySem_Handle二值信号量释放失败!\r\n");
        }
        else if(key==KEY1_PRESS)
        {
            xReturn = xSemaphoreGive( BinarySem_Handle );//给出二值信号量
            if( xReturn == pdTRUE )
                printf("BinarySem_Handle二值信号量释放成功!\r\n");
            else
                printf("BinarySem_Handle二值信号量释放失败!\r\n");
        }
        vTaskDelay(20);
    }
}

计数信号量整体代码

#include "system.h"
#include "SysTick.h"
#include "led.h"
#include "usart.h"
#include "FreeRTOS.h"
#include "task.h"
#include "key.h"
#include "semphr.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 RECEIVE_TASK_PRIO        3
//任务堆栈大小    
#define RECEIVE_STK_SIZE         50  
//任务句柄
TaskHandle_t ReceiveTask_Handler;
//任务函数
void receive_task(void *pvParameters);

//任务优先级
#define SEND_TASK_PRIO        4
//任务堆栈大小    
#define SEND_STK_SIZE         50  
//任务句柄
TaskHandle_t SendTask_Handler;
//任务函数
void send_task(void *pvParameters);


SemaphoreHandle_t CountSem_Handle =NULL;


/*******************************************************************************
* 函 数 名         : main
* 函数功能           : 主函数
* 输    入         : 无
* 输    出         : 无
*******************************************************************************/
int main()
{
    SysTick_Init(72);
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);//设置系统中断优先级分组4
    LED_Init();
    KEY_Init();
    USART1_Init(115200);
    printf("FreeRTOS计数信号量实验\r\n");
    printf("车位默认值为5个,按下KEY_UP申请车位,按下KEY1释放车位\r\n");
    //创建开始任务
    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();           //进入临界区
     
    /* 创建 计数信号量 */
    CountSem_Handle = xSemaphoreCreateCounting(5,5); 
    
    //创建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 )receive_task,     
                (const char*    )"receive_task",   
                (uint16_t       )RECEIVE_STK_SIZE, 
                (void*          )NULL,
                (UBaseType_t    )RECEIVE_TASK_PRIO,
                (TaskHandle_t*  )&ReceiveTask_Handler);

    //创建发送任务
    xTaskCreate((TaskFunction_t )send_task,     
                (const char*    )"send_task",   
                (uint16_t       )SEND_STK_SIZE, 
                (void*          )NULL,
                (UBaseType_t    )SEND_TASK_PRIO,
                (TaskHandle_t*  )&SendTask_Handler);
                
    vTaskDelete(StartTask_Handler); //删除开始任务
    taskEXIT_CRITICAL();            //退出临界区
} 

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

//接收任务函数
void receive_task(void *pvParameters)
{
    BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为pdPASS */
    u8 key=0;
    
    while(1)
    {
        key=KEY_Scan(0);
        if(key==KEY1_PRESS)
        {
            xReturn = xSemaphoreTake( CountSem_Handle,0 );//获取计数信号量,0是阻塞时间
            if( xReturn == pdTRUE )
                printf( "KEY1被按下,释放1个停车位。\r\n" );
            else
                printf( "KEY1被按下,但已无车位可以释放!\r\n" );
        }
        vTaskDelay(20);
    }
}

//发送任务函数
void send_task(void *pvParameters)
{
    BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为pdPASS */
    u8 key=0;
    
    while(1)
    {
        key=KEY_Scan(0);
        if(key==KEY_UP_PRESS)
        {
            xReturn = xSemaphoreGive( CountSem_Handle );//给出计数信号量
            if( xReturn == pdTRUE )
                printf("KEY_UP被按下,成功申请到停车位。\r\n");
            else
                printf("KEY_UP被按下,不好意思,现在停车场已满!\r\n");
        }
        vTaskDelay(20);
    }
}

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

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

相关文章

【数据结构从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至关重要!掌握高效的排错技巧,对于程序员来说必不可少。 目录 一、错误异常的分类 二、常见报错信息及原因(持续更新中)…

域内权限维持:AdminSDHolder

01、简介 AdminSDHolder是一个特殊的AD容器,通常作为某些特权组成员的对象的安全模板。Active Directory将采用AdminSDHolder对象的ACL并定期将其应用于所有受保护的AD账户和组,以防止意外和无意的修改并确保对这些对象的访问是安全的。如果攻击者能完全…

Flex布局和主要属性用法详解

目录 前言 一个小例子 基本概念: 设置在主轴上的排列方式 设置在侧轴上的排列方式 更换主轴和侧轴方向 换行 align-content属性 元素(子容器)的相关属性 flex-basis flex-grow flex-shrink属性 flex属性 前言 flex布局是继标准…