STM32F1+HAL库+FreeTOTS学习13——二值信号量

news2024/12/24 11:35:10

STM32F1+HAL库+FreeTOTS学习13——二值信号量

  • 1. 信号量
  • 2. 二值信号量
  • 3. 相关API函数
    • 3.1 创建二值信号量
    • 3.2 获取信号量
    • 3.3 释放信号量
    • 3.4 删除信号量
  • 4. 二值信号量操作实验
    • 1. 实验内容
    • 2. 代码实现:
    • 3. 运行结果

上一期我们学习了FreeRTOS中的队列,这一期我们学习信号量中的——二值信号量

1. 信号量

在学习信号量之前,我们先来回顾一下队列:

  • 队列:队列是一种任务到任务、任务到中断、中断到任务数据交流的一种机制、

随之,我们给出信号量的定义:

  • 信号量:信号量是一种解决同步问题的机制,可以实现对共享资源的有序访问。其中,“同步”指的是任务间的同步,即信号量可以使得一个任务等待另一个任务完成某件事情后,才继续执行;而“有序访问”指的是对被多任务或中断访问的共享资源(如全局变量)的管理,当一个任务在访问(读取或写入)一个共享资源时,信号量可以防止其他任务或中断在这期间访问(读取或写入)这个共享资源。

我们来举一个简单的例子,比如说现在有一个停车场,停车场上有5个车位,如果现在小明需要去停车,那么他应该先要看一下是否还有车位(判断信号量是否有资源),如果有车位,就直接停车(获取信号成功,这个时候车位-1),如果没有车位,那么可以直接选择不停车(获取信号量失败),或者原地等待(任务阻塞),直到有人开车出停车场(释放信号量,车位+1)。

在上述的例子中:一共5个车位(信号资源数为5),有人开出停车场,信号量释放,信号量计数值(资源数)加1,反之则减1。

  • 因为有5个车位,计数值最大值为5,所以这个是计数信号量。
  • 如果只有一个车位,那么计数值只有0和1,这个就是二值信号量。
  • 类似的,还有互斥信号量,这个我们后面会一一介绍。

下面我们来看一下队列和信号量的对比

队列信号量
可以容纳多个数据;创建队列有两部分内存:队列结构体+队列项存储空间仅存放计数值,无法存放其他数据;创建信号量,只需分配信号量结构体
写入队列:当队列满时,可阻塞;释放信号量:不可阻塞,计数值++,当计数值为最大值时,返回失败
读取队列:当队列为空时,可阻塞;获取信号量:计数值–,当没有资源时,可阻塞

2. 二值信号量

二值信号量是信号量的一种,前面我们提到过,信号量是基于队列实现的,二值信号量也是一样,二值信号量实际上就是一个队列长度为 1 的队列,队列中只有空和满两种情况,常用于互斥访问和任务同步,与互斥信号量比较类似,但是二值信号量可能会导致优先级翻转的问题(关于优先级翻转,我们稍后做出介绍),所以二值信号量更适合用于同步。
在这里插入图片描述

优先级翻转问题:当一个高优先级任务因获取一个被低优先级任务获取而处于没有资源状态的二值信号量时,这个高优先级的任务将被阻塞,直到低优先级的任务释放二值信号量,而在这之前,如果有一个优先级介于这个高优先级任务和低优先级任务之间的任务就绪,那么这个中等优先级的任务就会抢占低优先级任务的运行,这么一来,这三个任务中优先级最高的任务反而要最后才运行,这就是二值信号量带来的优先级翻转问题。

以上就是二值信号量的内容,下面我们看二值信号量的相关API函数

3. 相关API函数

常用的二值信号量API函数如下表:

函数描述
xSemaphoreCreateBinary()使用动态方式创建二值信号量
xSemaphoreCreateBinaryStatic()使用静态方式创建二值信号量
xSemaphoreTake()获取信号量
xSemaphoreTakeFromISR()在中断中获取信号量
xSemaphoreGive()释放信号量
xSemaphoreGiveFromISR()在中断中释放信号量
vSemaphoreDelete()删除信号量

3.1 创建二值信号量

  1. xSemaphoreCreateBinary()

此函数用于动态方式创建二值信号量,创建所需要的内存,有FreeRTOS自动分配,该函数实际上是一个宏定义,在semphr.h文件中有定义,具体的代码如下:

