FreeRTOS_信号量之互斥信号量

news2025/4/13 15:24:42

目录

1. 互斥信号量

1.1 互斥信号量简介

1.2 创建互斥信号量

1.2.1 函数 xSemaphoreCreateMutex()

1.2.2 函数 xSemaphoreCreateMutexStatic()

1.2.3 互斥信号量创建过程分析

1.2.4 释放互斥信号量

1.2.5 获取互斥信号量

2. 互斥信号量操作实验

2.1 实验程序

2.1.1 main.c

2.1.2 实验现象


1. 互斥信号量

1.1 互斥信号量简介

        互斥信号量其实就是一个拥有优先级继承的二值信号量,在同步的应用中(任务与任务或中断与任务之间的同步)二值信号量最适合。互斥信号量适合用于那些需要互斥访问的应用中。在互斥访问中互斥信号量相当于一把钥匙,当任务想要使用资源的时候就必须先获得这个钥匙,当使用完资源以后就必须归还这个钥匙,这样其他的任务就可以拿着这个钥匙去使用资源。

        互斥信号量使用和二值信号量相同的 API 操作函数,所以互斥信号量也可以设置阻塞时间,不同于二值信号量的是互斥信号量具有优先级继承的特性。当一个互斥信号量正在被一个低优先级的任务使用,而此时有个高优先级的任务也尝试获取这个互斥信号量的话就会被阻塞。这个高优先级的任务会将低优先级任务的优先级提升到与自己相同的优先级,这个过程就是优先级继承。优先级继承尽可能的降低了高优先级任务处于阻塞态的时间,并且将已经出现的 “优先级翻转” 的影响降到最低。

优先级继承尽可能的降低了高优先级任务处于阻塞态的时间,并且将已经出现的 “优先级翻转” 的影响降到最低。
       
意思就是说:低优先级的任务获得互斥信号量,此时高优先级的任务无法访问获得互斥信号量,其他中等优先级的任务也无法获得互斥信号量,这样一来,低优先级任务就不能被中等优先级任务所打断,高优先级任务只需要等待低优先级任务释放互斥信号量即可,不用担心被其他的中等任务所打断;这也就是为什么降低高优先级任务处于阻塞态的时间,降低了优先级翻转的可能性!

        优先级继承并不能完全的消除优先级翻转,它只是尽可能的降低优先级翻转带来的影响。硬实时应用应该在设计之初就要避免优先级翻转的发生。

互斥信号量不能用于中断服务函数中:

        互斥信号量具有优先级继承的机制,只能用在任务中,不能用于中断服务函数。

        中断服务函数中不能因为要等待互斥信号量而设置阻塞时间进入阻塞态。

1.2 创建互斥信号量

        FreeRTOS提供两个互斥信号量创建函数。

函数:

        xSemaphoreCreateMutex()                使用动态方法创建互斥信号量

        xSemaphoreCreateMutexStatic()        使用静态方法创建互斥信号量

1.2.1 函数 xSemaphoreCreateMutex()

        此函数用于创建一个互斥信号量,所需要的内存通过动态内存管理方法分配。此函数本质是一个宏,真正完成信号量创建的是函数 xQueueCreateMutex(),此函数原型如下:

SemaphoreHandle_t xSemaphoreCreateMutex(void)

参数:

        无。

返回值:

        NULL:互斥信号量创建失败。

        其他值:创建成功的互斥信号量的句柄。

1.2.2 函数 xSemaphoreCreateMutexStatic()

        此函数也是创建互斥信号量的,只不过使用此函数创建互斥信号量的话信号量所需要的 RAM 需要由用户来分配,此函数是一个宏,具体创建过程是通过函数 xQueueCreateMutexStatic() 来完成的,函数原型如下:

SemaphoreHandle_t xSemaphoreCreateMutexStatic(StaticSemaphore_t *pxMutexBuffer)

参数:

        pxMutexBuffer:此参数指向一个 StaticSemaphore_t 类型的变量,用来保存信号量结构体。

返回值:

        NULL:互斥信号量创建失败。

        其他值:创建成功的互斥信号量的句柄。

