SylixOS 对物理内存的配置在 BSP_T3/config.h 中,其详细描述如下:

这部分内容需要根据实际情况作出修改。在后面会给出修改结果。
SylixOS将物理内存划分成4个区域进行使用:内核代码区,内核数据区,DMA内存区和APP内存区。系统启动开启MMU后会将内核代码区和内核数据区的物理地址和虚拟地址建立对等映射的关系,也就是物理地址和虚拟地址是一样的;DMA内存区会在需要的时候建立映射,也是对等映射的关系;APP内存区也是在需要的时候建立映射,但是映射关系不一定是对等映射,APP区的虚拟地址是可以在bspMap.h中进行配置的。
使能MMU后,系统VMM组件接管的内存区是DMA和APP区,系统内核和内核数据区不归VMM组件管理:

通用内存区就是操作系统本身使用的内存空间,即内核空间(上图中的内核代码段和内核数据段),主要包括操作系统镜像、系统使用的内存堆和栈空间,他们物理地址和虚拟地址是完全相同的,因此可以看到,它们没有对应的虚拟页面。
VMM(Virtual Memory Management),即虚拟内存管理单元,以页面的方式管理除通用内存区外的所有物理内存,VMM还负责以页面的方式管理一片虚拟内存空间,并在需要的时候将虚拟内存页面映射到物理内存页面。虚拟页面和物理页面的大小是相同的,值为PAGESIZE(通常为4KB)
这几个区域的作用:
内核代码区:这段区域存放有系统的中断向量表和内核代码,使能MMU后这段区域无法写入。
内核数据区:这段区域是内核使用的数据区,包括内核栈、内核堆、全局数据等等。使能MMU后这段区域可读可写。
DMA内存区:有些外设控制器需要申请物理连续的内存,DMA内存区就是为这些控制器准备的。
APP内存区:SylixOS动态加载的应用程序、动态库和内核模块都是使用的这段区域。
在上述的4个区域中,内核代码区、内核数据区和APP内存区都是带Cache属性的,DMA内存区根据需要可以申请带Cache或者不带Cache属性的物理连续内存。
我们手中的T3开发板使用的内存是1GB大小,根据上述信息,我们可以将物理内存空间在config.h 中进行如下配置:
/*********************************************************************************************************
ROM RAM 相关配置
*********************************************************************************************************/
#define BSP_CFG_ROM_BASE (0x00000000)
#define BSP_CFG_ROM_SIZE (4 * 1024 * 1024) /* 0x0040 0000*/
#define BSP_CFG_RAM_BASE (0x40000000) /* 内存基址 这个地址是我们T3开发板的DRAM基地址 */
#define BSP_CFG_RAM_SIZE (1 * 1024 * 1024 * 1024) /* 内存大小 0x4000 0000 */
#define BSP_CFG_TEXT_SIZE (10 * 1024 * 1024) /* 内核代码段大小 0x00A0 0000*/
#define BSP_CFG_DATA_SIZE (50 * 1024 * 1024) /* 内核数据段大小 */
#define BSP_CFG_DMA_SIZE (128 * 1024 * 1024) /* DMA内存段大小 */
#define BSP_CFG_APP_SIZE (BSP_CFG_RAM_SIZE - BSP_CFG_TEXT_SIZE - \
BSP_CFG_DATA_SIZE - BSP_CFG_DMA_SIZE) /* 动态加载内存段大小 */
#define BSP_CFG_BOOT_STACK_SIZE (128 * 1024)
分别介绍一下:


从上面的代码可以看出,内核数据段的运行地址是紧挨在内核代码段之后的,内核代码段大小一般我们都是根据经验来分配一个合理的大小,也许实际并没有使用全部的空间,这样为了减少生成的bin文件大小,默认生成的bin文件中内核数据和内核实际使用的代码空间是紧挨着存放的。所以在实际运行时,需要将内核数据区的数据从加载地址复制到运行地址:

这个拷贝的操作就是在startup.S中的初始化DATA段,就是将内核的数据段从加载地址搬运到运行地址处。
这三个组件的初始化都是在bspInit.c中进行的。如下图所示,接下来一个一个函数进行实现

我们首先来看下FPU的初始化:
/*********************************************************************************************************
** 函数名称: halFpuInit
** 功能描述: 目标系统 FPU 浮点运算单元初始化
** 输 入 : NONE
** 输 出 : NONE
** 全局变量:
** 调用模块:
*********************************************************************************************************/
#if LW_CFG_CPU_FPU_EN > 0
static VOID halFpuInit (VOID)
{
API_KernelFpuInit(ARM_MACHINE_A7, ARM_FPU_VFPv4);
}
#endif /* LW_CFG_CACHE_EN > 0
可以看出来FPU的初始化很简单,只需要调用API_KernelFpuInit 即可。第一个参数表示当前CPU的架构,第二个参数表示当前SOC中FPU使用的类型,这两个参数根据芯片数据手册的信息然后使用内核提供好的宏填入就行了。



static VOID halCacheInit (VOID)
{
API_CacheLibInit(CACHE_COPYBACK, CACHE_COPYBACK, ARM_MACHINE_A7); /* 初始化 CACHE 系统 */
API_CacheEnable(INSTRUCTION_CACHE);
API_CacheEnable(DATA_CACHE); /* 使能 CACHE */
}
首先使用API_CacheLibInit 接口初始化了内核Cache组件:
这个函数定义在BASE_T3/libsylixos/SylixOS/kernel/cache/cache.h中

追到cache.c中可以看到这个函数实现了什么。

回到正题:
初始化完了内核Cache组件,接着使用了API_CacheEnable 接口使能了指令Cache和数据Cache,可以看出Cache的初始化还是比较简单的,因为内核已经为大部分arm架构封装好了Cache等部件的操作,我们只需要调用接口即可。
MMU的初始化需要做三部分工作,1. 在BSP_T3/SylixOS/bsp/bspMap.h 中设置映射表,2. 在BSP_T3/SylixOS/bsp/bspInit.c 初始化内核VMM组件,3. 设置MMU页表大小。
映射表是定义在bspMap.h 中,_G_physicalDesc 描述物理地址空间的关系,_G_virtualDesc 描述虚拟地址空间关系,VMM通过这两个表中定义的关系来管理物理地址和虚拟地址。
/*********************************************************************************************************
物理内存信息描述
注意:
TEXT, DATA, DMA 物理段 PHYD_ulPhyAddr 必须等于 PHYD_ulVirMap,
TEXT, DATA, VECTOR, BOOTSFR, DMA 物理段 PHYD_ulVirMap 区间不能与虚拟内存空间冲突.
*********************************************************************************************************/
typedef struct __lw_vmm_physical_desc {
addr_t PHYD_ulPhyAddr; /* 物理地址 (页对齐地址) */
addr_t PHYD_ulVirMap; /* 需要初始化的映射关系 */
size_t PHYD_stSize; /* 物理内存区长度 (页对齐长度) */
#define LW_PHYSICAL_MEM_TEXT 0 /* 内核代码段 */
#define LW_PHYSICAL_MEM_DATA 1 /* 内核数据段 (包括 HEAP) */
#define LW_PHYSICAL_MEM_VECTOR 2 /* 硬件向量表 */
#define LW_PHYSICAL_MEM_BOOTSFR 3 /* 启动时需要的特殊功能寄存器 */
#define LW_PHYSICAL_MEM_BUSPOOL 4 /* 总线地址池, 不进行提前映射 */
#define LW_PHYSICAL_MEM_DMA 5 /* DMA 物理内存, 不进行提前映射*/
#define LW_PHYSICAL_MEM_APP 6 /* 装载程序内存, 不进行提前映射*/
UINT32 PHYD_uiType; /* 物理内存区间类型 */
UINT32 PHYD_uiReserve[8];
} LW_MMU_PHYSICAL_DESC;
typedef LW_MMU_PHYSICAL_DESC *PLW_MMU_PHYSICAL_DESC;
/*********************************************************************************************************
| 物理内存段类型 | 含义 |
|---|---|
| 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 | 装载程序内存 |
注意:这里的地址和大小值必须是当前平台页对齐的值,系统启动过程中会检测是否对齐,如果不对齐则会启动失败。
/*********************************************************************************************************
内存分配关系图
+-----------------------+--------------------------------+
| 通用内存区 | VMM 管理区 |
| CACHE | |
+-----------------------+--------------------------------+
*********************************************************************************************************/
/*********************************************************************************************************
physical memory
*********************************************************************************************************/
#ifdef __BSPINIT_MAIN_FILE
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
},
/*
* TODO: 加入启动设备的寄存器映射, 参考代码如下:
*/
#if 0
{
0x20000000,
0x20000000,
LW_CFG_VMM_PAGE_SIZE,
LW_PHYSICAL_MEM_BOOTSFR
},
#endif
{ /* 结束 */
0,
0,
0,
0
}
};
在bspMap.h中的物理地址空间映射表配置
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
},
/*
* TODO: 加入启动设备的寄存器映射, 参考代码如下:
*/
{ /* UART0 ~ 4 */
0x01C28000, /* UART0的起始地址——物理地址 */
0x01C28000, /* UART0的虚拟地址 */
2 * LW_CFG_VMM_PAGE_SIZE, /* 需要映射的大小 */
LW_PHYSICAL_MEM_BOOTSFR
},
{ /* 结束 */
0,
0,
0,
0
}
};
由于我们已经配置了config.h中的地址。所以这里的断向量表、内核代码段、内核数据段、DMA缓冲区、APP通用内存我们不必进行修改。我们这里使能MMU之后,还需要串口进行输出信息,所以这里需要将串口寄存器地址进行静态映射。从数据手册查看uart的首地址。

