VMM 子系统内存管理
VMM 子系统内存管理包含物理内存管理和虚拟内存管理,对内存管理的配置存放在“bspxxx/SylixOS/bsp/bspMap.h”中。
物理内存区域划分
在 SylixOS 中,物理内存被划分为 4 个区域。如下图所示:
物理内存空间配置
VMM 子系统管理的物理内存是 DMA 内存区和 APP 内存区,如下图所示。
SylixOS 使用 LW_MMU_PHYSICAL_DESC 数据结构来描述物理内存段映射信息,其详细描述如下:
#include <SylixOS.h>
typedef struct __lw_vmm_physical_desc {
addr_t PHYD_ulPhyAddr;
addr_t PHYD_ulVirMap;
size_t PHYD_stSize;
UINT32 PHYD_uiType;
UINT32 PHYD_uiReserve[8];
} LW_MMU_PHYSICAL_DESC;
typedef LW_MMU_PHYSICAL_DESC *PLW_MMU_PHYSICAL_DESC;
- PHYD_ulPhyAddr :页对齐的物理地址。
- PHYD_ulVirMap :需要映射的虚拟地址。
- PHYD_stSize :需要映射的大小。
- PHYD_uiType :物理内存段的类型,具体有 7 种,如下表所示:
物理内存段类型 | 含义 |
---|---|
LW_PHYSICAL_MEM_TEXT | 内核代码段 |
LW_PHYSICAL_MEM_DATA | 内核数据段 |
LW_PHYSICAL_MEM_VECTOR | 硬件向量表 |
LW_PHYSICAL_MEM_BOOTSFR | 特殊功能寄存器区域 |
LW_PHYSICAL_MEM_BUSPOOL | 总线地址池 |
LW_PHYSICAL_MEM_DMA | DMA 物理内存 |
LW_PHYSICAL_MEM_APP | 装载程序内存 |
- PHYD_uiReserve :预留区域,目前没用。
VMM 子系统通过一个全局数组来管理物理内存段,其详细描述如下:
LW_MMU_PHYSICAL_DESC _G_physicalDesc[] = {
{
/* 中断向量表 */
BSP_CFG_RAM_BASE,
0,
LW_CFG_VMM_PAGE_SIZE,
LW_PHYSICAL_MEM_VECTOR
},
{
/* 内核代码段 */
BSP_CFG_RAM_BASE,
BSP_CFG_RAM_BASE,
BSP_CFG_TEXT_SIZE,
LW_PHYSICAL_MEM_TEXT
},
{
/* 内核数据段 */
BSP_CFG_RAM_BASE + BSP_CFG_TEXT_SIZE,
BSP_CFG_RAM_BASE + BSP_CFG_TEXT_SIZE,
BSP_CFG_DATA_SIZE,
LW_PHYSICAL_MEM_DATA
},
{
/* DMA缓冲区 */
BSP_CFG_RAM_BASE + BSP_CFG_TEXT_SIZE + BSP_CFG_DATA_SIZE,
BSP_CFG_RAM_BASE + BSP_CFG_TEXT_SIZE + BSP_CFG_DATA_SIZE,
BSP_CFG_DMA_SIZE,
LW_PHYSICAL_MEM_DMA
},
{
/* APP通用内存 */
BSP_CFG_RAM_BASE + BSP_CFG_TEXT_SIZE +
BSP_CFG_DATA_SIZE + BSP_CFG_DMA_SIZE,
BSP_CFG_RAM_BASE + BSP_CFG_TEXT_SIZE +
BSP_CFG_DATA_SIZE + BSP_CFG_DMA_SIZE,
BSP_CFG_APP_SIZE,
LW_PHYSICAL_MEM_APP
},
/*
* 特殊寄存器区域
*/
{
/* BANK4 - CAN CONTROLER */
0x20000000,
0x20000000,
LW_CFG_VMM_PAGE_SIZE,
LW_PHYSICAL_MEM_BOOTSFR
},
……
{
/* 结束 */
0,
0,
0,
0
}
};
- 前 5 个内存段配置一般不需要修改。
- 特殊寄存器区域需要根据芯片手册进行修改添加。
虚拟内存空间配置
SylixOS 使用 LW_MMU_VIRTUAL_DESC 数据结构来描述虚拟内存段映射信息,其详细描述如下:
#include <SylixOS.h>
typedef struct __lw_mmu_virtual_desc {
addr_t VIRD_ulVirAddr;
size_t VIRD_stSize;
UINT32 VIRD_uiType;
UINT32 VIRD_uiReserve[8];
} LW_MMU_VIRTUAL_DESC;
typedef LW_MMU_VIRTUAL_DESC *PLW_MMU_VIRTUAL_DESC;
- VIRD_ulVirAddr :页对齐的虚拟地址。
- VIRD_stSize :虚拟空间的大小。
- VIRD_uiType :虚拟内存段的类型,具体有 2 种,如下表所示:
虚拟内存段类型 | 含义 |
---|---|
LW_VIRTUAL_MEM_APP | 动态装载区 |
LW_VIRTUAL_MEM_DEV | IO 设备映射区 |
- VIRD_uiReserve :预留区域,目前没用。
VMM 子系统通过一个全局数组来管理虚拟内存段,其详细描述如下:
LW_MMU_VIRTUAL_DESC _G_virtualDesc[] = {
{
/* 应用程序虚拟空间 */
0x60000000,
((size_t)2 * LW_CFG_GB_SIZE),
LW_VIRTUAL_MEM_APP
},
{
/* ioremap 空间 */
0xe0000000,
(256 * LW_CFG_MB_SIZE),
LW_VIRTUAL_MEM_DEV
},
{
/* 结束 */
0,
0,
0
}
};
应用程序和 IO 设备的虚拟内存空间地址不能与物理内存空间所映射的虚拟空间地址有冲突。
虚拟内存管理简介
在 SylixOS 中,所有动态加载的对象,如内核模块、动态链接库、应用程序所使用的内存都来自于虚拟内存空间。虚拟内存空间以页为单位进行管理,对象加载时,只会获得虚拟页面,只有在真正使用时,才会进行物理页面的分配。如下图所示:
页面分与回收
在多次分配和释放页面后,会产生许多不同大小的页面块,为有效管理,SylixOS 页分配的基本策略是找到最符合申请页面大小的连续页面(最佳适应),同时为了提高搜索速度,将被回收的连续页面空间以页面数量为关键参数进行 HASH 散列放置。如下图所示:
SylixOS 将一个由页面组成的空间称作区域(ZONE)。由一个 LW_VMM_ZONE 数据结构来管理空闲页面,其详细描述:
#include <SylixOS.h>
typedef struct __lw_vmm_zone {
ULONG ZONE_ulFreePage;
addr_t ZONE_ulAddr;
size_t ZONE_stSize;
UINT ZONE_uiAttr;
LW_VMM_FREEAREA ZONE_vmfa[LW_CFG_VMM_MAX_ORDER];
} LW_VMM_ZONE;
typedef LW_VMM_ZONE *PLW_VMM_ZONE;
- ZONE_ulFreePage :空闲页面的个数。
- ZONE_ulAddr :分区的起始地址。
- ZONE_stSize :分区的大小。
- ZONE_uiAttr :分区的属性,有两种,如下表所示。
分区属性类型 | 含义 |
---|---|
LW_ZONE_ATTR_NONE | 无属性 |
LW_ZONE_ATTR_DMA | 映射为 DMA 平板模式 |
- ZONE_vmfa :空闲页面 HASH 表。
分配页面时,根据需要分配的页面数量,快速定位到 HASH 表内具有相同散列值的链表头,再遍历以找到最佳大小的连续页面。如果找到的连续页面有剩余,则分裂该连续页面,并用同样的策略将剩余的空闲页面插入 HASH 表。
系统分配的页面空间会被用户全部使用,这不同于堆内存基于字节的分配管理。分配时一部分空间用于内部数据管理,当用户释放时可以通过地址直接获得相关数据块管理信息。由于以页(典型值为 4KB)为基本分配单位,如果每一次分配都多分配一个页进行相关信息管理,则会造成较大的内存浪费,因此,页面的回收采用特殊方式。
已经分配的连续页面由 LW_VMM_AREA 数据结构来管理,其详细描述如下:
#include <SylixOS.h>
typedef struct __vmm_area {
PLW_TREE_RB_ROOT AREA_ptrbrHash;
ULONG AREA_ulHashSize;
addr_t AREA_ulAreaAddr;
size_t AREA_stAreaSize;
} LW_VMM_AREA;
typedef LW_VMM_AREA *PLW_VMM_AREA;
- AREA_ptrbrHash :哈希红黑树表。
- AREA_ulHashSize :哈希表的大小。
- AREA_ulAreaAddr :空间起始地址。
- AREA_stAreaSize :空间大小。
对于页面空间,SylixOS 使用 LW_VMM_PAGE 数据结构来描述,其详细描述如下:
#include <SylixOS.h>
typedef struct __lw_vmm_page {
LW_LIST_LINE PAGE_lineFreeHash;
LW_LIST_LINE PAGE_lineManage;
LW_TREE_RB_NODE PAGE_trbnNode;
ULONG PAGE_ulCount;
addr_t PAGE_ulPageAddr;
INT PAGE_iPageType;
ULONG PAGE_ulFlags;
BOOL PAGE_bUsed;
PLW_VMM_ZONE PAGE_pvmzoneOwner;
union {
struct {
addr_t PPAGE_ulMapPageAddr;
INT PPAGE_iChange;
ULONG PPAGE_ulRef;
struct __lw_vmm_page *PPAGE_pvmpageReal;
} __phy_page_status;
struct {
PVOID VPAGE_pvAreaCb;
LW_LIST_LINE_HEADER *VPAGE_plinePhyLink;
} __vir_page_status;
} __lw_vmm_page_status;
} LW_VMM_PAGE;
typedef LW_VMM_PAGE *PLW_VMM_PAGE;
- PAGE_lineFreeHash :空闲页面控制块双向链表。
- PAGE_lineManage :已分配页面控制块双向链表。
- PAGE_trbnNode :红黑树节点。
- PAGE_ulCount :分配的页面个数。
- PAGE_ulPageAddr :分配的页面起始地址。
- PAGE_iPageType :页面类型,有 2 种,如下表所示:
页面类型 | 含义 |
---|---|
__VMM_PAGE_TYPE_PHYSICAL | 物理空间页面 |
__VMM_PAGE_TYPE_VIRTUAL | 虚拟空间页面 |
- PPAGE_ulMapPageAddr :页面被映射的虚拟地址。
- PPAGE_iChange :物理页面是否已经变化。
- PPAGE_ulRef :物理页面引用次数。
- PPAGE_pvmpageReal :指向真实的物理页面。
- VPAGE_pvAreaCb :指向缺页中断区域控制块。
- VPAGE_plinePhyLink :物理页面哈希链表。
释放页面时,为了从页面地址快速定位到对应的页面控制块,SylixOS 首先以该连续页面的起始地址为关键参数进行 HASH 散列,然后将具有相同散列值的页面控制块以红黑树方式进行管理,有效提高了页面回收的时间确定性。如下图所示:
VMM 内存基本操作
关于 VMM 内存操作的 API 位于“libsylixos/sylixos/kernel/vmm/vmm.h”和“libsylixos/sylixos/kernel/vmm/vmmio.h”中,这里列出常用的几个接口。
分配逻辑连续,物理可能不连续内存(APP 区域)
#include <SylixOS.h>
PVOID API_VmmMalloc(size_t stSize);
函数原型 API_VmmMalloc 分析:
- 函数成功返回虚拟内存地址,失败返回 LW_NULL 。
- 参数 stSize 是需要分配的内存字节数。
释放逻辑连续,物理可能不连续内存(APP 区域)
#include <SylixOS.h>
VOID API_VmmFree(PVOID pvVirtualMem);
函数原型 API_VmmFree 分析:
- 函数无返回值。
- 参数 pvVirtualMem 是需要释放的虚拟内存地址。
分配逻辑连续,物理连续内存(DMA 区域)
#include <SylixOS.h>
PVOID API_VmmDmaAlloc(size_t stSize);
函数原型 API_VmmDmaAlloc 分析:
- 函数成功返回物理内存地址,失败返回 LW_NULL 。
- 参数 stSize 是需要分配的内存字节数。
注意:
DMA 区分配的内存的虚拟地址和物理地址相同。
释放逻辑连续,物理连续内存(DMA 区域)
#include <SylixOS.h>
VOID API_VmmDmaFree(PVOID pvDmaMem);
函数原型 API_VmmDmaFree 分析:
- 函数无返回值。
- 参数 pvDmaMem 是需要释放的虚拟内存地址。
通过虚拟地址查询物理地址
#include <SylixOS.h>
ULONG API_VmmVirtualToPhysical(addr_t ulVirtualAddr, addr_t *pulPhysicalAddr);
函数原型 API_VmmVirtualToPhysical 分析:
- 成功通过 pulPhysicalAddr 保存物理内存地址,并返回 ERROR_NONE 。
- 失败返回错误码。
- 参数 ulVirtualAddr 是要查询的虚拟地址。
- 参数 pulPhysicalAddr 是保存查询结果(物理地址)。
只分配物理内存
#include <SylixOS.h>
PVOID API_VmmPhyAlloc(size_t stSize);
函数原型 API_VmmPhyAlloc 分析:
- 成功返回 物理内存地址 ,失败返回 LW_NULL 。
- 参数 stSize 是需要分配的内存字节数。
注意:
调用此接口只是分配出物理内存,不能直接使用,一般需要使用 API_VmmRemapArea 来映射虚拟地址和物理地址。
释放物理内存
#include <SylixOS.h>
VOID API_VmmPhyFree(PVOID pvPhyMem);
函数原型 API_VmmPhyFree 分析:
- 无返回值。
- 参数 pvPhyMem 是需要释放的物理内存地址。
映射虚拟内存和物理内存
#include <SylixOS.h>
ULONG API_VmmRemapArea(PVOID pvVirtualAddr,
PVOID pvPhysicalAddr,
size_t stSize,
ULONG ulFlag,
FUNCPTR pfuncFiller,
PVOID pvArg);
函数原型 API_VmmRemapArea 分析:
- 成功返回 ERROR_NONE ,失败返回其他错误码。
- 参数 pvVirtualAddr 是连续虚拟地址。
- 参数 pvPhysicalAddr 是连续物理地址。
- 参数 stSize 是映射长度,以字节为单位。
- 参数 ulFlag 是映射属性,有 5 种,如下表所示:
虚拟内存段类型 | 含义 |
---|---|
LW_VMM_FLAG_EXEC | 可执行属性 |
LW_VMM_FLAG_READ | 只读属性 |
LW_VMM_FLAG_RDWR | 读写属性 |
LW_VMM_FLAG_DMA | 平板属性 |
LW_VMM_FLAG_FAIL | 不允许访问属性 |
- 参数 pfuncFiller 是缺页中断填充函数(一般为 LW_NULL )。
- 参数 pvArg 是缺页中断填充函数首参数。
注意:
物理地址和虚拟地址都必须是 VMM 子系统管理范围内的地址,而且都必须是 4K 对齐的地址。
#include <SylixOS.h>
ULONG API_VmmMap(PVOID pvVirtualAddr,
PVOID pvPhysicalAddr,
size_t stSize,
ULONG ulFlag);
函数原型 API_VmmMap 分析:
- 参数 pvVirtualAddr 是连续虚拟地址。
- 参数 pvPhysicalAddr 是连续物理地址。
- 参数 stSize 是映射长度,以字节为单位。
- 参数 ulFlag 是映射属性,有 5 种,如上表所示。
注意:
虚拟地址不能出现在 BSP 配置的虚拟地址空间中,这样会影响内核 VMM 其他管理组件。
映射 IO 寄存器区域
#include <SylixOS.h>
PVOID API_VmmIoRemap(PVOID pvPhysicalAddr, size_t stSize);
函数原型 API_VmmIoRemap 分析:
- 成功返回映射后的虚拟地址,失败返回 NULL 。
- 参数 pvPhysicalAddr 是 IO 寄存器物理地址。
- 参数 stSize 是映射长度,以字节为单位。
注意:
映射的 IO 物理寄存器地址必须 4K 对齐。
取消 IO 寄存器区域映射
#include <SylixOS.h>
VOID API_VmmIoUnmap(PVOID pvVirtualAddr);
函数原型 API_VmmIoUnmap 分析:
- 无返回值。
- 参数 pvVirtualAddr 是 IO 寄存器区域映射后的虚拟地址。