1.2.3 互斥信号量创建过程分析

        这里只分析动态创建互斥信号量函数 xSemaphoreCreateMutex(),此函数是个宏,定义如下:

#define  xSemaphoreCreateMutex()  xQueueCreateMutex(queueQUEUE_TYPE_MUTEX)

        可以看出,真正干事的是函数 xQueueCreateMutex(),此函数在文件 queue.c 中有如下定义,

QueueHandle_t xQueueCreateMutex( const uint8_t ucQueueType ) 
{ 
    Queue_t *pxNewQueue; 
    const UBaseType_t uxMutexLength = ( UBaseType_t ) 1, uxMutexSize = ( UBaseType_t ) 0; 
 
    pxNewQueue = ( Queue_t * ) xQueueGenericCreate( uxMutexLength, uxMutexSize,     (1) 
                                                    ucQueueType ); 
    prvInitialiseMutex( pxNewQueue );                                               (2) 
 
    return pxNewQueue; 
}

(1)、调用函数 xQueueGenericCreate() 创建一个队列,队列长度为 1 ,队列项长度为 0 ,队列类型为参数 ucQueueType。由于本函数创建的是互斥信号量,所以参数 ucQueueType 为 queueQUEUE_TYPE_MUTEX。

(2)、调用函数 prvInitialiseMutex() 初始化互斥信号量。

函数 prvInitialiseMutex() 初始化互斥信号量代码如下:

static void prvInitialiseMutex( Queue_t *pxNewQueue ) 
{ 
    if( pxNewQueue != NULL ) 
    { 
        //虽然创建队列的时候会初始化队列结构体的成员变量,但是此时创建的是互斥 
        //信号量,因此有些成员变量需要重新赋值,尤其是那些用于优先级继承的。 
        pxNewQueue->pxMutexHolder = NULL;                                 (1) 
        pxNewQueue->uxQueueType = queueQUEUE_IS_MUTEX;                     (2) 
 
        //如果是递归互斥信号量的话。 
        pxNewQueue->u.uxRecursiveCallCount = 0;                             (3) 
 
        traceCREATE_MUTEX( pxNewQueue ); 
 
        //释放互斥信号量 
        ( void ) xQueueGenericSend( pxNewQueue, NULL, ( TickType_t ) 0U,
                                    queueSEND_TO_BACK ); 
    } 
    else 
    { 
        traceCREATE_MUTEX_FAILED(); 
    } 
} 

(1)和(2)、队列结构体中 Queue_t 中没有 pxMutexHolder 和 uxQueueType 这两个成员变量,这两个成员变量其实是一个宏,专门为互斥信号量准备的,在文件 queue.c 中有如下定义:

#define pxMutexHolder    pcTail
#define uxQueueType      pcHead
#define queueQUEUE_IS_MUTEX    NULL

        当 Queue_t 用于表示队列的时候 pcHead 和 pcTail 指向队列的存储区域,当 Queue_t 用于表示互斥信号量的时候就不需要 pcHead 和 pcTail 了。当用于互斥信号量的时候将 pcHead 指向 NULL 来表示 pcTail 保存着互斥队列的所有者,pxMutexHolder 指向拥有互斥信号量的那个任务的任务控制块。重命名 pcTail 和 pcHead 是为了增强代码的可读性。

(3)、如果创建的信号量是递归互斥信号量的话,还需要初始化队列结构体中的成员变量 u.uxRecursiveCallCount。

        互斥信号量创建成功以后会调用函数 xQueueGenericSend() 释放一次信号量,说明互斥信号量默认就是有效的!

1.2.4 释放互斥信号量

        释放互斥信号量的时候和二值信号量、计数型信号量一样,都是用的函数 xSemaphoreGive()(实际上完成信号量释放的是函数 xQueueGenericSend())。由于互斥信号量涉及到优先级继承的问题,所以具体的处理过程会有区别。使用函数 xSemaphoreGive() 释放信号量最重要的一步就是将 uxMessageWaiting 加一,而这一步就是通过函数 prvCopyDataToQueue() 来完成的,释放信号量的函数 xQueueGenericSend() 会调用 prvCopyDataToQueue()。互斥信号量的优先级继承也是在函数 prvCopyDataToQueue() 中完成的,此函数有如下一段代码:

static BaseType_t prvCopyDataToQueue( Queue_t * const pxQueue, 
                                      const void * pvItemToQueue, 
                                      const BaseType_t xPosition ) 
{ 
    BaseType_t xReturn = pdFALSE; 
    UBaseType_t uxMessagesWaiting; 
 
    uxMessagesWaiting = pxQueue->uxMessagesWaiting; 
 
    if( pxQueue->uxItemSize == ( UBaseType_t ) 0 ) 
    { 
        #if ( configUSE_MUTEXES == 1 ) //互斥信号量 
        { 
             if( pxQueue->uxQueueType == queueQUEUE_IS_MUTEX )             (1) 
             { 
                 xReturn = xTaskPriorityDisinherit( ( void * ) pxQueue->pxMutexHolder );                        (2) 
                 pxQueue->pxMutexHolder = NULL;                            (3) 
             } 
             else 
             { 
                 mtCOVERAGE_TEST_MARKER(); 
             } 
         } 
         #endif /* configUSE_MUTEXES */ 
     } 
 
    /*********************************************************************/ 
    /*************************省略掉其他处理代码**************************/ 
    /*********************************************************************/ 
 
    pxQueue->uxMessagesWaiting = uxMessagesWaiting + 1; 
 
    return xReturn; 
} 

(1)、当前操作的是互斥信号量。

(2)、调用函数 xTaskPriorityDisinherit() 处理互斥信号量的优先级继承问题。

(3)、互斥信号量释放以后,互斥信号量就不属于任何任务了,所以 pxMutexHolder 要指向 NULL。

函数 xTaskPriorityDisinherit() 代码如下:

BaseType_t xTaskPriorityDisinherit( TaskHandle_t const pxMutexHolder ) 
{ 
    TCB_t * const pxTCB = ( TCB_t * ) pxMutexHolder; 
    BaseType_t xReturn = pdFALSE; 
 
    if( pxMutexHolder != NULL )                                         (1) 
    { 
        //当一个任务获取到互斥信号量以后就会涉及到优先级继承的问题,正在释放互斥 
        //信号量的任务肯定是当前正在运行的任务 pxCurrentTCB。 
        configASSERT( pxTCB == pxCurrentTCB ); 
        configASSERT( pxTCB->uxMutexesHeld ); 
 
        ( pxTCB->uxMutexesHeld )--;                                     (2) 
 
        //是否存在优先级继承?如果存在的话任务当前优先级肯定和任务基优先级不同。
        if( pxTCB->uxPriority != pxTCB->uxBasePriority )                 (3) 
        { 
            //当前任务只获取到了一个互斥信号量 
            if( pxTCB->uxMutexesHeld == ( UBaseType_t ) 0 )                 (4) 
            { 
                if( uxListRemove( &( pxTCB->xStateListItem ) ) == ( UBaseType_t ) 0 )         (5) 
                { 
                    taskRESET_READY_PRIORITY( pxTCB->uxPriority );         (6) 
                } 
                else 
                { 
                    mtCOVERAGE_TEST_MARKER(); 
                } 
 
                //使用新的优先级将任务重新添加到就绪列表中 
                traceTASK_PRIORITY_DISINHERIT( pxTCB, pxTCB->uxBasePriority ); 
                                        pxTCB->uxPriority = pxTCB->uxBasePriority; (7) 
 
                /* Reset the event list item value. It cannot be in use for 
                any other purpose if this task is running, and it must be 
                running to give back the mutex. */ 
                listSET_LIST_ITEM_VALUE( &( pxTCB->xEventListItem ), \             (8) 
                ( TickType_t ) configMAX_PRIORITIES - \ 
                ( TickType_t ) pxTCB->uxPriority ); 
                prvAddTaskToReadyList( pxTCB );                                 (9) 
                xReturn = pdTRUE;                                                 (10) 
            } 
            else 
            { 
                mtCOVERAGE_TEST_MARKER(); 
            } 
        } 
        else 
        { 
            mtCOVERAGE_TEST_MARKER(); 
        } 
    } 
    else 
    { 
        mtCOVERAGE_TEST_MARKER(); 
    } 
 
    return xReturn; 
}