由手册我们可得UART的物理地址是 0x01C28000
/*********************************************************************************************************
虚拟地址空间描述
注意:
虚拟内存空间编址不能与物理内存空间 TEXT, DATA, VECTOR, BOOTSFR, DMA 区间冲突,
DEV 属性内存分区最多一个.
一般情况下 APP 虚拟内存区间要远远大于物理 APP 内存大小, 同时大于 DEV 虚拟内存区间大小.
*********************************************************************************************************/
typedef struct __lw_mmu_virtual_desc {
addr_t VIRD_ulVirAddr; /* 虚拟空间地址 (页对齐地址) */
size_t VIRD_stSize; /* 虚拟空间长度 (页对齐长度) */
#define LW_VIRTUAL_MEM_APP 0 /* 装载程序虚拟内存区间 */
#define LW_VIRTUAL_MEM_DEV 1 /* 设备映射虚拟内存空间 */
UINT32 VIRD_uiType; /* 虚拟内存区间类型 */
UINT32 VIRD_uiReserve[8];
} LW_MMU_VIRTUAL_DESC;
typedef LW_MMU_VIRTUAL_DESC *PLW_MMU_VIRTUAL_DESC;
#endif /* __SYLIXOS_KERNEL */
/*********************************************************************************************************
| 虚拟内存段类型 | 含义 |
|---|---|
| LW_VIRTUAL_MEM_APP | 动态装载区(系统动态加载的程序、动态库和内核模块所使用的虚拟地址空间) |
| LW_VIRTUAL_MEM_DEV | IO 设备映射区 (系统调用API_VmmIoRemap 这类接口动态映射寄存器地址时所使用的虚拟地址空间) |
/*********************************************************************************************************
virtual memory
*********************************************************************************************************/
LW_MMU_VIRTUAL_DESC _G_virtualDesc[] = {
/*
* TODO: 加入虚拟地址空间的定义, 参考代码如下:
*/
#if 0
{ /* 应用程序虚拟空间 */
0x60000000,
((size_t)2 * LW_CFG_GB_SIZE),
LW_VIRTUAL_MEM_APP
},
{
0xe0000000, /* ioremap 空间 */
(256 * LW_CFG_MB_SIZE),
LW_VIRTUAL_MEM_DEV
},
#endif
{ /* 结束 */
0,
0,
0
}
};
在bspMap.h中的虚拟地址空间映射表配置:
LW_MMU_VIRTUAL_DESC _G_virtualDesc[] = {
/*
* TODO: 加入虚拟地址空间的定义, 参考代码如下:
*/
#if 1
{ /* 应用程序虚拟空间 */
0x80000000, /* 这个地址只要不和我们之前配置的物理地址有重合就行,而且从起始地址加上大小不能超过4GB*/
((size_t)1 * LW_CFG_GB_SIZE),
LW_VIRTUAL_MEM_APP
},
{
0xC0000000, /* ioremap 空间 */
(64 * LW_CFG_MB_SIZE),
LW_VIRTUAL_MEM_DEV
},
#endif
{ /* 结束 */
0,
0,
0
}
};

