嵌入式系统之内存管理

C语言为变量定义5种存储类,分别是自动(auto)、寄存器(register)、具有外部链接的静态(extern)、具有内部链接的静态(文件内static)、具有代码块作用域的静态(代码块static)。不同的存储类决定了作用域和存储期。

那么存储类和内存之间的关系是怎样的?

从存储期看分为静态、自动、分配。

静态存储(static)在编译时已经确定,整个程序运行期存在;变量(auto、register)在代码块运行时产生,退出时清理;动态内存分配在调用malloc或相关函数时产生,在free时释放。因为分配和释放不是线性的,这就导致了内存碎片。

从作用域看决定哪一部分程序可以访问哪个数据。

在嵌入式中内存就是以堆和栈的形式存在。栈由系统自动分配释放,堆由开发者分配和释放。

数据结构场景下,堆与栈表示两种常用的数据结构。

堆内存管理

C语言分配内存的声明在stdlib.h中,主要有四个函数:

malloc

calloc

realloc

free

无论哪个函数申请内存,结束后都要使用free释放。当然,如果是Linux这种系统,在分配内存后,程序运行期间一直执行,不free也行,当进程终止时,其占用的内存都会返还给操作系统。可以看到很多示例代码是这么做的。

关于堆内存的管理,不同嵌入式平台,不同的操作系统都有一定区别。

  • 在裸机环境下,特别是以stm32位代表的平台,使用的keil编译器实现的是简化版的C标准库,叫MicroLIB库。对于大块内存的分配,建议使用全局数组,不要多次去申请和释放。
  • 在ucos中已经有了基本的内存管理,使用ucos提供的接口函数去申请和释放堆内存。
  • 在Linux中内存管理就相当完善了,一般使用全局数据就可,如果是很大的内存块(如128KB以上),一般通过mmap系统调用来映射,使用完后ummap释放内存。

一般嵌入式系统中会这么划分:

  1. 栈(stack): 由编译器自动分配释放,存放函数的参数值、局部变量的值、返回地址等,其操作方式类似于数据结构中的栈。
  2. 堆(heap):一般由程序员动态分配(调用 mallo0函数) 和释放(调用 free0函数),若程序员不释放,程序结束时可能由操作系统回收。
  3. 数据段(data): 存放的是全局变量、静态变量、常数。根据存放的数据,数据段又可以分成普通数据段(包括可读可写/只读数据段,存放静态初始化的全局变量或常量)、BSS 数据段(存放未初始化的全局变量)。
  4. 代码段 (code): 用于存放程序代码。

所以一般动态申请的就是堆内存。当使用malloc分配内存时,会额外分配几个字节来存放记录这个内存大小。malloc分配的内存需要进行初始化,一般使用memset来初始化,calloc会将已分配的内存初始化为0。

Unix系统还提供了alloca来分配内存,不过不是从堆上申请,二是通过增加栈帧的大小从堆栈上分配。不需要free来释放内存。

Linux内存管理

Linux的内存管理构建在MMU(Memory Management Unit,内存管理单元),用于在CPU和内存之间实现虚拟内存管理。其主要功能是将虚拟地址转换为物理地址,同时提供访问权限的控制和缓存管理等功能。

在内存管理方面,MMU可以通过页面表(Page Table)实现虚拟内存管理。页面表是一种数据结构,记录了每个虚拟页面和其对应的物理页面之间的映射关系。当CPU发出一个虚拟地址时,MMU会通过页面表查找并将其转换为对应的物理地址。

Linux采用虚拟内存管理技术,利用虚拟内存技术让每个进程都有4GB 互不干涉的虚拟地址空间。

进程初始化分配和操作的都是基于这个「虚拟地址」,只有当进程需要实际访问内存资源的时候才会建立虚拟地址和物理地址的映射,调入物理内存页。

虚拟地址的好处

  • 避免用户直接访问物理内存地址,防止一些破坏性操作,保护操作系统
  • 每个进程都被分配了4GB的虚拟内存,用户程序可使用比实际物理内存更大的地址空间

4GB 的进程虚拟地址空间被分成两部分:3GB用户空间+1GB内核空间。

在Linux系统malloc 用于申请用户空间的虚拟内存,当申请小于 128KB 小内存的时,malloc使用 sbrk或brk 分配内存;当申请大于 128KB 的内存时,使用 mmap 函数申请内存;

你可能感兴趣的:(嵌入式,arm开发,mcu)