(1)、函数的参数 pxMutexHolder 表示拥有此互斥信号量任务控制块,所以要先判断此互斥信号量是否已经被其他任务获取。

(2)、有的任务可能会获取多个互斥信号量,所以就需要标记任务当前获取到的互斥信号量个数,任务控制块结构体成员变量 uxMutexesHeld 用来保存当前任务获取到的互斥信号量个数。任务每释放一次互斥信号量,变量 uxMutexesHeld 肯定就要减一。

(3)、判断是否存在优先级继承,如果存在的话任务的当前优先级肯定不等于任务的基优先级(任务的基优先级是指任务在创建时分配的固定优先级。它是一个用于调度任务的数值,数值越高,优先级越高)

(4)、判断当前释放的是不是任务所获取到的最后一个互斥信号量,因为如果任务还获取了其他互斥信号量的话就不能处理优先级继承。优先级继承的处理必须是在释放最后一个互斥信号量的时候。

(5)、优先级继承的处理说白了就是把任务的当前优先级降低到任务的基优先级,所以要把当前任务先从任务就绪表中移除。当任务优先级恢复为原来的优先级以后再重新加入到就绪表中。

FreeRTOS 是一种实时操作系统 (RTOS),用于嵌入式系统的开发。优先级继承是一种调度策略,用于解决优先级翻转问题。

在多任务系统中,任务可以具有不同的优先级,优先级较高的任务可以抢占优先级较低的任务。然而,当存在任务依赖关系时,可能会出现优先级反转问题。

优先级翻转问题是当一个具有较低优先级的任务占用一个共享资源时,一个具有较高优先级的任务因为等待该资源而被阻塞。此时,更高优先级任务无法运行,从而导致系统的响应性能下降。

通过优先级继承,当一个高优先级任务需要使用一个低优先级任务占用的资源时,低优先级任务的优先级会临时提升至与高优先级任务相同的优先级,以确保高优先级任务能够尽快获得所需资源,当高优先级任务释放该资源后,低优先级任务的优先级恢复原状。

(6)、如果任务继承来的这个优先级对应的就绪表中没有其他任务的话就将取消这个优先级的就绪态。

(7)、重新设置任务的优先级为任务的基优先级 uxBasePriority。

(8)、复位任务的事件列表项。

(9)、将优先级恢复后的任务重新添加到任务就绪表中。

(10)、返回 pdTRUE,表示需要进行任务调度。

1.2.5 获取互斥信号量

        获取互斥信号量的函数同获取二值信号量和计数型信号量的函数相同,都是 xSemaphoreTake()(实际执行信号量获取的函数是 xQueueGenericReceive()),获取互斥信号量的过程也需要处理优先级继承的问题,函数 xQueueGenericReceive()在文件 queue.c 中有定义)

2. 互斥信号量操作实验

本实验设计了四个任务:start_task、high_task、middle_task、low_task,这四个任务的任务功能如下:

        start_task:用来创建其他的三个任务。

        high_task:高优先级任务,会获取互斥信号量,获取成功以后会进行相应的处理,处理完成以后就会释放互斥信号量。

        middle_task:中等优先级任务,一个简单的应用任务。

        low_task:低优先级任务,和高优先级任务一样,会获取互斥信号量,获取成功以后会进行相应的处理,不过不同之处在于低优先级的任务占用互斥信号量的时间要久一点(软件模拟占用)。

        实验中创建了一个互斥信号量 MutexSemaphore,高优先级和低优先级这两个任务会使用这个互斥信号量。

2.1 实验程序

2.1.1 main.c

