所有的硬件驱动程序已经实现,硬件功能已经验证。但是每一个硬件都是单独测试的,并不完美。接下来,我们需要整合和改进每个驱动程序。在集成之前,需要做一些基础工作。其中之一就是实现内存管理。什么是内存管理?为什么是内存管理?我们已经对程序中的变量有了大致的了解。现在让我们回顾一下:局部变量和全局变量。
局部变量在进入函数时从堆栈空间分配,在退出函数前释放。全局变量总是在整个程序中使用。RAM空间是在程序编译时分配的。
还有第三个变量吗?你可以说没有.但是从生命周期的角度来看,有:一个变量在多个函数中使用,但是在整个程序运行过程中没有使用。或者:一个变量,使用一段时间,不是整个程序生命周期都在使用,但是使用这个变量的函数会退出然后重新进入(静态定义的局部变量相当于全局变量)。
如果不使用动态内存管理,这样的变量只能定义为全局变量。如果把这些变量定义为指针,在要使用的时候通过内存管理来分配,用完后释放,这叫动态分配。举个实际例子:
一个设备有三种通信方式:串口、USB、网络,每种通信方式在通信过程中都需要1K RAM。经过分析,三种通信方式不会同时使用。然后,如果你不使用动态内存,你需要3K变量。如果用内存管理动态分配,只需要1K内存。(这只是一个例子。如果一个简单的系统确定三种方法不同时使用,可以直接重用内存。)
通信模式只是一个例子。事实上,并不是一个系统中的所有设备都会被用到。如果使用动态内存管理,RAM的峰值量会大大降低。
内存管理方案
不要发明轮子,优化轮胎就好。
内存管理是编程领域的一个大话题,有很多经典的方案。很多人也在尝试写新的计划。内存分配模块是基于KR C的例子,然后进行了优化。谁是KR?写《C程序设计语言》的两个家伙。如果没有这本书就太可惜了。本书《实例--存储分配程序》第8.7章介绍了一种基本的存储分配方法。代码见alloc.c,整个代码只有120行,结构很漂亮。
KR内存管理方案分析
我们用代码来分析一下这个内存分配方案。代码在wujiqueUtilitiesalloc文件夹中。
记忆分析
初始化
在malloc函数中,如果第一次被调用,内存链表将被初始化。代码最初通过获取堆地址在堆上建立内存池。让我们用一种更直观的方式来定义数组。内存建立后的内存视图如下:
内存分配的最小单位是:
typedef struct ALLOC _ HDR { struct { struct ALLOC _ HDR * ptr;无符号int大小;} s;无符号整数对齐;无符号整数填充;} ALLOC _ HDR;
这也是内存管理结构。在32位ARM系统上,这个结构是16字节。
首次分配
每次分配都是在可以分配的空间末端切一块。切割的大小是16字节的倍数,它将比所需的内存多一个头。释放内存时需要这个头。这一块,也就是内存管理的开销。
发行版发布后
经过多次分配释放,内存可能是这样的:绿色是两个不连续的空闲块,黄色是分配的块。分配的块不再在内存链表中。
劣势
总的来说,上面的代码可以满足要求。但是,它有以下缺陷:
缺点1:容易碎片化
第一种适配方法用于分配,即找到大于或等于待分配内存的空闲块,立即分配。这种方式的优点是速度更快,缺点是容易将内存碎片化,很多大块内存在分配的时候被切割成小块。多次分配后,可能会出现以下情况:
空闲内存总量还是10K,但是分散在10块,没有大内存块,所以再次申请2K内存会失败。如果我们对时间不是那么敏感,可以用最合适的方法,就是遍历空闲链表,寻找最合适的内存(容量最小大于要分配内存的空闲块),以减少大内存被撕碎的概率。需要注意的是,最合适的方法,除了增加分配时间,不会减少内存碎片的数量,只会增加空闲内存的浓度。假设经过多次分配,总的空闲空间量还是10K,也是分散在10个空闲块中,但是这10个空闲块中,会有5K的组块,申请2K的时候就可以申请2K内存。
缺点2:内存消耗
内存分配方案使用一个结构,每次分配的最小单位是16字节的结构大小。
typedef struct ALLOC _ HDR { struct { struct ALLOC _ HDR * ptr;无符号int大小;} s;无符号整数对齐;无符号整数填充;} ALLOC _ HDR;
一个分配至少有两个结构(一个结构用于管理分配的内存,另一个结构用作应用程序内存),32个字节。如果代码有大量的小内存应用,例如100个8字节的应用。
所需内存:100X8=800字节,实际消耗内存100X32=3200字节,利用率只有800/3200=25%。
如果内存分配只有25%,对于小内存嵌入式设备来说是致命的方案缺陷。
怎么解决?可以参考LINUX的内存分配方案SLAB。在LINUX中,很多模块需要申请固定大小的内存(比如节点结构)。为了加快分配速度,系统会使用malloc从一个大内存池中申请一批节点结构大小的内存作为slab内存池。需要分配节点结构时,直接从slab内存池申请。同样,内存分配也可以优化如下:当需要小内存时,从大内存池中分配一个大内存,比如512,用新的小内存分配算法进行管理。当512用完时,从大内存池中申请第二块512字节的大内存。当释放小内存时,判断小内存池是否为空,如果为空,则将小内存池释放回大内存池。那么如何管理这个小内存池呢?
缺点3:分配的内存没有被管理。
内存分配不管理分配的内存。我们可以用统一的方式管理分配的内存:
1原结构存在于已分配内存的头中,所有已分配内存通过ptr指针连接到已分配链表。2使用未使用的align和pad成员记录分配时间和分配对象(记录哪个驱动程序应用程序内存)。
通过上面的优化,你可以统计已经分配了多少内存,还剩下多少空闲内存,哪个模块申请了最多的内存等数据。
使用
1将代码中的所有free改为wjq_free,malloc改为wjq_malloc。
串口缓冲区空闲,使用malloc.fatfs的syscall.c,使用lwip的mem.h。
2修改启动代码,让堆栈变小。没有Malloc库,堆可能完全没有必要。栈,还是要保留的,但是不需要那么大,如果函数使用较大的局部变量,改成动态应用。
堆栈大小EQU0x00002000
区域堆栈,NOINIT,READWRITE,ALIGN=3 STACK _ Mem SPACE STACK _ Size _ _ initial _ sp
;《h》堆配置;《o》堆大小(字节)《0x0-0xFFFFFFFF:8》;《/h》
堆大小EQU0x00000010
AREA HEAP,NOINIT,READWRITE,ALIGN=3__heap_baseHeap_Mem空间Heap_Size__heap_limit
3内存池80K,但是无法编译。
链接。Objectswujique.axf:错误:L6406E:执行区域中没有空格。任何匹配dev _触摸屏. o(。bss).Objectswujique.axf:错误:L6406E:执行区域中没有空格。匹配mcu_uart.o(。bss).Objectswujique.axf:错误:L6406E:执行区域中没有空格。匹配etharp.o(。bss).Objectswujique.axf:错误:L6406E:执行区域中没有空格。匹配mcu_can.o(。bss).Objectswujique.axf:错误:L6406E:执行区域中没有空格。任何匹配netconf.o(。bss)。先把内存池改小,编译通过之后,分析地图文件,用了较多全局变量的统统改小或者改为动态申请。分析地图文件,还可以检查还有没有使用库里面的马洛克。代码(包括数据)RO数据辐射武器(radiation weapon的缩写)数据后方地带数据调试对象名称124 32 0 4 40976 1658分配。16 0 0 0 0 2474年国防部长。o 96 34 8640 4 0 1377 dev _ DAC声音。o 300 36 00 00 2751 dev _ esp 8266。o 204 38 0 1 0 1446 dev _ key。o 436 98 0 10 16 3648 dev _ touch键。o 310 18 0 0 0 35864以太网中频。0 0 0 0 0 3820 inet。2022年inet _ chksum。0 0 0 0 0 4163初始化。o 168 4 0 20 0 4763 IP。o 0 0 0 4 0 0 6463 IP _ addr。o 386 4 0 0 4118 IP _ frag。o 264 38 0 8 16 3833399 main。o 84 8 0单片机_ I2C。o 28 8 0 1 0 630 MCU _ i2s。o 336 92 0 0 0 2689 MCU _ RTC。o 430 86 0 1 0 4396单片机_定时器。o 1564 82 0 328 9072 MCU _ UART。o 504 20 12 0 4510 mem。o 56 10 0 0 9463 3250 memp。o 120 14 0 0 0 0 1651杂项o
分配内存池dev _触摸屏。o触摸屏缓冲dual_func_demo.o USB,应该能优化记忆什么鬼?又一个内存池?应该是要优化掉startup_stm32f40_41xxx.o启动代码,是栈跟堆用的拉姆。
由于编译器的优化,项目没用到的代码没有编译进来,上面的地图数据并不完整。等后面我们做完全部测试程序,所有用到的代码都会参与连接,到时还需要优化一次。
总结
内存管理暂时到此,等后面所有功能都完成后,再进行一次优化。如果对内存分配时间有更高要求,可使用伙伴内存分配法林恩。
标签:内存分配代码