#define xSemaphoreCreateBinary() \
 xQueueGenericCreate( ( UBaseType_t ) 1, \
 semSEMAPHORE_QUEUE_ITEM_LENGTH, \
 queueQUEUE_TYPE_BINARY_SEMAPHORE)

可以看到xSemaphoreCreateBinary() 内部是调用了xQueueGenericCreate() ,该函数在 STM32F1+HAL库+FreeTOTS学习12——队列 中有介绍,我们这里不赘述。所以我们直接把xSemaphoreCreateBinary() 当作一个函数介绍(实际上是一个宏,我们当作函数来使用,FreeRTOS的官方文档也是这样的),下面是函数原型:

/**
 * @brief       xSemaphoreCreateBinary
 * @param       无
 * @retval      返回值为NULL,表示创建失败,其他值表示为创建二值信号量的句柄
 */
SemaphoreHandle_t xSemaphoreCreateBinary( void );
  1. xSemaphoreCreateBinaryStatic()
    此函数用于静态方式创建二值信号量,创建二值信号量所需要的内存,需要用户手动分配,该函数实际上是一个宏定义,在semphr.h文件中有定义,具体的代码如下:
#define xSemaphoreCreateBinaryStatic(pxStaticSemaphore) \
 xQueueGenericCreateStatic( ( UBaseType_t ) 1, \
							  semSEMAPHORE_QUEUE_ITEM_LENGTH, \
 							  NULL, \
 							  pxStaticSemaphore, \
							  queueQUEUE_TYPE_BINARY_SEMAPHORE)

可以看到xSemaphoreCreateBinaryStatic() 内部是调用了xQueueGenericCreateStatic() ,有用该函数不经常使用,我们这里不赘述。所以我们直接把xSemaphoreCreateBinaryStatic() 当作一个函数介绍(实际上是一个宏,我们当作函数来使用,FreeRTOS的官方文档也是这样的),下面是函数原型:

/**
 * @brief       xSemaphoreCreateBinaryStatic
 * @param       pxSemaphoreBuffer:必须指向 StaticSemaphore_t 类型的变量,该变量将用于保存信号量的状态。
 * @retval      返回值为NULL,表示创建失败,其他值表示为创建二值信号量的句柄
 */
SemaphoreHandle_t xSemaphoreCreateBinaryStatic(
                          StaticSemaphore_t *pxSemaphoreBuffer );

3.2 获取信号量

  1. xSemaphoreTake()

此函数用于获取信号量,如果信号量处于没有资源的状态,那么可以选择将任务进入阻塞状态,如果成功获取到了信号量,那么信号的资源数减1,该函数实际上是一个宏定义,在semphr.h文件中有定义,具体的代码如下:

#define xSemaphoreTake( xSemaphore, \
						 xBlockTime) \
 xQueueSemaphoreTake( ( xSemaphore ), \
 					  ( xBlockTime ))

可以看到xSemaphoreTake() 内部是调用了xQueueSemaphoreTake() ,关于xQueueSemaphoreTake函数的定义和使用,我们这里不赘述。所以我们直接把xSemaphoreTake() 当作一个函数介绍(实际上是一个宏,我们当作函数来使用,FreeRTOS的官方文档也是这样的),下面是函数原型:

/**
 * @brief       xSemaphoreTake
 * @param       xSemaphore:需要获取二值信号量的句柄
 * @param       xTicksToWait:阻塞时间
 * @retval      返回值为pdTRUE,表示获取成功,如果返回值为pdFALSE,表示获取失败。
 */
 BaseType_t xSemaphoreTake( SemaphoreHandle_t xSemaphore,
                 TickType_t xTicksToWait );
  1. xSemaphoreTakeFromISR()

此函数用于在中断中获取信号量,该函数实际上是一个宏定义,在semphr.h文件中有定义,具体的代码如下:

#define xSemaphoreTakeFromISR( xSemaphore, \
							   pxHigherPriorityTaskWoken) \
 xQueueReceiveFromISR( ( QueueHandle_t ) \
 						( xSemaphore ), \
 					   	 NULL, \
 					   ( pxHigherPriorityTaskWoken ))

