【正点原子STM32连载】 第四十八章 内存管理实验 摘自【正点原子】STM32F103 战舰开发指南V1.2

news2024/11/24 3:03:57

1)实验平台:正点原子stm32f103战舰开发板V4
2)平台购买地址:https://detail.tmall.com/item.htm?id=609294757420
3)全套实验源码+手册+视频下载地址: http://www.openedv.com/thread-340252-1-1.html#

第四十八章 内存管理实验

本章,我们将介绍内存管理。我们将使用内存的动态管理减少对内存的浪费。本章分为如下几个小节:
48.1 内存管理简介
48.2 硬件设计
48.3 程序设计
48.4 下载验证

48.1 内存管理简介
内存管理,是指软件运行时对计算机内存资源的分配和使用的技术。其最主要的目的是如何高效、快速的分配,并且在适当的时候释放和回收内存资源。内存管理的实现方法有很多种,其实最终都是要实现两个函数:malloc和free。malloc函数用来内存申请,free函数用于内存释放。
本章,我们介绍一种比较简单的办法来实现:分块式内存管理。下面我们介绍一下该方法的实现原理,如图48.1.1所示:
在这里插入图片描述

图48.1.1 分块式内存管理原理
从上图可以看出,分块式内存管理由内存池和内存管理表两部分组成。内存池被等分为了n块,对应的内存管理表,大小也为n,内存管理表的每一个项对应内存池的一块内存。
内存管理表的项值代表的意义为:当该项值为0的时候,代表对应的内存块未被占用,当该项值非零的时候,代表该项对应的内存块已经被占用,其数值则代表被连续占用的内存块数。比如某项值为10,那么说明包括本项对应的内存块在内,总共分配了10个内存块给外部的某个指针。
内存分配方向如上图所示,是从顶→底的分配方向。即首先从最末端开始找空内存。当内存管理刚初始化的时候,内存表全部清零,表示没有任何内存块被占用。
分配原理:
当指针p调用malloc申请内存的时候,先判断p要分配的内存块数(m),然后从第n开始,向下查找,直到找到m块连续的空内存块(即对应内存管理表项为0),然后将这m个内存管理表项的值都设置为m(标记被占用),最后,把最后的这个空内存块的地址返回指针p,完成一次分配。注意:如果当内存不够的时候(找到最后也没有找到连续m块空闲内存),则返回NULL给p,表示分配失败。
释放原理:
当p申请的内存用完,需要释放的时候,调用free函数实现。free函数先判断p指向的内存地址所对应的内存块,然后找到对应的内存管理表项目,得到p所占用的内存块数目m(内存管理表项目的值就是所分配内存块的数目),将这m个内存管理表项目的值都清零,标记释放,完成一次内存释放。
48.2 硬件设计

  1. 例程功能
    每次按下按键KEY0就申请2K字节内存,每次按下KEY1就写数据到申请到的内存里面,每次按下WK_UP按键用于释放内存。LED0闪烁用于提示程序正在运行。
  2. 硬件资源
    1)LED灯
    LED0 – PB5
    2)独立按键
    KEY0 – PE4
    KEY1 – PE3
    WK_UP – PA0
    3)串口1(USMART使用)
    4)正点原子 2.8/3.5/4.3/7/10寸TFTLCD模块(仅限MCU屏,16位8080并口驱动)
    5)STM32自带的SRAM
    6)开发板板载的SRAM
    48.3 程序设计
    48.3.1 程序流程图
    在这里插入图片描述

图48.3.1.1 内存管理实验程序流程图
48.3.2 程序解析
1.内存管理代码
这里我们只讲解核心代码,详细的源码请大家参考光盘本实验对应源码。内存管理驱动源码包括两个文件:malloc.c和malloc.h。这两个文件放在Middlewares文件夹下面的MALLOC文件夹。
下面我们直接介绍malloc.h中比较重要的一个结构体和内存参数宏定义,其定义如下:

/* mem1内存参数设定.mem1是F103内部的SRAM. */
#define MEM1_BLOCK_SIZE		32          /* 内存块大小为32字节 */
#define MEM1_MAX_SIZE    	40 * 1024 /* 最大管理内存40K,F103ZE内部SRAM总共64KB*/
#define MEM1_ALLOC_TABLE_SIZE   MEM1_MAX_SIZE/MEM1_BLOCK_SIZE   /* 内存表大小 */
/* mem2内存参数设定.mem2是F103外扩SRAM */
#define MEM2_BLOCK_SIZE   	32  		/* 内存块大小为32字节 */
#define MEM2_MAX_SIZE     963 *1024 /* 最大管理内存963K, F103外扩SRAM大小1024KB */
#define MEM2_ALLOC_TABLE_SIZE   MEM2_MAX_SIZE/MEM2_BLOCK_SIZE   /* 内存表大小 */
/* 内存管理控制器 */
struct _m_mallco_dev
{
    void (*init)(uint8_t);         	/* 初始化 */
    uint16_t (*perused)(uint8_t);  	/* 内存使用率 */
    uint8_t *membase[SRAMBANK];    	/* 内存池 管理SRAMBANK个区域的内存 */
    MT_TYPE *memmap[SRAMBANK];     	/* 内存管理状态表 */
    uint8_t  memrdy[SRAMBANK];     	/* 内存管理是否就绪 */
};

我们可以定义几个不同的内存管理表,再分配相应的指针给到管理控制器即可。程序中我们用宏定义MEM1_BLOCK_SIZE来定义malloc可以管理的内部内存池总大小,实际上我们定义为一个大小为MEM1_BLOCK_SIZE的数组,这样编译后就能获得一块实际的连续内存区域,这里是40KB,MEM1_ALLOC_TABLE_SIZE代表内存池的内存管理表大小。我们可以定义多个内存管理表,这样就可以同时管理多块内存。
从这里可以看出,如果内存分块越小,那么内存管理表就越大,当分块为2字节1个块的时候,内存管理表就和内存池一样大了(管理表的每项都是uint16_t类型),显然是不合适。我们这里取32字节,比例为1:16,内存管理表相对就比较小了。
通过这个内存管理控制器_m_malloc_dev结构体,我们把分块式内存管理的相关信息,其初始化函数、获取使用率、内存池、内存管理表以及内存管理的状态保存下来,实现对内存池的管理控制。其中,内存池的定义为:

/* 内存池(64字节对齐) */
static __align(64) uint8_t mem1base[MEM1_MAX_SIZE];  	/* 内部SRAM内存池 */
static __align(64) uint8_t mem2base[MEM2_MAX_SIZE] 
__attribute__((at(SRAM_BASE_ADDR)));	/* 外扩SRAM内存池 */

/* 内存管理表 */
static MT_TYPE mem1mapbase[MEM1_ALLOC_TABLE_SIZE];   	/* 内部SRAM内存池MAP */
static MT_TYPE mem2mapbase[MEM2_ALLOC_TABLE_SIZE] __attribute__((at(SRAM_BASE_ADDR + MEM2_MAX_SIZE))); 	/* 外扩SRAM内存池MAP */
这里我们定义了两个内存池:一个是内部的SRAM,另一个是外部的SRAM。

MDK支持用__attirbute__((at(地址)))的方法把变量定义到指定的区域,而且这个变量支持是算式,大家可以去MKD的帮助文件中查找__attribute__这个关键字查找相关信息,有比较详细的介绍。(SRAM实验一章我们也有过说明了)我们这里是通过这个关键字,指定mem2mapbase这个大数组的存放位置为SRAM上的空间,如果不加这个关键字修饰,MDK会默认把这些变量定义到STM32的内部空间,这样的话就超出了STM32内部的SRAM空间,编译时会直接报错。当然还有其它把变量定义到指定位置的方法,大家可以自行研究下。
其中,MEM1_MAX_SIZE是在头文件中定义的内存池大小。align(64)定义内存池为64字节对齐,这个非常重要!如果不加上这个限制,在某些情况下(比如分配内存给结构体指针),可能出现错误,所以一定要加上这个。
上面的写法是对于AC5来说的,但是如果你想换成AC6编译器的话就比较麻烦了,指定变量位置的函数变成__attribute
((section(“.bss.ARM.__at_地址”)))的方式,其中的.bss表示初始化值为0,而且这个方式不支持算式,所以还用上面的方法直接用宏计算出SRAM的地址的方法不可行了,所以我们需要直接手动算出SRAM对应的内存地址,同样地__align(64)在AC6下的写法也变成了__ALIGNED(64),还有其它差异的部分,大家参考MDK官方提供的AC5到AC6的迁移方法的文档,这样定义的方法就变成下面的方式:

/* 内存池(64字节对齐) */
static __ALIGNED(64) uint8_t mem1base[MEM1_MAX_SIZE]; 	/* 内部SRAM内存池 */
static __ALIGNED(64) uint8_t mem2base[MEM2_MAX_SIZE] __attribute__((section(".bss.ARM.__at_0X68000000"))); 	/* 外扩SRAM内存池 */

/* 内存管理表 */
static MT_TYPE mem1mapbase[MEM1_ALLOC_TABLE_SIZE];    	/* 内部SRAM内存池MAP */
static MT_TYPE mem2mapbase[MEM2_ALLOC_TABLE_SIZE] __attribute__((section(".bss.ARM.__at_0X680F0C00")));	/* 外扩SRAM内存池MAP */
整个malloc代码的核心函数:my_mem_malloc和my_mem_free,分别用于内存申请和内存释放。思路就是前面48.1所介绍的分配内存和释放内存,不过在这里,这两个函数知识内部调用,外部调用我们另外定义了mymalloc和myfree两个函数,其他函数我们就不多介绍了。下面看一下分配内存和释放内存相关函数,其定义如下:
/**
 * @brief       内存分配(内部调用)
 * @param       memx : 所属内存块
 * @param       size : 要分配的内存大小(字节)
 * @retval      内存偏移地址
 * @arg          0 ~ 0XFFFFFFFE : 有效的内存偏移地址
 * @arg          0XFFFFFFFF     : 无效的内存偏移地址
 */
static uint32_t my_mem_malloc(uint8_t memx, uint32_t size)
{
    signed long offset = 0;
    uint32_t nmemb;     	/* 需要的内存块数 */
    uint32_t cmemb = 0; /* 连续空内存块数 */
    uint32_t i;

    if (!mallco_dev.memrdy[memx]) 
    {
        mallco_dev.init(memx);          	/* 未初始化,先执行初始化 */
    }
    if (size == 0) return 0XFFFFFFFF;  	/* 不需要分配 */
    nmemb = size / memblksize[memx];   	/* 获取需要分配的连续内存块数 */
    if (size % memblksize[memx]) nmemb++;
    for (offset=memtblsize[memx] - 1; offset >= 0; offset--) /*搜索整个内存控制区*/
    {
        if (!mallco_dev.memmap[memx][offset])
        {
            cmemb++;            			/* 连续空内存块数增加 */
        }
        else 
        {
            cmemb = 0;          			/* 连续内存块清零 */
        }
        
        if (cmemb == nmemb)     			/* 找到了连续nmemb个空内存块 */
        {
            for (i = 0; i < nmemb; i++)	/* 标注内存块非空 */
            {
                mallco_dev.memmap[memx][offset + i] = nmemb;
            }
            return (offset * memblksize[memx]); /* 返回偏移地址 */
        }
    }
    return 0XFFFFFFFF;  /* 未找到符合分配条件的内存块 */
}

/**
 * @brief       释放内存(内部调用)
 * @param       memx   : 所属内存块
 * @param       offset : 内存地址偏移
 * @retval      释放结果
 * @arg          0, 释放成功;
 * @arg          1, 释放失败;
 * @arg          2, 超区域了(失败);
 */
static uint8_t my_mem_free(uint8_t memx, uint32_t offset)
{
    int i;

    if (!mallco_dev.memrdy[memx])   					/* 未初始化,先执行初始化 */
    {
        mallco_dev.init(memx);
        return 1;                   					/* 未初始化 */
    }
    if (offset < memsize[memx])     					/* 偏移在内存池内. */
    {
        int index = offset / memblksize[memx];      	/* 偏移所在内存块号码 */
        int nmemb = mallco_dev.memmap[memx][index]; /* 内存块数量 */
        for (i = 0; i < nmemb; i++)                 	/* 内存块清零 */
        {
            mallco_dev.memmap[memx][index + i] = 0;
        }
        return 0;
    }
    else
    {
        return 2;   									/* 偏移超区了. */
    }
}

