内存管理
1.内存相关结构体
设计了E820结构体,Zone结构体,Page结构体,Slab结构体,SlabPool结构体,Memory结构体。
1.E820
struct E820{
unsigned long address;//该块内存的物理开始地址
unsigned long length;//该块内存的长度
unsigned int type;//该块内存的类型
}__attribute__((packed));
E820结构体保存的是一块物理内存的信息,该信息在loader阶段保存到物理地址0x7e00处,kernel的内存初始化阶段将会从0x7e00处读取所有的物理内存信息并赋值E820结构体。
表 type物理内存类型
类型值 | 描述 |
---|---|
01h | 可用物理内存 |
02h | 保留或无效值(包括ROM、设备内存) |
03h | ACPI的回收内存 |
04h | ACPINVS内存 |
其他值 | 未定义,保留使用 |
2.Page
struct Page
{
//区域属性
unsigned long zoneAttribute;//对应于上述的type类型
//页的开始物理地址
unsigned long physicsAddress;
//页的属性(0:未使用,1:内核使用,2:用户使用,3:Slab使用)
unsigned long attribute;
//保留
unsigned long reserve[2];
};
Page对应一个物理页的信息,KePOS操作系统设计的一个页大小为2M。
3.Zone
struct Zone{
//该区域的页位图映射
unsigned long * bitmap;
//区域管理的开始页位置
struct Page * pages;
//该区域管理的页总数
unsigned long length;
};
Zone结构体对应每一个类型的物理内存区域,总共有5中类型的区域(等于E820.type),所以内存结构体Memory会维护一个Zone[5]数组。
pages与length共同表示一个变长的数组Pages[length]。
4.SlabPool和slab
这两个结构体主要的功能是:分配和回收一块物理内存给内核代码使用,借鉴了linux中的设计与实现。
如果内核某个代码申请一块物理内存,则可以使用getMemoryBlock(unsigned long blockSize)获取一块不大于blockSize的物理内存,该函数会返回申请到的内存块的开始线性地址。相应的可以使用backMemoryBlock(unsigned long virtualAddress)归坏一块物理内存。
//代表一块特定大小(blockSize)的内存池对象
struct SlabPool{
unsigned long blockSize; //该slabPool课分配的最大blocksize
struct Slab * head; //指向第一个slab
struct Slab * now; //未使用
struct Slab * reserve; //未使用
unsigned long useCount; //当前slabPool已经使用了的内存块
unsigned long freeCount;//当前slabPool还空闲的内存块
};
struct Slab{
//链子(双向循环链表)
struct Slab * prev;
struct Slab * next;
//管理的页
unsigned long pageID;
//使用计数
unsigned long usingCount;
//空闲计数
unsigned long freeCount;
//对应的虚拟地址
unsigned long virtualAddress;
//区域的位图映射
unsigned long * bitMap;
//位图长度
unsigned long bitMapLength;
};
//KePOS支持的blockSize(32b-->4096b)
struct SlabPool temSlabPool[7] = {
{32,NULL,NULL,NULL,0,0},
{64,NULL,NULL,NULL,0,0},
{128,NULL,NULL,NULL,0,0},
{256,NULL,NULL,NULL,0,0},
{512,NULL,NULL,NULL,0,0},
{1024,NULL,NULL,NULL,0,0},
{4096,NULL,NULL,NULL,0,0},
};
每一个slap结构体保存到一个物理页的开始部分,然后管理该物理页的可用内存块。
初始时每一个slabpool的head为NULL,当需要分配该slabpool管理的一块内存时,会首先获取一个物理页,然后将该物理页的开始部分赋值一个slap结构体。
5.Memory
//保存在内核.bss段
struct Memory{
//管理的E820结构体
struct E820 e820[32];
unsigned int e820Length;
//管理的区域
struct Zone zone[5];
//SlabPooL
struct SlabPool slabPool[7];
};
该结构体只有一个全局变量memory,用于管理系统的各个内存结构体。
图 内存结构体关系图
2.内存地址
1.地址转换
地址转换具体就是在长模式中,把线性地址->物理地址。本系统只使用了GDT,IDT段表,和3级页表(即最后每一个物理页为2M)
2.线性地址划分
系统中内核占据线性地址0xffff800000000000->0xffffffffffffffff,在内核的内存代码运行完后,物理地址空间中的所有物理地址(a)都被映射为线性地址(a+0xffff800000000000),即内核代码可以访问任意物理地址。
用户代码使用线性地址0-0x00007fffffffffff。
3.CDT表
GDT_Table:
.quad 0x0000000000000000 /*0 NULL descriptor 00*/
.quad 0x0020980000000000 /*1 KERNEL Code 64-bit Segment 08*/
.quad 0x0000920000000000 /*2 KERNEL Data 64-bit Segment 10*/
.quad 0x0000f20000000000 /*3 USER Data 64-bit Segment 18*/
.quad 0x0020f80000000000 /*4 USER Code 64-bit Segment 20*/
.quad 0x00cf9a000000ffff /*5 KERNEL Code 32-bit Segment 28*/
.quad 0x00cf92000000ffff /*6 KERNEL Data 32-bit Segment 30*/
.fill 10,8,0 /*8 ~ 9 TSS (jmp one segment <7>) in long-mode 128-bit 40*/
GDT_END:
在GDT表中:
内核态代码和数据分别使用1,2;对应的段选择子为(08,10)
用户态代码和数据分别使用4,3;对应的段选择子为(20,18)
8~9为TSS的描述符
3.参考
AMD64 Architecture Programmer’s Manual Volume 2: System Programming)