#include "stm32f4xx.h"  
#include "FreeRTOS.h" //这里注意必须先引用FreeRTOS的头文件,然后再引用task.h
#include "task.h"     //存在一个先后的关系
#include "LED.h"
#include "LCD.h"
#include "Key.h"
#include "usart.h"
#include "delay.h"
#include "string.h"
#include "beep.h"
#include "malloc.h"
#include "timer.h"
#include "queue.h"
#include "semphr.h"


//任务优先级
#define START_TASK_PRIO     1       //用于创建其他三个任务
//任务堆栈大小
#define START_STK_SIZE      256
//任务句柄
TaskHandle_t StartTask_Handler;
//任务函数
void start_task(void *pvParameters);

//任务优先级
#define LOW_TASK_PRIO     2       //低优先级任务,会获取互斥信号量,获取成功以后进行相应的处理,占用互斥信号量的时间要久一点
//任务堆栈大小 
#define LOW_STK_SIZE      256
//任务句柄
TaskHandle_t LowTask_Handler;
//任务函数
void low_task(void *pvParameters);

//任务优先级
#define MIDDLE_TASK_PRIO     3       //中等优先级任务,一个简单的应用任务
//任务堆栈大小
#define MIDDLE_STK_SIZE      256
//任务句柄
TaskHandle_t MiddleTask_Handler;
//任务函数
void middle_task(void *pvParameters);

//任务优先级
#define HIGH_TASK_PRIO     4       //高优先级任务,会获取互斥信号量,获取成功以后会进行相应的处理,处理完成以后会释放互斥信号量
//任务堆栈大小
#define HIGH_STK_SIZE      256
//任务句柄
TaskHandle_t HighTask_Handler;
//任务函数
void high_task(void *pvParameters);

//互斥信号量句柄
SemaphoreHandle_t MutexSemaphore;   //互斥信号量

//LCD刷屏时使用的颜色
int lcd_discolor[14]={	WHITE, BLACK, BLUE,  BRED,      
						GRED,  GBLUE, RED,   MAGENTA,       	 
						GREEN, CYAN,  YELLOW,BROWN, 			
						BRRED, GRAY };

int main(void)
{
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);  
    delay_init(168);
    uart_init(115200);
    LED_Init();
    KEY_Init();
    BEEP_Init();
    LCD_Init();
    my_mem_init(SRAMIN);        //初始化内部内存池
    
    POINT_COLOR=RED;
    LCD_ShowString(30,10,200,16,16,"ATK STM32F407");
    LCD_ShowString(30,30,200,16,16,"FreeRTOS Example");
    LCD_ShowString(30,50,200,16,16,"Mutex Semaphore");
    LCD_ShowString(30,70,200,16,16,"ATOM@ALIENTEK");
    LCD_ShowString(30,90,200,16,16,"2023/10/31");
    
    //创建开始任务
    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();       //进入临界区
    
    //创建互斥信号量
    MutexSemaphore=xSemaphoreCreateMutex();  //创建互斥信号量函数,返回创建成功的互斥信号量句柄
    
    
    //创建高优先级任务
    xTaskCreate((TaskFunction_t)high_task,         //任务函数
                (const char*   )"high_task",       //任务名称
                (uint16_t      )HIGH_STK_SIZE,     //任务堆栈大小
                (void*         )NULL,               //传递给任务函数的参数
                (UBaseType_t   )HIGH_TASK_PRIO,    //任务优先级
                (TaskHandle_t* )&HighTask_Handler);//任务句柄
    //创建中等优先级任务
    xTaskCreate((TaskFunction_t)middle_task,         //任务函数
                (const char*   )"middle_task",       //任务名称
                (uint16_t      )MIDDLE_STK_SIZE,     //任务堆栈大小
                (void*         )NULL,               //传递给任务函数的参数
                (UBaseType_t   )MIDDLE_TASK_PRIO,    //任务优先级
                (TaskHandle_t* )&MiddleTask_Handler);//任务句柄
    //创建低优先级任务
    xTaskCreate((TaskFunction_t)low_task,         //任务函数
                (const char*   )"low_task",       //任务名称
                (uint16_t      )LOW_STK_SIZE,     //任务堆栈大小
                (void*         )NULL,               //传递给任务函数的参数
                (UBaseType_t   )LOW_TASK_PRIO,    //任务优先级
                (TaskHandle_t* )&LowTask_Handler);//任务句柄
    vTaskDelete(StartTask_Handler);
    taskEXIT_CRITICAL();            //退出临界区
}