/**
 * @brief       释放内存(外部调用)
 * @param       memx : 所属内存块
 * @param       ptr  : 内存首地址
 * @retval      无
 */
void myfree(uint8_t memx, void *ptr)
{
    uint32_t offset;
    if (ptr == NULL)return;     /* 地址为0. */
    offset = (uint32_t)ptr - (uint32_t)mallco_dev.membase[memx];
    my_mem_free(memx, offset);  /* 释放内存 */
}

/**
 * @brief       分配内存(外部调用)
 * @param       memx : 所属内存块
 * @param       size : 要分配的内存大小(字节)
 * @retval      分配到的内存首地址.
 */
void *mymalloc(uint8_t memx, uint32_t size)
{
    uint32_t offset;
offset = my_mem_malloc(memx, size);
    if (offset == 0XFFFFFFFF)   	/* 申请出错 */
    {
        return NULL;            	/* 返回空(0) */
    }
    else    /* 申请没问题, 返回首地址 */
    {
        return (void *)((uint32_t)mallco_dev.membase[memx] + offset);
    }
}
2. main.c代码
在main.c里面编写如下代码:
const char *SRAM_NAME_BUF[SRAMBANK] = {" SRAMIN ", " SRAMEX "};
int main(void)
{
    uint8_t paddr[20];  /* 存放P Addr:+p地址的ASCII值 */
    uint16_t memused = 0;
    uint8_t key;
    uint8_t i = 0;
    uint8_t *p = 0;
    uint8_t *tp = 0;
    uint8_t sramx = 0;  						/* 默认为内部sram */

    HAL_Init();                         		/* 初始化HAL库 */
    sys_stm32_clock_init(RCC_PLL_MUL9);		/* 设置时钟, 72Mhz */
    delay_init(72);                     		/* 延时初始化 */
    usart_init(115200);                 		/* 串口初始化为115200 */
    usmart_dev.init(72);                		/* 初始化USMART */
    led_init();                         		/* 初始化LED */
    lcd_init();                         		/* 初始化LCD */
    key_init();                         		/* 初始化按键 */
    sram_init();                        		/* SRAM初始化 */
    my_mem_init(SRAMIN);                		/* 初始化内部SRAM内存池 */
    my_mem_init(SRAMEX);                		/* 初始化外部SRAM内存池 */

    lcd_show_string(30,  50, 200, 16, 16, "STM32", RED);
    lcd_show_string(30,  70, 200, 16, 16, "MALLOC TEST", RED);
    lcd_show_string(30,  90, 200, 16, 16, "ATOM@ALIENTEK", RED);
    lcd_show_string(30, 110, 200, 16, 16, "KEY0:Malloc & WR & Show", RED);
    lcd_show_string(30, 130, 200, 16, 16, "KEY_UP:SRAMx KEY1:Free", RED);
    lcd_show_string(60, 160, 200, 16, 16, " SRAMIN ", BLUE);
    lcd_show_string(30, 176, 200, 16, 16, "SRAMIN   USED:", BLUE);
    lcd_show_string(30, 192, 200, 16, 16, "SRAMEX   USED:", BLUE);

    while (1)
    {
        key = key_scan(0);      /* 不支持连按 */
        switch (key)
        {
            case KEY0_PRES:     /* KEY0按下 */
                p = mymalloc(sramx, 2048);/*申请2K字节,并写入内容,显示在lcd屏幕上面 */
                if (p != NULL)
                {	  /* 向p写入一些内容 */
                    sprintf((char *)p, "Memory Malloc Test%03d", i);
/* 显示P的内容 */
                    lcd_show_string(30, 260, 209, 16, 16, (char *)p, BLUE); 
                }
                break;
            case KEY1_PRES:         	/* KEY1按下 */
                myfree(sramx, p);   	/* 释放内存 */
                p = 0;              		/* 指向空地址 */
                break;
            case WKUP_PRES:         	/* KEY UP按下 */
                sramx++;
                if (sramx > 1)sramx = 0;
                lcd_show_string(60, 160, 200, 16, 16,
(char *)SRAM_NAME_BUF[sramx], BLUE);
                break;
        }

        if (tp != p)
        {
            tp = p;
            sprintf((char *)paddr, "P Addr:0X%08X", (uint32_t)tp);
/* 显示p的地址 */
            lcd_show_string(30, 240, 209, 16, 16, (char *)paddr, BLUE);
            if (p)
            {   /* 显示P的内容 */
                lcd_show_string(30, 260, 280, 16, 16, (char *)p, BLUE); 
            }
            else 
            {
                lcd_fill(30, 260, 209, 296, WHITE); /* p=0,清除显示 */
            }
        }

        delay_ms(10);
        i++;
        if ((i % 20) == 0) 
        {
            memused = my_mem_perused(SRAMIN);
            sprintf((char *)paddr, "%d.%01d%%", memused / 10, memused % 10);
/* 显示内部内存使用率 */
            lcd_show_string(30 + 112, 176, 200, 16, 16, (char *)paddr, BLUE); 
            memused = my_mem_perused(SRAMEX);
            sprintf((char *)paddr, "%d.%01d%%", memused / 10, memused % 10);
    /* 显示外部内存使用率 */
            lcd_show_string(30 + 112, 192, 200, 16, 16, (char *)paddr, BLUE);
            LED0_TOGGLE();  /* LED0闪烁 */
        }
    }
}