可以看到xSemaphoreTakeFromISR() 内部是调用了xQueueReceiveFromISR() ,关于xQueueReceiveFromISR函数的定义和使用,我们这里不赘述。所以我们直接把xSemaphoreTakeFromISR() 当作一个函数介绍(实际上是一个宏,我们当作函数来使用,FreeRTOS的官方文档也是这样的),下面是函数原型:

/**
 * @brief       xSemaphoreTakeFromISR
 * @param       xSemaphore:需要获取二值信号量的句柄
 * @param       pxHigherPriorityTaskWoken:获取信号量之后是否需要任务切换,需要切换则 *pxHigherPriorityTaskWoken = pdTRUE
 * @retval      返回值为pdTRUE,表示获取成功,如果返回值为pdFALSE,表示获取失败。
 */
BaseType_t xSemaphoreTakeFromISR ( SemaphoreHandle_t xSemaphore, signed BaseType_t *pxHigherPriorityTaskWoken );

需要注意的是:

  • xSemaphoreTake()可以用来获取二值信号量、计数信号量、互斥信号量。
  • xSemaphoreTakeFromISR() 只能用来获取二值信号量和计数信号量,不能用于互斥信号量。

3.3 释放信号量

  1. xSemaphoreGive()

此函数用于释放信号量,如果信号量处于资源满的状态,那么可以选择将任务进入阻塞状态,如果成功释放了信号量,那么信号的资源数加1,该函数实际上是一个宏定义,在semphr.h文件中有定义,具体的代码如下:

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

可以看到xSemaphoreGive() 内部是调用了xQueueGenericSend() ,该函数在 STM32F1+HAL库+FreeTOTS学习12——队列 中有介绍,我们这里不赘述。所以我们直接把xSemaphoreGive() 当作一个函数介绍(实际上是一个宏,我们当作函数来使用,FreeRTOS的官方文档也是这样的),下面是函数原型:

/**
 * @brief       xSemaphoreGive
 * @param       xSemaphore:需要释放二值信号量的句柄
 * @retval      返回值为pdTRUE,表示释放成功,如果返回值为pdFALSE,表示释放失败。
 */
 BaseType_t xSemaphoreGive( SemaphoreHandle_t xSemaphore );
  1. xSemaphoreGiveFromISR()

此函数用于在中断中释放信号量,该函数实际上是一个宏定义,在semphr.h文件中有定义,具体的代码如下:

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

可以看到xSemaphoreGiveFromISR() 内部是调用了xQueueGiveFromISR() ,关于xQueueGiveFromISR函数的定义和使用,我们这里不赘述。所以我们直接把xSemaphoreGiveFromISR() 当作一个函数介绍(实际上是一个宏,我们当作函数来使用,FreeRTOS的官方文档也是这样的),下面是函数原型:

/**
 * @brief       xSemaphoreGiveFromISR
 * @param       xSemaphore:需要释放二值信号量的句柄
 * @param       pxHigherPriorityTaskWoken:释放信号量之后是否需要任务切换,需要切换则 *pxHigherPriorityTaskWoken = pdTRUE
 * @retval      返回值为pdTRUE,表示释放成功,如果返回值为pdFALSE,表示释放失败。
 */
BaseType_t xSemaphoreGiveFromISR ( SemaphoreHandle_t xSemaphore, signed BaseType_t *pxHigherPriorityTaskWoken )

需要注意的是:

  • xSemaphoreGive()可以用来释放二值信号量、计数信号量、互斥信号量。
  • xSemaphoreGiveFromISR () 只能用来释放二值信号量和计数信号量,不能用于互斥信号量。因为互斥信号量会有优先级继承的处理,而中断不属于任务,没有办法进行优先级继承。

3.4 删除信号量

  1. vSemaphoreDelete()
    此函数用于删除已创建的信号量。该函数实际上是一个宏定义,在 semphr.h 文件中有定义,具体的代码如下所示
#define vSemaphoreDelete(xSemaphore) \
 		vQueueDelete ( QueueHandle_t ) \
					 ( xSemaphore ))

可以看到vSemaphoreDelete() 内部是调用了vQueueDelete () ,关于vQueueDelete 函数的定义和使用,我们这里不赘述。所以我们直接把vSemaphoreDelete() 当作一个函数介绍(实际上是一个宏,我们当作函数来使用,FreeRTOS的官方文档也是这样的),下面是函数原型:

/**
 * @brief       vSemaphoreDelete
 * @param       xSemaphore :需要删除信号量的句柄
 * @retval      无
 */