//高优先级任务任务函数
void high_task(void *pvParameters)
{
    u8 num;
    
    POINT_COLOR=BLACK;
    LCD_DrawRectangle(5,110,115,314);   //画一个矩形
    LCD_DrawLine(5,130,115,130);        //画线
    POINT_COLOR=BLUE;
    LCD_ShowString(6,111,110,16,16,"High Task");
    
    while(1)
    {
        vTaskDelay(500);   //延时500ms,也就是500个时钟节拍
        num++;
        printf("high task Pend Semaphore\r\n");
        xSemaphoreTake(MutexSemaphore,portMAX_DELAY);    //获取互斥信号量
        //获取互斥信号量的阻塞时间设置为无限等待,既然这个任务可以获取互斥信号量,那么总有一个时刻可以获取到互斥信号量
        //否则程序就会卡在这里
        printf("high task Running!\r\n");   //获取到互斥信号量,高优先级任务开始运行
        LCD_Fill(6,131,114,313,lcd_discolor[num%14]);   //填充区域
        LED1=!LED1;
        xSemaphoreGive(MutexSemaphore);           //释放互斥信号量,当高优先级任务获取互斥信号量完成相应的处理之后,就会释放掉信号量
        vTaskDelay(500);                //延时500ms,也就是500个时钟节拍
    }
}

//中等优先级任务的任务函数
void middle_task(void *pvParameters)
{
    u8 num;
    
    POINT_COLOR=BLACK;
    LCD_DrawRectangle(125,110,234,314);     //画一个矩形
    LCD_DrawLine(125,130,234,130);      //画线
    POINT_COLOR=BLUE;
    LCD_ShowString(126,111,110,16,16,"Middle Task");
    
    while(1)
    {
        num++;
        printf("middle task Running!\r\n");
        LCD_Fill(126,131,233,313,lcd_discolor[13-num%14]);  //倒过来填充区域
        LED0=!LED0;
        vTaskDelay(1000);       //延时1s,也就是1000个时钟节拍
    }
}

//低优先级任务的任务函数
//低优先级任务占用互斥信号量的时间更长
void low_task(void *pvParameters)
{
    static u32 times;
    
    while(1)
    {
        xSemaphoreTake(MutexSemaphore,portMAX_DELAY);  //获取二值信号量
        printf("low task Running!\r\n");            
        for(times=0;times<20000000;times++)         //模拟低优先级占用二值信号量
        {
            taskYIELD();            //发起任务调度
            //这也就保证了低优先级任务占用二值信号量的时间更长
            //因为我一旦发起了任务调度,低优先级抢占的这个二值信号量是不能被高优先级的任务所抢占的
        }
        xSemaphoreGive(MutexSemaphore);           //释放二值信号量
        vTaskDelay(1000);       //延时1s,也就是1000个时钟节拍
    }
}




2.1.2 实验现象

通过上述串口输出的数据进行分析!

        首先高优先级任务存在延时,所以中等优先级任务抢占 CPU,中等优先级任务运行一个时间片之后,低优先级任务获取互斥信号量,高优先级任务延时时间到以后抢占CPU使用权,高优先级任务请求信号量,等待低优先级任务释放互斥信号量,此时中等优先级任务不会运行,因为互斥信号量优先级继承的缘故,低优先级任务临时获得和高优先级任务同等的优先级,等待低优先级任务释放互斥信号量之后,高优先级任务得以运行,高优先级任务释放信号量之后,任务调度,中等优先级任务运行,高优先级任务请求信号量,此时信号量被高优先级任务获取,高优先级任务运行,然后中等任务运行,低优先级任务运行!!!

互斥信号量的精髓在于:低优先级任务正在使用互斥信号量,而高优先级任务请求使用互斥信号量,此时会临时的将低优先级任务的优先级提高到和高优先级任务一个层次,这时被提升上来的任务就不会被其他优先级任务所打断,最大程度保证高优先级任务尽快获得互斥信号量,提高系统的响应性能。当高优先级任务释放信号量之后,被提升的任务回到之前的优先级。

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

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