该部分代码比较简单,主要是对mymalloc和myfree的应用。不过这里提醒大家,如果对一个指针进行多次内存申请,而之前的申请又没释放,那么将造成“内存泄露”,这是内存管理所不希望发生的,久而久之,可能导致无内存可用的情况!所以,在使用的时候,请大家一定记得,申请的内存在用完以后,一定要释放。
另外,本章希望利用USMART调试内存管理,所以在USMART里面添加了mymalloc和myfree两个函数,用于测试内存分配和内存释放。大家可以通过USMART自行测试。
48.4 下载验证
将程序下载到开发板后,可以看到LED0不停的闪烁,提示程序已经在运行了。LCD显示的内容如图48.4.1所示:
在这里插入图片描述

图48.4.1 内存管理实验测试图
可以看到,内存的使用率均为0%,说明还没有任何内存被使用。我们可以通过KEY_UP选择申请内存的位置:SRAIN为内部,SRAMEX为外部。此时我们选择从内部申请内存,按下KEY0,就可以看到申请了5%的一个内存块,同时看到下面提示了指针p所指向的地址(其实就是被分配到的内存地址)和内容。效果如图48.4.2所示。
KEY0键用来更新p的内容,更新后的内容将重新显示在LCD模块上。多按几次KEY0,可以看到内存使用率持续上升(注意比对p的值,可以发现是递减的,说明是从顶部开始分配内存!)。每次申请一个内存块后,可以通过按下KEY0释放本次申请的内存,如果我们每次申请完内存不再使用却不及时释放掉,再按KEY1将无法释放之前的内存了,当这样的情况重复了多次,就会造成“内存泄漏”。我们程序就是模拟这样一个情况,告诉大家在实际使用的时候去注意到这种做法的危险性,必须在编程时严格避免内存泄漏的情况发生。
在这里插入图片描述

图48.4.2 按下KEY0申请了部分内存
本章,我们还可以借助USMART,测试内存的分配和释放,有兴趣的朋友可以动手试试。如图48.4.2所示:
在这里插入图片描述

图48.4.2 USMART测试内存管理图
图中,我们先申请了4660字节的内存,然后得到申请到的内存首地址为:0x20009080,说明我们申请内存成功(如果不成功,则会收到0),然后释放内存的时候,参数是指针的地址,即执行:myfree(0x200097FC),就可以释放我们申请到的内存。其他情况,大家可以自行测试并分析。

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

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

相关文章

全志V3S嵌入式驱动开发(spi-nand image制作)