void vSemaphoreDelete( SemaphoreHandle_t xSemaphore );

4. 二值信号量操作实验

1. 实验内容

在STM32F103RCT6上运行FreeRTOS,通过按键控制,完成对应的二值信号量读写,具体要求如下:

  • 定义一个二值信号量,
  • 定义任务1:每次按下按键0,释放信号量
  • 定义任务2:获取二值信号量

2. 代码实现:

  • 由于本期内容涉及到按键扫描,会用到: STM32框架之按键扫描新思路 ,这里不做过多介绍。我们直接来看代码:
  1. freertos_demo.c
#include "freertos_demo.h"
#include "main.h"
#include "queue.h" 		//需要包含队列和任务相关的头文件
#include "task.h"
#include "key.h"		//包含按键相关头文件

/*FreeRTOS*********************************************************************************************/

/******************************************************************************************************/
/*FreeRTOS配置*/

/* TASK1 任务 配置
 * 包括: 任务句柄 任务优先级 堆栈大小 
 */
#define TASK1_PRIO      1                  /* 任务优先级 */
#define TASK1_STK_SIZE  128                 /* 任务堆栈大小 */
TaskHandle_t            Task1Task_Handler;  /* 任务句柄 */
void task1(void *pvParameters);					/*任务函数*/

/* TASK2 任务 配置
 * 包括: 任务句柄 任务优先级 堆栈大小 
 */
#define TASK2_PRIO      2                  /* 任务优先级 */
#define TASK2_STK_SIZE  128                 /* 任务堆栈大小 */
TaskHandle_t            Task2Task_Handler;  /* 任务句柄 */
void task2(void *pvParameters);					/*任务函数*/

QueueHandle_t SemaphoreBinary;				/* 定义二值信号量 */

/******************************************************************************************************/

/**
 * @brief       FreeRTOS例程入口函数
 * @param       无
 * @retval      无
 */
void freertos_demo(void)
{
	taskENTER_CRITICAL();           /* 进入临界区,关闭中断,此时停止任务调度*/

	SemaphoreBinary = xSemaphoreCreateBinary();
	if(SemaphoreBinary != NULL)
	{
		printf("二值信号量创建成功!!!\r\n");
	}
	else
	{
		printf("二值信号量创建失败!!!\r\n");
	}
	
    /* 创建任务1 */
    xTaskCreate((TaskFunction_t )task1,
                (const char*    )"task1",
                (uint16_t       )TASK1_STK_SIZE,
                (void*          )NULL,
                (UBaseType_t    )TASK1_PRIO,
                (TaskHandle_t*  )&Task1Task_Handler);
    /* 创建任务2 */
    xTaskCreate((TaskFunction_t )task2,
                (const char*    )"task2",
                (uint16_t       )TASK2_STK_SIZE,
                (void*          )NULL,
                (UBaseType_t    )TASK2_PRIO,
                (TaskHandle_t*  )&Task2Task_Handler);

    taskEXIT_CRITICAL();            /* 退出临界区,重新开启中断,开启任务调度 */
    vTaskStartScheduler();		//开启任务调度
}

/**
 * @brief       task1
 * @param       pvParameters : 传入参数(未用到)
 * @retval      无
 */
void task1(void *pvParameters)
{
    
    while(1)
    {
		Key_One_Scan(Key_Name_Key0,Key0_Up_Task,Key0_Down_Task);		/* 按键0扫描,按下后释放二值信号量 */

       
    }
}	
/**
 * @brief       task2
 * @param       pvParameters : 传入参数(未用到)
 * @retval      无
 */
void task2(void *pvParameters)	
{	
	

	BaseType_t errMessage;		/* 错误信息 */
	
	while(1)
    {	
	
	errMessage = xSemaphoreTake(SemaphoreBinary,portMAX_DELAY);		/* 获取二值信号量 */
	if(errMessage == pdTRUE)	
	{
		printf("获取信号量成功\r\n");
	}
	else
	{
		printf("获取信号量失败\r\n");
	}

    }
}
  1. key.c
/* USER CODE BEGIN 2 */

#include "freertos_demo.h"
#include "key.h"
#include "usart.h"

extern QueueHandle_t SemaphoreBinary;				/* 声明外部的二值信号量 */