在配置好上述的两个映射表后,就可以回到我们的bspInit.c中使用API_VmmLibInit 这个接口来初始化VMM组件:

初始化内容如下:
/*********************************************************************************************************
** 函数名称: halVmmInit
** 功能描述: 初始化目标系统虚拟内存管理组件
** 输 入 : NONE
** 输 出 : NONE
** 全局变量:
** 调用模块:
*********************************************************************************************************/
#if LW_CFG_VMM_EN > 0
static VOID halVmmInit (VOID)
{
API_VmmLibInit(_G_physicalDesc, _G_virtualDesc, ARM_MACHINE_A7);
API_VmmMmuEnable();
}
#endif /* LW_CFG_VMM_EN > 0 */
API_VmmLibInit的前两个参数我们在2.3.1已经配置好了,第三个参数是T3开发板的架构我们也很熟悉了,这里就不再赘述。
内存管理单元(Memory Management Unit)简称 MMU,负责虚拟地址到物理地址的转换,并提供硬件机制的内存访问权限检查。在 MMU 开启之前,CPU 都是通过物理地址来访问内存;MMU 开启之后,CPU 都是通过虚拟地址来访问内存。如下图 所示:

虚拟地址转换为物理地址是通过 MMU 和页表(Page table)实现的。页表就是一段描述虚拟地址到物理地址转换关系及访问权限的内存区域,由一个个页表项组成。
SylixOS 采用的是二级页表映射,二级页表映射过程如图所示:

MMU正常工作需要使用页表,SylixOS内核使用的是二级页表机制。
第一级页表叫全局页目录(Page Global Directory),也可以叫做一级页表,页目录中单个条目就叫做页目录项,一个页目录项可以映射1MB的物理页,32位地址空间有4GB(2 ^ 32 =4GB)大小,所以整个页目录共有4096(4GB/1MB=4096)个页目录项。
第二级页表叫做二级页表,二级页表中单个条目就叫做页表项(Page Table Entry),一般arm32位都是用4KB页大小,也就是一个页表项可以映射4KB的物理页,一个二级页表可以映射1MB大小,所以一个二级页表中共有256(1MB/4KB=256)个页表项:

在SylixOS中,一级页表和二级页表所占的内存是在内核堆上的,也就是内核数据区空间,所以在配置config.h时,最好将内核数据区配置大点,否则有可能因为MMU页表申请不到内存空间而导致系统启动失败。
在一个实际的嵌入式项目中,并不会完全使用4GB的空间,那么我们就可以将二级页表的个数减少点来节省下内存给其他的内核模块或者应用使用,这是通过bspLib.c中的bspMmuPgdMaxNum 和bspMmuPteMaxNum 这两个接口实现的。



至此,我们的内存相关配置就差不多了。接下来进行系统调试信息打印接口(bspLib.c文件中的bspDebugMsg )实现。再后面就是我们的串口驱动开发、时钟驱动开发以及中断驱动开发。