【 声明:版权所有,欢迎转载,请勿用于商业用途。 联系信箱:feixiaoxing @163.com】 上一篇文章,我们说到了spi-nor image的制作和输入。相比较spi-nor,spi-nand虽然在稳定性上面差一点,但是价格上面有很大的优势。举例来说,一般32M的spi-nor大约在6-7元左右,但…

Springboot链接Redis实现AOP防止重复提交

目录 安装redis Springboot链接Redis 1.创建springboot项目 如果spring boot启动报Error creating bean with name redisUtil/redisTemplate 2.新建application.yml配置 3.redis配置类-直接用 4.redis工具类-直接用 5.写Controller测试 6.启动、测试 整合AOP&#xff…

内网横线移动—WmiSmbCrackMapExecProxyChainsImpacket

内网横线移动—Wmi&Smb&CrackMapExec&ProxyChains&Impacket 1. 前置环境准备2. wmic介绍2.1. wmic操作演示2.1.1. 受控主机上线2.1.1.1. 内网存活探测2.1.1.2. 密码抓取 2.1.2. 横向移动2.1.2.1. 上传文件2.1.2.2. 文件上传目标主机2.1.2.3. 执行木马 2.2. wmi…

JAVA 安全-JWT 安全及预编译 CASE 注入等(40)

在各种语言脚本的环境下&#xff0c;也会产生一些新的漏洞&#xff0c;如果是java又能产生那些漏洞&#xff0c;思维导图里面常规漏洞之前都有&#xff1b; java的访问控制&#xff0c;jwt令牌&#xff08;php几乎没有&#xff09;组件安全&#xff0c;这些都是java特有的 #综…

C#核心知识回顾——3.继承构造、拆装箱、多态

1.继承中的构造函数&#xff1a; 特点&#xff1a; 当申明一个子类对象时 先执行父类的构造函数&#xff0c;再执行子类的构造函数注意&#xff01;&#xff01;&#xff1a; 1.父类的无参构造很重要 2.子类可以通过base关键字代表父类调用父类构造 public class Mot…

【单片机】STM32单片机,定时器的输入捕获,基于捕获的频率计,STM32F103

文章目录 简单介绍外部计数频率计TIM5 频率计 简单介绍 下面的定时器都具有输入捕获能力&#xff1a; 外部计数频率计 查看另一篇文章&#xff1a;https://qq742971636.blog.csdn.net/article/details/131471539 外部计数频率计的缺点&#xff1a;需要两个定时器配合&#x…

控制 显示、隐藏

1、使用 v-if < v-if"isShow"></> data(){ return:{ isShow:false } } if (concepts && concepts.length >0){ this.isShow nv.concepts.includes(snap) } 确认 数组中有某个 字段&#xff0c;用includes 有&…

Qt QGraphics导入背景图并绘制图形,画布移动、缩放、图形旋转等

前言 之前写过一篇博文《Qt鼠标拖动绘制基本几何图形》 &#xff0c;这是介绍使用QGraphic中利用鼠标事件实现基本几何图形的绘制&#xff0c;支持直线、矩形、圆形、椭圆&#xff0c;本次是在此基础上进行扩展&#xff0c;实现背景图导入&#xff0c;并在图片上进行几何图形绘…

现货白银行情实时行情与展望

现货白银作为低门槛、高收益的贵金属投资工具&#xff0c;因为交易时间自且没有涨跌幅限制而大受全球投资者追捧&#xff0c;它每天的实时行情走势中充满机会&#xff0c;投资者可结合技术和基本面分析手段&#xff0c;预测其未来的价格走势&#xff0c;在市场的波动中把握住获…

Go编写流量代理工具

目录 这是一个演示主要分为俩包&#xff1a;流程&#xff1a;逻辑&#xff1a;(端口随意&#xff0c;本地ssh为例)用法&#xff1a;文件地址&#xff1a;代码如下&#xff1a; 这是一个演示 代理本地HTTP服务 代理局域网SSH服务 其他的TCP服务没测试了 主要分为俩包&#x…

什么是DevOps? 什么是DORA?