void Key0_Down_Task(void)
{
	BaseType_t errMessage;		/* 错误信息 */
	
	errMessage = xSemaphoreGive(SemaphoreBinary);	/* 释放二值信号量 */
	if(errMessage == pdTRUE)
	{
		printf("二值信号量释放成功\r\n");
	}
	else
	{
		printf("二值信号量释放失败\r\n");
	}
}
void Key0_Up_Task(void)
{
	
}
void Key1_Down_Task(void)
{
	
}
void Key1_Up_Task(void)
{
	
}
void Key2_Down_Task(void)
{

}
void Key2_Up_Task(void)
{

}
void WKUP_Down_Task(void)
{

}
void WWKUP_Up_Task(void)
{

}

void Key_One_Scan(uint8_t KeyName ,void(*OnKeyOneUp)(void), void(*OnKeyOneDown)(void))
{
   static uint8_t Key_Val[Key_Name_Max];    //按键值的存放位置
   static uint8_t Key_Flag[Key_Name_Max];   //KEY0~2为0时表示按下,为1表示松开,WKUP反之
    
   Key_Val[KeyName] = Key_Val[KeyName] <<1;  //每次扫描完,将上一次扫描的结果左移保存
   
    switch(KeyName)
    {
        case Key_Name_Key0:  Key_Val[KeyName] = Key_Val[KeyName] | (HAL_GPIO_ReadPin(KEY0_GPIO_Port, KEY0_Pin));    //读取Key0按键值
            break;
        case Key_Name_Key1:  Key_Val[KeyName] = Key_Val[KeyName] | (HAL_GPIO_ReadPin(KEY1_GPIO_Port, KEY1_Pin));   //读取Key1按键值
            break;
        case Key_Name_Key2:  Key_Val[KeyName] = Key_Val[KeyName] | (HAL_GPIO_ReadPin(KEY2_GPIO_Port, KEY2_Pin));   //读取Key2按键值
            break;
//        case Key_Name_WKUP:  Key_Val[KeyName] = Key_Val[KeyName] | (HAL_GPIO_ReadPin(WKUP_GPIO_Port, WKUP_Pin));   //读取WKUP按键值
//            break; 
        default:
            break;
    }
//    if(KeyName == Key_Name_WKUP)     //WKUP的电路图与其他按键不同,所以需要特殊处理
//    {
//        //WKUP特殊情况
//        //当按键标志为1(松开)是,判断是否按下,WKUP按下时为0xff
//        if(Key_Val[KeyName] == 0xff && Key_Flag[KeyName] == 1)
//        {
//            (*OnKeyOneDown)();
//           Key_Flag[KeyName] = 0;
//        }
//        //当按键标志位为0(按下),判断按键是否松开,WKUP松开时为0x00
//        if(Key_Val[KeyName] == 0x00 && Key_Flag[KeyName] == 0)
//        {
//            (*OnKeyOneUp)();
//           Key_Flag[KeyName] = 1;
//        } 
//    }
//    else                               //Key0~2按键逻辑判断
//    {
        //Key0~2常规判断
          //当按键标志为1(松开)是,判断是否按下
        if(Key_Val[KeyName] == 0x00 && Key_Flag[KeyName] == 1)
        {
            (*OnKeyOneDown)();
           Key_Flag[KeyName] = 0;
        }
        //当按键标志位为0(按下),判断按键是否松开
        if(Key_Val[KeyName] == 0xff && Key_Flag[KeyName] == 0)
        {
            (*OnKeyOneUp)();
           Key_Flag[KeyName] = 1;
        }  
    }
     
   
//}
/* USER CODE END 2 */

3. 运行结果

在这里插入图片描述

这里解释一下,为什么按键0按下,先出现获取信号量成功,而不是直接打印二值信号量释放成功:

  • 在按键0没有按下之前,任务2处于阻塞状态,
  • 按键0按下之后,信号量释放,任务2立刻进入就绪状态。
  • 由于任务2的优先级比任务1高,所以先会立刻获取信号量,并打印获取信号量成功
  • 答应成功后,任务2再次进入阻塞,进入任务1,打印二值信号量释放成功。

以上就是本期使用到的核心代码,其他部分我这里就不做展示,直接去看我往期的内容,源代码都有的。至于按键的配置,可以参考:夜深人静学32系列9——GPIO驱动数码管/蜂鸣器/按键/LED

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

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

