从本篇开始,我们分析下鸿蒙轻内核A核的内存管理部分,包括物理内存、虚拟内存、虚拟映射等部分。物理内存(Physical memory)是指通过物理内存条而获得的内存空间,相对应的概念是虚拟内存(Virtual memory)。虚拟内存使得应用进程认为它拥有一个连续完整的内存地址空间,而通常是通过虚拟内存和物理内存的映射对应着多个物理内存页。本文我们先来熟悉下OpenHarmony
鸿蒙轻内核提供的物理内存(Physical memory)管理模块。
本文中所涉及的源码,以OpenHarmony LiteOS-A
内核为例,均可以在开源站点 https://gitee.com/openharmony/kernel_liteos_a 获取。如果涉及开发板,则默认以hispark_taurus
为例。
我们首先了解了物理内存管理的结构体,接着阅读了物理内存如何初始化,然后分析了物理内存的申请、释放和查询等操作接口的源代码。
1、物理内存结构体介绍
1.1、物理内存页LosVmPage
鸿蒙轻内核A核的物理内存采用了段页式管理,每个物理内存段被分割为物理内存页。在头文件kernel/base/include/los_vm_page.h
中定义了物理内存页结构体,以及内存页数组g_vmPageArray
及数组大小g_vmPageArraySize
。物理内存页结构体LosVmPage
可以和物理内存页一一对应,也可以对应多个连续的内存页,此时使用nPages
指定内存页的数量。
typedef struct VmPage {
LOS_DL_LIST node; /**< 物理内存页节点,挂在VmFreeList空闲内存页链表上 */
PADDR_T physAddr; /**< 物理内存页内存开始地址*/
Atomic refCounts; /**< 物理内存页引用计数 */
UINT32 flags; /**< 物理内存页标记 */
UINT8 order; /**< 物理内存页所在的链表数组的索引,总共有9个链表 */
UINT8 segID; /**< 物理内存页所在的物理内存段的编号 */
UINT16 nPages; /**< 连续物理内存页的数量 */
} LosVmPage;
extern LosVmPage *g_vmPageArray;
extern size_t g_vmPageArraySize;
在文件kernel\base\include\los_vm_common.h
中定义了内存页的大小、掩码和逻辑位移值,可以看出每个内存页的大小为4KiB。
#ifndef PAGE_SIZE
#define PAGE_SIZE (0x1000U)
#endif
#define PAGE_MASK (~(PAGE_SIZE - 1))
#define PAGE_SHIFT (12)
1.2、物理内存段LosVmPhysSeg
在文件kernel/base/include/los_vm_phys.h
中定义了物理内存段LosVmPhysSeg
等几个结构体。该文件的部分代码如下所示。⑴处的宏是物理内存伙伴算法中空闲内存页节点链表数组的大小,VM_PHYS_SEG_MAX
表示系统支持的物理内存段的数量。⑵处的结构体用于伙伴算法中空闲内存页节点链表数组的元素类型,除了记录双向链表,还维护链表上节点数量。⑶就是我们要介绍的物理内存段,包含开始地址,大小,内存页基地址,空闲内存页节点链表数组,LRU链表数组等成员。
⑴ #define VM_LIST_ORDER_MAX 9
#define VM_PHYS_SEG_MAX 32
⑵ struct VmFreeList {
LOS_DL_LIST node; // 空闲物理内存页节点
UINT32 listCnt; // 空闲物理内存页节点数量
};
⑶ typedef struct VmPhysSeg {
PADDR_T start; /* 物理内存段的开始地址 */
size_t size; /* 物理内存段的大小,bytes */
LosVmPage *pageBase; /* 物理内存段第一个物理内存页结构体地址 */
SPIN_LOCK_S freeListLock; /* 伙伴算法双向链表自旋锁 */
struct VmFreeList freeList[VM_LIST_ORDER_MAX]; /* 空闲物理内存页的伙伴双向链表 */
SPIN_LOCK_S lruLock; /* LRU双向链表自旋锁 */
size_t lruSize[VM_NR_LRU_LISTS]; /* LRU大小 */
LOS_DL_LIST lruList[VM_NR_LRU_LISTS];/* LRU双向链表 */
} LosVmPhysSeg;
struct VmPhysArea {
PADDR_T start; // 物理内存区开始地址
size_t size; // 物理内存区大小
};
在kernel/base/vm/los_vm_phys.c
文件中定义了物理内存区数组g_physArea[]
,如下代码所示,其中SYS_MEM_BASE
为DDR_MEM_ADDR
的宏名称,DDR_MEM_ADDR
和SYS_MEM_SIZE_DEFAULT
定义在文件./device/hisilicon/hispark_taurus/sdk_liteos/board/target_config.h
中,表示开发板相关的物理内存地址和大小。
STATIC struct VmPhysArea g_physArea[] = {
{
.start = SYS_MEM_BASE,
.size = SYS_MEM_SIZE_DEFAULT,
},
};
看下物理内存区VmPhysArea
和物理内存段的LosVmPhysSeg
区别,前者信息教少,主要记录开始地址和大小,为一块物理内存的最简单描述;后者除了物理内存块开始地址和大小,还维护物理页开始地址,空闲物理页伙伴链表,LRU链表,相应的自旋锁等信息。
上面提到了伙伴算法,先看下伙伴算法的示意图,如下。每个物理内存段都分割为一个一个的内存页,空闲的内存页挂载在空闲内存页节点链表上。共有9个空闲内存页节点链表,这些链表组成链表数组。第一个链表上的内存页节点大小为1个内存页,第二个链表上的内存页节点大小为2个内存页,第三个链表上的内存页节点大小为4个内存页,依次下去,第9个链表上的内存页节点大小为2^8个内存页。申请内存、释放内存时会操作这些空闲内存页节点链表,后文详细分析。
1.3、物理内存伙伴位图
上文提到伙伴算法,还需要了解下伙伴位图。在伙伴算法中,每个链表的索引都对应一个位图。 位图的某位对应于两个伙伴块,为1就表示其中一块忙,为0表示两块都闲或都在使用 。系统每次分配和回收伙伴块时都要对它们的伙伴位 跟1进行异或运算 。所谓异或是指刚开始时,两个伙伴块都空闲,它们的伙伴位为0,如果其中一块被使用,异或后得1;如果另一块也被使用,异或后得0;如果前面一块回收了异或后得1;如果另一块也回收了异或后得0。位图用于在释放内存页块时,判断两块内存是否属于地址连续的伙伴内存块。
在文件