1. 前言 对于搞云原生应用的同学&#xff0c;对于DevOps和DORA应该都不陌生。但对于传统应用程序开发的同学&#xff0c;经常被DevOps, Microservice, CICD, DORA这些新颖的名词搞得晕头转向。那么到底什么是DevOps? 什么是DORA呢&#xff1f; 2. 解析 2.1 DevOps DevOps并…

群晖NAS搭建WebDV服务手机ES文件浏览器远程访问

文章目录 1. 安装启用WebDAV2. 安装cpolar3. 配置公网访问地址4. 公网测试连接5. 固定连接公网地址 转载自cpolar极点云文章&#xff1a;群晖NAS搭建WebDAV服务手机ES文件浏览器远程访问 有时候我们想通过移动设备访问群晖NAS 中的文件,以满足特殊需求,我们在群辉中开启WebDav服…

支持刷机(OpenWrt)的路由器大全

2023年上半年最热门的刷机路由器当然是360T7、小米WR30U这两款&#xff0c;主要是性价比高&#xff0c;闲鱼100多搞定&#xff0c;支持刷OpenWrt、支持WiFi6&#xff0c;采用MTK798X系列处理器&#xff0c;性能强&#xff0c;轻松跑满千兆&#xff0c;如果你想追新&#xff0c;…

SpringMVC基础知识

一、SpringMVC 1. Spring与Web环境集成 1.1 ApplicationContext应用上下文获取方式 应用上下文对象是通过new ClasspathXmlApplicationContext(spring配置文件) 方式获取的&#xff0c;但是每次从容器中获得Bean时都要编写new ClasspathXmlApplicationContext(spring配置文件…

云捷|打破应用孤岛加速企业数字化转型

CBG云服务BU X 神州数码云基地 一、引言 从流程信息化到整体的数字化转型&#xff0c;对企业而言是一场深刻的升级再造。企业在决心开启数字化转型之路后&#xff0c;有近5成民营企业数字化转型采用标准化工具&#xff1b;关于“贵公司未来将在工作中增加哪些数字应用的使用…

强化学习从基础到进阶-案例与实践[4.2]:深度Q网络DQN-Cart pole游戏展示

【强化学习原理项目专栏】必看系列&#xff1a;单智能体、多智能体算法原理项目实战、相关技巧&#xff08;调参、画图等、趣味项目实现、学术应用项目实现 专栏详细介绍&#xff1a;【强化学习原理项目专栏】必看系列&#xff1a;单智能体、多智能体算法原理项目实战、相关技巧…

基于PaddleDetection fairmot目标跟踪 C++ 部署

1 源码下载 PaddleDetection 2 工程编译 参考&#xff1a;paddle 目标检测C部署流程 3 导出模型 python tools/export_model.py -c configs/mot/fairmot/fairmot_dla34_30e_576x320.yml --output_dir ./inference -o weightshttps://paddledet.bj.bcebos.com/models/mot/…

AutoSAR系列讲解(入门篇)4.7-BSW的Diagnostics功能

一、架构与术语解释 首先简单介绍以下诊断&#xff08;Diagnostics&#xff09;&#xff0c;由于百度百科中就有很好的解释&#xff0c;这里直接引用一下&#xff1a; 汽车诊断技术是凭借仪器设备对汽车进行性能测试和故障检查的方法和手段&#xff0c;它能够测试出汽车各项工作…

CMU 15-445 -- 存储篇 - 02

CMU 15-445 -- 存储篇 - 02 引言Database StorageDisk Manager 简介计算机存储体系为什么不使用 OS 自带的磁盘管理模块磁盘管理模块的核心问题在文件中表示数据库File StorageDatabase PagesHeap File OrganizationPage LayoutData LayoutTuple LayoutTuple Storage Data Repr…

Windows Update当前无法检查更新怎么办?

当进行Windows更新或升级时&#xff0c;可能会提示“Windows Update当前无法检查更新&#xff0c;因为未运行服务。您可能需要重新启动计算机”。而当重启也无法解决问题时&#xff0c;我们该怎么办呢&#xff1f;下面我们就来了解一下。 1、删除Software Distribution文件夹中…