相关文章

【Linux探索学习】第二弹——Linux的基础指令(中)——夯实基础第二篇

Linux基础指令&#xff08;上&#xff09;&#xff1a;【Linux探索学习】第一弹——Linux的基本指令&#xff08;上&#xff09;——开启Linux学习第一篇-CSDN博客 前言&#xff1a; 在前面我们已经讲解了一些常用的Linux的基础指令&#xff0c;那些当然是远远不够的&#xff…

舵机在无人机中的应用

一、舵机工作原理 舵机是一种位置&#xff08;角度&#xff09;伺服的驱动器&#xff0c;由电子控制与机械控制部分组成。当控制信号输入时&#xff0c;电子控制部分会根据控制器的指令调节直流电机输出的旋转角度和速度&#xff0c;由机械部分转化为控制面的位移以及相应的角…

基于SpringBoot+Vue的个人健康管理系统

作者&#xff1a;计算机学姐 开发技术&#xff1a;SpringBoot、SSM、Vue、MySQL、JSP、ElementUI、Python、小程序等&#xff0c;“文末源码”。 专栏推荐&#xff1a;前后端分离项目源码、SpringBoot项目源码、Vue项目源码、SSM项目源码、微信小程序源码 精品专栏&#xff1a;…

基于Python可视化的学习系统的设计与实现(源码+文档+调试+答疑)

文章目录 一、项目介绍二、视频展示三、开发环境四、系统展示五、代码展示六、项目文档展示七、项目总结 大家可以帮忙点赞、收藏、关注、评论啦 &#x1f447;&#x1f3fb; 一、项目介绍 随着计算机技术发展&#xff0c;计算机系统的应用已延伸到社会的各个领域&#xff0c…

小程序原生-利用setData()对不同类型的数据进行增删改