相关文章

pytorch 笔记:GRU

1 介绍 对于输入序列中的每个元素&#xff0c;每一层都计算以下函数&#xff1a; ht​ 是t时刻 的隐藏状态xt​ 是t时刻 的输入ht−1​ 是 t-1时刻 同层的隐藏状态或 0时刻 的初始隐藏状态rt​,zt​,nt​ 分别是重置门、更新门和新门。σ 是 sigmoid 函数∗ 是 Hadamard 乘积。…

kubernetes-service微服务

目录 一、service微服务 二、Ipvs模式 三、ClusterIP 1.ClusterIP 2.headless 四、NodePort 1.NodePort 2.默认端口 五、LoadBalancer 1.LoadBalancer 2.metallb 六、ExternalName 一、service微服务 Kubernetes Service微服务是一种基于Kubernetes的微服务架构&…

与云栖的浪漫邂逅:记一段寻找云端之美的旅程

云端之旅 2023 年的云栖大会如约而至&#xff0c;这次云栖大会也是阿里新任掌门蔡老板当任阿里巴巴董事局主席以来的第一次。大会与以往有很多不一样的地方&#xff0c;其中 AIGC 更是本届大会的重点议题&#xff01;你会感叹&#xff0c;阿里还是猛啊&#xff01; 我逛了下展…

十一、W5100S/W5500+RP2040树莓派Pico<ARP 地址解析>

文章目录 1 前言2 简介2 .1 什么是ARP&#xff1f;2.2 ARP的优点2.3 ARP工作原理2.4 ARP应用场景 3 WIZnet以太网芯片4 ARP网络设置示例概述以及使用4.1 流程图4.2 准备工作核心4.3 连接方式4.4 主要代码概述4.5 结果演示 5 注意事项6 相关链接 1 前言 随着网络安全越来越受到重…

UE5——网络——RPC

RPC&#xff08;这个是官方文档的资料&#xff09; 要将一个函数声明为 RPC&#xff0c;您只需将 Server、Client 或 NetMulticast 关键字添加到 UFUNCTION 声明。 例如&#xff0c;若要将某个函数声明为一个要在服务器上调用、但需要在客户端上执行的 RPC&#xff0c;您可以…

@JsonCreator 和 @JsonValue使用说明

Fastxml.jackson提供了JsonValue和JsonCreator注解用于序列化和反序列化时使用. Jackson 是一个流行的 Java 序列化和反序列化库,它可以将 Java 对象转换为 JSON 格式,并将 JSON 格式转换为 Java 对象。在 Jackson 中,@JsonCreator 和 @JsonValue 是两个重要的注解,用于自…

Kafka保证百万级数据写入和重发问题分享

Kafka作为当下流行的高并发消息中间件&#xff0c;大量用于数据采集&#xff0c;实时处理等场景&#xff0c; 那么它如何做到百万级写入速度呢?我们在享受它带来的高并发&#xff0c;高可靠等便利时&#xff0c;同时不得不面对可能存在的问题&#xff0c;项目中最常见的就是丢…

c++之vector容器

1.简介 向量&#xff08;Vector&#xff09;是一个封装了动态大小数组的顺序容器&#xff08;Sequence Container&#xff09;。跟任意其它类型容器一样&#xff0c;它能够存放各种类型的对象。可以简单的认为&#xff0c;向量是一个能够存放任意类型的动态数组。 1.1 vector和…

HyperAI超神经 x 中国信通院 | 可信开源大模型案例汇编(第一期)案例征集计划正式启动自定义目录标题)

为进一步促进大模型的开源和合作&#xff0c;引导开源大模型产业健康规范发展&#xff0c;中国信息通信研究院现开启「可信开源大模型案例汇编&#xff08;第一期&#xff09;」的案例征集计划。 HyperAI超神经将以合作伙伴的身份&#xff0c;协助调研国产开源大模型的技术细节…