1. 声明和绑定数据 wxml文件 <view> {{school}} </view> <view>{{obj.name}}</view> <view id"{{id}}" > 绑定属性值 </view> <checkbox checked"{{isChecked}}"/> <!--算数运算--> <view>{{ id …

TS系列(7):知识点汇总

你好&#xff0c;我是沐爸&#xff0c;欢迎点赞、收藏、评论和关注。 一、TS是什么&#xff1f; TypeScript 由微软开发&#xff0c;是基于 JavaScript 的一个扩展语言。TypeScript 包含 JavaScript 的所有内容&#xff0c;是 JavaScript 的超集。TypeScript 增加了静态类型检…

基于SSM的宠物领养管理系统的设计与实现 (含源码+sql+视频导入教程+文档+PPT)

&#x1f449;文末查看项目功能视频演示获取源码sql脚本视频导入教程视频 1 、功能描述 基于SSM的宠物领养管理系统2拥有两种角色 管理员&#xff1a;宠物分类管理、领养管理、宠物商品管理、用户管理、寄存管理、丢失信息管理、订单管理等 用户&#xff1a;登录注册、收藏评…

算法:LCR 173. 点名 (原:剑指 offer:0~n-1 中缺失的数字 )

题目 链接&#xff1a;leetcode链接 思路分析&#xff08;一题多解&#xff09; 思路一&#xff1a;高斯求和公式 利用高斯求和公式求出0~n的和&#xff0c;然后减去nums数组中的每一个数&#xff0c;最后的结果就是缺失的数字 时间复杂度&#xff0c;O(N) 过于简单&#xff…

nginx:反向代理服务器——一个非常强大且灵活的工具

Nginx 是一个高性能的 HTTP 和反向代理服务器&#xff0c;常用于 Web 服务器、负载均衡器和反向代理。它以其高性能、稳定性、丰富的功能集、简单的配置文件和低资源消耗而著称。在部署 Web 应用程序时&#xff0c;Nginx 常被用来处理静态文件、反向代理动态内容、负载均衡等任…

AI大模型面试大纲

大纲 1. 介绍和背景 自我介绍&#xff08;5分钟&#xff09; 了解候选人的教育背景、工作经历和对大模型架构的兴趣。 2. 基础理论和概念&#xff08;30分钟&#xff09; 机器学习基础 解释基本概念&#xff0c;如监督学习、无监督学习和强化学习。 讨论不同的模型类型&#xf…

UG NX二次开发(C#)-建模-根据拉伸体获取草图对象

文章目录 1、前言2、在UG NX中创建基于草图的拉伸对象2.1 在建模模块中进入草图环境2.2 创建拉伸特征2.3 分析拉伸特征父项3 实现代码3.1 基于NXOpen实现3.2 UFun函数实现3.3 效果1、前言 今天在QQ群中,有个群友咨询了根据拉伸体获取草图对象,我今天难得清闲一次,就讲一下吧…

JQuery基本介绍和使用方法

JQuery基本介绍和使用方法 W3C 标准给我们提供了⼀系列的函数, 让我们可以操作: ⽹⻚内容⽹⻚结构⽹⻚样式 但是原⽣的JavaScript提供的API操作DOM元素时, 代码⽐较繁琐, 冗⻓. 我们可以使⽤JQuery来操作⻚⾯对象. jQuery是⼀个快速、简洁且功能丰富的JavaScript框架, 于20…

这几种文件找回方法你都知道吗?

一、基础方法 检查回收站 无论是Windows系统还是Mac系统&#xff0c;回收站&#xff08;或废纸篓&#xff09;都是文件被删除后的默认存放位置。可以打开回收站&#xff0c;查看是否有误删的文件&#xff0c;并右键单击选择“还原”来恢复。利用文件历史记录或备份 Windows系统…

GDAL Unable to open EPSG support file gcs.csv

python环境从3.6升级到3.7&#xff0c;gdal版本从2.2.4升级到3.4.1之后&#xff0c;执行原来的gdal脚本&#xff0c;结果报出如下错误 ”ERROR 4: Unable to open EPSG support file gcs.csv. Try setting the GDAL_DATA environment variable to point to the directory conta…

CMake教程(八):添加定制命令和生成的文件

本篇继续 CMake 官网教程的第八篇教程&#xff0c;所用材料是 Step8 目录下的源代码。 本篇教程主要讲解如何通过 CMake 生成一个头文件&#xff0c;该头文件当中包含了 1 到 10 的平方根表格&#xff0c;然后在程序的其它部分包含这个生成的头文件。 出于教学的目的&#xf…

氨基酸在PDB文件中的原子命名规则

氨基酸在PDB文件中的原子命名规则 氨基和羧基上的原子都采用本名&#xff0c;C, N, O, H, etc. 其它原子除 H 外&#xff0c;所有原子命名均采用“原子名后缀[编号]”形式。整体命名方法类似于图论中求解最大流问题时所采用的标号法。首先α-C被命名为CA。其后按照成键关系逐级…

推荐一个可以把PDF样本册转换为翻页电子书的网站

​随着互联网的普及&#xff0c;越来越多的企业和个人开始意识到线上展览的重要性。如何将实体样本册转化为线上版本&#xff0c;让更多人了解和欣赏自己的产品与服务&#xff1f; 一、网站简介 这款PDF样本册免费上传网站名为“FLBOOK”&#xff0c;致力于为广大用户提供便捷…

书生大模型实战(从入门到进阶)L3-彩蛋岛-InternLM 1.8B 模型 Android 端侧部署实践

目录 1 环境准备 1.1 安装rust 1.2 安装Android Studio 1.3 设置环境变量 2 转换模型 2.1 安装mlc-llm 2.2 (可选)转换参数 2.3 (可选)生成配置 2.4 (可选)上传到huggingface 2.5 (可选) 测试转换的模型 3 打包运行 3.1 修改配置文件 3.2 运行打包命令 3.3 创建签…

Parallels Desktop19官方中文版10月最新

如何使用 Parallels Desktop 在 Mac 上运行虚拟机 Parallels Desktop 是一款强大的虚拟机软件&#xff0c;允许 Mac 用户在 macOS 上方便地运行 Windows 和其他操作系统。这款软件尤其适合开发者、设计师以及任何需要使用不同操作系统的用户。本文将为新手用户提供一步一步的指…

EthernetIP IO从站设备数据 转profinet IO项目案例

目录 1 案例说明 1 2 VFBOX网关工作原理 1 3 准备工作 2 4 网关采集ETHERNETIP IO数据 2 5 用PROFINET IO协议转发数据 4 6 从设备的的EDS文件获取参数信息 7 7 案例总结 9 1 案例说明 设置网关采集EthernetIP IO设备数据把采集的数据转成profinet IO协议转发给其他系统。 2…