敏捷开发用户故事

产品Backlog中的需求通常使用用户故事来表达。 用户故事是从用户&#xff08;需求方&#xff09;的视角描述对用户有价值的需求 Who 这个需求为谁服务 What 具体要做什么 Why 目的是什么 一个典型的用户故事会以如下形式表达&#xff1a; 用户故事格式示例&#xff1a; 作…

基于SC-LeGO-LOAM的建图和ndt_localizer的定位

link 基于SC-LeGO-LOAM的建图和ndt_localizer的定位 链接: link. SC-LeGO-LOAM 链接: link. ndt_localizer 将建图和定位两部分分开&#xff0c;利用SC-LeGO-LOAM进行建图&#xff0c;相比于LeGO-LOAM&#xff0c;其采用了Scan Context方法&#xff0c;对点云地图进行闭环检测和…

5G智能安全帽_实时对讲/视频通话/高精度定位_智能安全帽功能介绍

5G智能安全帽是一种具有工业级高清晰度摄像头和5G/WIFI网络功能的产品。在传统安全帽的基础上&#xff0c;智能安全帽集成了摄像头、语音和通信主板等模块。它具备高清视频采集、语音通讯、对讲、本地视频存储等功能&#xff0c;通过这种佩戴式设备&#xff0c;不仅可以实现数据…

app逆向之charles配置

声明&#xff1a;本文仅限学习交流使用&#xff0c;禁止用于非法用途、商业活动等。否则后果自负。如有侵权&#xff0c;请告知删除&#xff0c;谢谢&#xff01;本教程也没有专门针对某个网站而编写&#xff0c;单纯的技术研究 目录 charles配置手机配置 charles配置 最近有点…

瑞数专题五

今日文案&#xff1a;焦虑&#xff0c;想象力过度发酵的产物。 网址&#xff1a;https://www.iyiou.com/ 专题五主要是分享瑞数6代。6代很少见&#xff0c;所以找理想哥要的&#xff0c;感谢感谢。 关于瑞数作者之前已经分享过4篇文章&#xff0c;全都收录在瑞数专栏中了&am…

arcpy.message实现探索

arcpy 位置D:\Program Files\GeoScene\Pro\Resources\ArcPy\arcpy\__init__.py ”““AddMessage(消息) 创建可以使用任何GetMessages函数访问的地理处理信息消息(Severity0)。 message(字符串):要添加的消息。”“ arcpy.geoprocessing D:\Program Files\GeoScene\Pro\Re…

求购供应发布农业副业产品市场行情小程序开发

农业副业产品求购供应发布市场行情小程序H5开源版开发 后台同步&#xff1a;一键获取全国近200家农产品批发市场的商品价格&#xff0c;包括蔬菜、水果、水产、粮油和农副产品等。 实时更新和同步市场价格动态&#xff0c;保障信息的准确性和时效性。 前端VIP权益功能&…

WPF RelativeSource属性-目标对象类型易错

上一篇转载了RelativeSource的三种用法&#xff0c;其中第二种用法较常见&#xff0c;这里记录一下项目中曾经发生错误的地方&#xff0c;以防自己哪天忘记了&#xff0c;又犯了同样错误—WPF RelativeSource属性-CSDN博客 先回顾一下&#xff1a; 控件关联其父级容器的属性—…

利用Graviton2和S3免费套餐搭建私人网盘

网盘是一种在线存储服务&#xff0c;提供文件存储&#xff0c;访问&#xff0c;备份&#xff0c;贡献等功能&#xff0c;是我们日常中不可或缺的一种服务。很多互联网公司都为个人和企业提供免费的网盘服务。但这些免费服务都有一些限制&#xff0c;比如限制下载速度&#xff0…

C语言 Number 1 基本数据类型

数据类型的定义 c语言的数据分类基本类型整型浮点型float和double的精度和范围范围精度 枚举类型空类型派生类型派生的一般表达形式 注 c语言的数据分类 首先是针对C语言的数据类型做个整理 大致分为四个大类型 基本类型枚举类型空类型派生类型 那么根据以上四个大类型 我们…