获取物理内存信息

之前再Loader阶段通过BIOS中断服务程序int 0x15; ax=0xe820,包保存到物理地址0x7e00。此处存储的物理地址空间信息存有若干组,描述计算机平台的地址空间划分情况,数据量回根据当前主板硬件配置和物理内存容量而定,每条物理地址空间信息占用20B,详细定义如下:

1
2
3
4
5
6
7
struct Memory_E820_Formate {
unsigned int address1;
unsigned int address2;
unsigned int length1;
unsigned int length2;
unsigned int type;
};

该结构体格式化物理地址0x7e00处的数据。还必须经过页表映射后才能使用,转换后的线性地址是0xffff800000007e00

显示内存信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
void memory_init() {
int i, j;
unsigned long total_memory;
struct Memory_E820_Formate *p = NULL;

p = (struct Memory_E820_Formate *)0xffff800000007e00;
for (i = 0; i < 32; i++) {
color_printk(ORANGE, BLACK,
"Adress:%#010x, %08x\tLength:%#010x,%08x\tType:%010x\n",
p->address2, p->address1, p->length2, p->length1, p->type);
unsigned long tmp = 0;
if (p->type == 1) {
tmp = p->length2;
total_memory += p->length1;
total_memory += tmp << 32;
}

p++;
if (p->type > 4) {
break;
}
}
color_printk(ORANGE, BLACK, "OS can used total ram: %018lx\n", total_memory);
}

将指针变量p指向线性地址0xffff800000007e00处,通过32次循环逐条显示地址空间的分布信息。其type值不会大于4,如果出现了大于4的情况,则是程序程序出现了脏数据,就跳出循环。

显示结果如下:

Screenshot_20220730_180936

可以看出可用的物理内存空间(type=1)由两部分组成,一部分是容量为0x9f00的段,另一部分是容量为0x7fef0000的段,可以计算处可用的物理总内存容量为0x9f000+0x7fef0000=7ff8f000 B大约是2047.55MB。与Bochs的内存设置一致:

1
memory: host=2048M, guest=2048M

计算可用物理内存页数

可用物理地址页数通常间接描述了操作系统可以使用的物理内存数,这些页必须按照页大小进行物理地址对齐。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 8 bytes per cell
#define PTRS_PER_PAGE 512

#define PAGE_OFFSET ((unsigned long)0xffff800000000000)

#define PAGE_GDT_SHIFT 39
#define PAGE_1G_SHIFT 30
#define PAGE_2M_SHIFT 21
#define PAGE_4K_SHIFT 12

#define PAGE_2M_SIZE (1UL << PAGE_2M_SHIFT)
#define PAGE_4K_SIZE (1UL << PAGE_4K_SHIFT)

#define PAGE_2M_MASK (~(PAGE_2M_SIZE - 1))
#define PAGE_4K_MASK (~(PAGE_4K_SIZE - 1))

#define PAGE_2M_ALIGN(addr) \
(((unsigned long)(addr) + PAGE_2M_SIZE - 1) & PAGE_2M_MASK)
#define PAGE_4K_ALIGN(addr) \
(((unsigned long)(addr) + PAGE_4K_SIZE - 1) & PAGE_4K_MASK)

#define VIRT_TO_PHY(addr) ((unsigned long)(addr)-PAGE_OFFSET)
#define PHY_TO_VIRT(addr) \
((unsigned long *)((unsigned long)(addr) + PAGE_OFFSET))

PTRS_PER_PAGE代表页表项个数,在64位模式下每个页表项占用字节数由原来的4个字节扩展为8个字节,每个页表大小为4KB,因此页表项个数为4KB/8B=512。

PAGE_OFFSET代表内核的起始线性地址,该地址位于物理地址0处。

PAGE_4K_SHIFT代表$2^{PAGE_4K_SHIFT}$B=4KB,同理$2^{PAGE_2M_SHIFT}$B=2MB、$2^{PAGE_1G_SHIFT}$B=1GB,以此类推,它们将64位模式下的每种页表项代表的物理页容量都表示出来。

PAGE_2M_SIZE代表2MB页的容量,展开后将1向左移动PAGE_2M_SHIFT位。

PAGE_2M_MASK是2MB数值的屏蔽码,通常用于屏蔽低于2MB的数值。

PAGE_2M_ALIGN(addr)的作用是将参数addr按2MB页的上边界对齐。

VIRT_TO_PHY(addr)用于将内核层虚拟地址转换成物理地址。PHY_TO_VIRT(addr)的功能恰好相反。

定义全局结构体struct global_memory_descriptor用来保存有关内存信息:

1
2
3
4
5
6
7
8
9
10
11
12
struct E820 {
unsigned long address;
unsigned long length;
unsigned int type;
} __attribute__((packed));

struct global_memory_descriptor {
struct E820 e820[32];
unsigned long e820_length;
};

extern struct global_memory_descriptor memory_management_struct;

__attribute__((packed))修饰该结构体不会对齐空间,改用紧凑格式,这样才能解析内存空间的分布信息。

全局变量初始化:

1
struct global_memory_descriptor memory_management_struct = {{0}, 0};

获取内存信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
p = (struct E820 *)0xffff800000007e00;
for (i = 0; i < 32; i++) {
color_printk(ORANGE, BLACK,
"Address:%#018lx\tLength:%#018lx\tType:%#010x\n", p->address,
p->length, p->type);
if (p->type == 1) {
total_memory += p->length;
}

memory_management_struct.e820[i].address += p->address;
memory_management_struct.e820[i].length += p->length;
memory_management_struct.e820[i].type += p->type;
memory_management_struct.e820_length = i;

p++;
if (p->type > 4) {
break;
}
}
color_printk(ORANGE, BLACK, "OS can used total ram: %018lx\n", total_memory);

将内存信息都保存到全局变量memory_management_struct

然后对E820结构体数组中可用的内存段进行2MB物理页边界对齐,并统计出可用物理页的总量:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
total_memory = 0;
for (i = 0; i <= memory_management_struct.e820_length; i++) {
unsigned long start, end;
if (memory_management_struct.e820[i].type != 1) {
continue;
}
start = PAGE_2M_ALIGN(memory_management_struct.e820[i].address);
end = ((memory_management_struct.e820[i].address +
memory_management_struct.e820[i].length) >>
PAGE_2M_SHIFT)
<< PAGE_2M_SHIFT;
if (end <= start) {
continue;
}
total_memory += (end - start) >> PAGE_2M_SHIFT;
}

color_printk(ORANGE, BLACK, "OS can used total 2M PAGEs: %#010x=%010d\n",
total_memory, total_memory);

使用宏函数PAGE_2M_ALIGN将这些段的结束地址按2MB页的上边界对齐,经过对齐处理后的地址才是段的有效内存起始地址。这些段结束地址是由段的起始地址和段长度相加,随后将计算结果用移位的方式按2MB页的下边界对齐,也可以使用之前定义的宏常量PAGE_2M_MASK进行页的下边界对齐操作。如果计算后的起始地址小于等于计算后的结束地址,则视这个段为有效内存段,进而计算可用的物理页数量,并在屏幕上打印可用的物理页总数量。

虚拟机可用内存页数量为1022,运行结果如下:

Screenshot_20220730_225903

分配可用的物理内存页

物理页管理结构的定义和初始化

物理内存描述定义

为了汇总可用物理内存信息并方便以后管理,现在特将整个内存空间(通过E820功能返回的各个内存段信息,包括RAM空间、ROM空间、保留空间等),按2MB大小的物理内存页进行分割和对齐。分割后的每个物理内存页由一个struct page结构体负责管理,然后使用区域空间结构体struct zone代表各个可用物理内存区域(可用物理内存段,zone是一个比page更大的概念),并记录和管理本区域物理内存页的分配情况。最后将struct page结构体和struct zone结构体都保存到全局结构体struct global_memory_descriptor内。

物理内存页的管理结构示意图:

Screenshot_20220730_231227

pages_struct结构包含所有内存结构体,zones_struct结构包含所有区域空间结构体,它们增强全局结构体struct global_memory_descriptor的管理能力。每当执行页的分配或回收等操作时,内核回从全局结构体struct global_memory_descriptor中索引处对应的区域空间结构和页结构,并调整区域空间结构的管理信息与页结构的属性和参数。

内存页结构、区域空间结构和全局结构体详细定义:

1
2
3
4
5
6
7
struct page {
struct zone *zone_struct;
unsigned long phy_address;
unsigned long attribute;
unsigned long reference_count;
unsigned long age;
};
  • zone_struct:指向本页所属的区域结构体
  • phy_address:页的物理地址
  • attribute:页的属性,描述当前页的映射状态、活动状态、使用者等信息
  • reference_count:描述该页的引用次数
  • age:描述页的创建时间

phy_addresszone_struct结构成员可以通过计算获得,添加这两个成员是为了节省计算时间,以空间换时间。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
struct zone {
struct page *pages_group;
unsigned long pages_length;
unsigned long zone_start_address;
unsigned long zone_end_address;
unsigned long zone_length;
unsigned long attribute;

struct global_memory_descriptor *gmd_struct;

unsigned long page_using_count;
unsigned long page_free_count;
unsigned long total_pages_link;
};
  • pages_groupstruct page结构体数组指针
  • pages_length:本区域包含的struct page结构体数量
  • zone_start_address:本区域的起始页对齐地址
  • zone_end_address:本区域的结束页对齐地址
  • zone_length:本区域经过对齐后的地址长度
  • attribute:本区域空间的属性
  • gdm_struct:指向全局结构体struct global_memory_descriptor
  • page_using_count:本区域已经使用的物理内存页数量
  • page_free_count:本区域空闲物理内存页数量
  • total_pages_link:本区域物理页被引用次数

一个物理页可以同时映射到线性地址空间的多个位置上,所有total_pages_linkpage_using_count在数值上不一定相等。而成员变量attribute则用于描述当前区域是否支持DMA、页是否经过页表映射等信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
struct global_memory_descriptor {
struct E820 e820[32];
unsigned long e820_length;

unsigned long *bits_map;
unsigned long bits_size;
unsigned long bits_length;

struct page *pages_strust;
unsigned long pages_size;
unsigned long pages_length;

struct zone *zones_struct;
unsigned long zones_size;
unsigned long zones_length;

unsigned long start_code, end_code, end_data, end_brk;

unsigned long end_of_struct;
};
  • e820[32]:物理内存段结构数组

  • e820_length:物理内存段结构数组长度

  • bits_map:物理地址空间页映射位图

  • bits_size:物理地址空间页数量

  • bits_length:物理地址空间页数量

  • pages_struct:指向全局struct page结构体数组的指针

  • pages_sizestruct page结构体总数

  • pages_lengthstruct page结构体数组长度

  • zones_struct:指向全局struct zone结构体数组的指针

  • zones_sizestruct zone结构体总数

  • zones_lengthstruct zone结构体数组长度

  • start_code:内核程序的起始代码段地址

  • end_code:内核程序的结束地址段地址

  • end_data:内核程序的结束数据段地址

  • end_brk:内核程序的结束地址

  • end_of_struct:内存页管理结构的结尾地址

bits_*相关字段是struct page结构体的位图映射,它们一一对应的关系。建立bits位图映射的目的是为方便检索pages_struct中的空闲页表,而pages_*zones_*相关变量用来记录struct pagestruct zone结构体数组的首地址以及资源分配情况的信息。

初始化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
extern char _text;
extern char _etext;
extern char _edata;
extern char _end;

void start_kernel(void) {
...
pos.fb_length = (pos.x_resolution * pos.y_resolution * 4 + PAGE_4K_SIZE - 1) &
PAGE_4K_MASK;

memory_management_struct.start_code = (unsigned long) &_text;
memory_management_struct.end_code = (unsigned long) &_etext;
memory_management_struct.end_data = (unsigned long) &_edata;
memory_management_struct.end_brk = (unsigned long) &_end;
...
}

end_brk成员变量保存内核程序的结束地址,这个地址后的内存空间可以被任意使用。

保存struct pagestruct zone数组:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
void memory_init() {
...
// 剔除e820结构体数组中的脏数据
if (p->type > 4 || p->length == 0 || p->type < 1) {
break;
}
...init
// 根据物理地址空间的结束地址,把物理地址按照2MB页对齐,从而统计出物理地址空间可分页数量。这个物理地址空间可分页数不仅包括可用的物理内存,还包括内存空洞和ROM地址指针
total_memory =
memory_management_struct.e820[memory_management_struct.e820_length]
.address +
memory_management_struct.e820[memory_management_struct.e820_length]
.length;

// bits map construction
// bits_map是映射位图的指针,它指向内核程序结束地址end_brk的4KB边界上对齐位置处
memory_management_struct.bits_map =
(unsigned long *)((memory_management_struct.end_brk + PAGE_4K_SIZE - 1) &
PAGE_4K_MASK);

// 将物理地址空间可分页数赋值给bits_size成员变量
memory_management_struct.bits_size = total_memory >> PAGE_2M_SHIFT;

memory_management_struct.bits_length =
(((unsigned long)(total_memory >> PAGE_2M_SHIFT) + sizeof(long) * 8 - 1) /
8) &
(~(sizeof(long) - 1));
// bits_map置位,以标注非内存页(内存空洞和ROM空间)已被使用,随后再通过程序将映射位图中的可用物理内存页复位
memset(memory_management_struct.bits_map, 0xff,
memory_management_struct.bits_length);
}

struct page结构体建立存储空间,并对其进行初始化:

1
2
3
4
5
6
7
8
9
10
11
12
13
// page construction init
memory_management_struct.pages_struct =
(struct page *)(((unsigned long)memory_management_struct.bits_map +
memory_management_struct.bits_length + PAGE_4K_SIZE -
1) &
PAGE_4K_MASK);
memory_management_struct.pages_size = total_memory >> PAGE_2M_SHIFT;
memory_management_struct.pages_length =
((total_memory >> PAGE_2M_SHIFT) * sizeof(struct page) + sizeof(long) -
1) &
(~(sizeof(long) - 1));
memset(memory_management_struct.pages_struct, 0x00,
memory_management_struct.pages_length);

struct page结构体数组的存储空间位于bit映射位图之后,数组的元素数量为物理地址空间可分页数,其分配与计算方式同bit映射位图相似。

struct zone结构体建立存储空间并初始化:

1
2
3
4
5
6
7
8
9
10
11
12
// zones construction init
memory_management_struct.zones_struct =
(struct zone *)(((unsigned long)memory_management_struct.pages_struct +
memory_management_struct.pages_length + PAGE_4K_SIZE -
1) &
PAGE_4K_MASK);

memory_management_struct.zones_size = 0;
memory_management_struct.zones_length =
(5 * sizeof(struct zone) + sizeof(long) - 1) & (~(sizeof(long) - 1));
memset(memory_management_struct.zones_struct, 0x00,
memory_management_struct.zones_length);

执行流程与上下文的struct page结构体初始化过程基本相同。当前暂时无法计算struct zone结构体数组的元素个数,只能将成员变量zone_size设置为0,将zone_length成员变量暂时按照5个struct zone结构体来计算。

再次遍历E820数组完成各个数组成员变量的初始化工作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
for (i = 0; i <= memory_management_struct.e820_length; i++) {
unsigned long start, end;
struct zone *z;
struct page *p;
unsigned long *b;

if (memory_management_struct.e820[i].type != 1) {
continue;
}
start = PAGE_2M_ALIGN(memory_management_struct.e820[i].address);
end = ((memory_management_struct.e820[i].address +
memory_management_struct.e820[i].length) >>
PAGE_2M_SHIFT)
<< PAGE_2M_SHIFT;
if (end <= start) {
continue;
}
// zone init
z = memory_management_struct.zones_struct +
memory_management_struct.zones_size;
memory_management_struct.zones_size++;
z->zone_start_address = start;
z->zone_end_address = end;
z->zone_length = end - start;

z->page_using_count = 0;
z->page_free_count = end - start;

z->total_pages_link = 0;
z->attribute = 0;
z->gmd_struct = &memory_management_struct;

z->pages_length = (end - start) >> PAGE_2M_SHIFT;
z->pages_group = (struct page *)(memory_management_struct.pages_struct +
(start >> PAGE_2M_SHIFT));

// page init
p = z->pages_group;
for (j = 0; j < z->pages_length; j++, p++) {
p->zone_struct = z;
p->phy_address = start + PAGE_2M_SIZE * j;
p->attribute = 0;
p->reference_count = 0;
p->age = 0;
// 将当前struct page结构体所代表的物理地址转换成bits_map映射位图中对应的位
// 由于此前已将bits_map映射位图全部置位,将可用物理页对应的位和1执行异或操作,将对应的可用物理页标注位未被使用
*(memory_management_struct.bits_map +
((p->phy_address >> PAGE_2M_SHIFT) >> 6)) ^=
1UL << (p->phy_address >> PAGE_2M_SHIFT) % 64;
}
}

遍历全部物理内存段信息以初始化可用物理内存段。代码首先过滤非物理内存段,再将剩下的可用物理内存段进行页对齐,如果本段物理内存有可用物理页,则把该段内存空间视为一个可用的struct zone区域空间,并初始化。

首尾:

1
2
3
4
5
6
7
8
9
10
11
12
13
// init address 0 to page struct 0; because the
// memory_management_struct.e820[0].type ! = 1
memory_management_struct.pages_struct->zone_struct =
memory_management_struct.zones_struct;
memory_management_struct.pages_struct->phy_address = 0UL;
memory_management_struct.pages_struct->attribute = 0;
memory_management_struct.pages_struct->reference_count = 0;
memory_management_struct.pages_struct->age = 0;

memory_management_struct.zones_length =
(memory_management_struct.zones_size * sizeof(struct zone) +
sizeof(long) - 1) &
(~(sizeof(long) - 1));

打印关键信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
color_printk(
ORANGE, BLACK, "bits_map:%#018lx,bits_size:%#018lx,bits_length:%#018lx\n",
memory_management_struct.bits_map, memory_management_struct.bits_size,
memory_management_struct.bits_length);

color_printk(ORANGE, BLACK,
"pages_struct:%#018lx,pages_size:%#018lx,pages_length:%#018lx\n",
memory_management_struct.pages_struct,
memory_management_struct.pages_size,
memory_management_struct.pages_length);

color_printk(ORANGE, BLACK,
"zones_struct:%#018lx,zones_size:%#018lx,zones_length:%#018lx\n",
memory_management_struct.zones_struct,
memory_management_struct.zones_size,
memory_management_struct.zones_length);

ZONE_DMA_INDEX = 0;
ZONE_NORMAL_INDEX = 0;

全局变量ZONE_DMA_INDEXZONE_NORMAL_INDEX暂时无法区分,所以先将它们指向同一个struct zone区域空间,然后再显示struct zone结构体信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
for (i = 0; i < memory_management_struct.zones_size;
i++) // need rewrite in the future
{
struct zone *z = memory_management_struct.zones_struct + i;
color_printk(ORANGE, BLACK,
"zone_start_address:%#018lx,zone_end_address:%#018lx,zone_"
"length:%#018lx,pages_group:%#018lx,pages_length:%#018lx\n",
z->zone_start_address, z->zone_end_address, z->zone_length,
z->pages_group, z->pages_length);
if (z->zone_start_address == 0x100000000)
ZONE_UNMAPED_INDEX = i;
}
memory_management_struct.end_of_struct =
(unsigned long)((unsigned long)memory_management_struct.zones_struct +
memory_management_struct.zones_length +
sizeof(long) * 32) &
(~(sizeof(long) -
1)); ////need a blank to separate memory_management_struct

显示区域空间结构体struct zone的详细统计信息,如果当前区域的起始地址是0x100000000,就将此区域索引值记录在全局变量ZONE_UNMAPED_INDEX内,表示该区域空间开始的物理页未曾经过页表映射。最后还需要调整end_of_struct的值,以记录上述结构的结束地址,并且预留一段内存空间防止越界访问。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
color_printk(
ORANGE, BLACK,
"start_code:%#018lx,end_code:%#018lx,end_data:%#018lx,end_brk:%#018lx,"
"end_of_struct:%#018lx\n",
memory_management_struct.start_code, memory_management_struct.end_code,
memory_management_struct.end_data, memory_management_struct.end_brk,
memory_management_struct.end_of_struct);

i = VIRT_TO_PHY(memory_management_struct.end_of_struct) >> PAGE_2M_SHIFT;

for (j = 0; j <= i; j++) {
page_init(memory_management_struct.pages_struct + j,
PG_PTABLE_MAPED | PG_KERNEL_INIT | PG_ACTIVE | PG_KERNEL);
}

打印start_codeend_codeend_dataend_brkend_of_struct,还将系统内核与内存管理单元结构所占物理页的page结构体全部初始化成 PG_PTABLE_MAPED(经过页表映射的页)| PG_KERNEL_INIT(内核初始化程序) | PG_ACTIVE(使用中的页) | PG_KERNEL(内核层页)属性。

1
2
3
4
5
6
7
8
9
10
11
12
global_cr3 = get_gdt();

color_printk(INDIGO, BLACK, "Global_CR3\t:%#018lx\n", global_cr3);
color_printk(INDIGO, BLACK, "*Global_CR3\t:%#018lx\n",
*PHY_TO_VIRT(global_cr3) & (~0xff));
color_printk(PURPLE, BLACK, "**global_cr3\t:%#018lx\n",
*PHY_TO_VIRT(*PHY_TO_VIRT(global_cr3) & (~0xff)) & (~0xff));

for (i = 0; i < 10; i++)
*(PHY_TO_VIRT(global_cr3) + i) = 0UL;

FLUSH_TLB();

get_gdt()用于读取CR3控制寄存器内保存的页目录基地址。然后打印几层页表的首地址。由于页表项只能保存物理地址,那么根据内核执行头程序(head.S)初始化的页表项可知,global_cr3变量保存的物理地址是0x0000000000101000*global_cr3保存的物理地址是0x0000000000102000,而**global_cr3保存的物理地址是0x0000000000103000

为了消除一致性页表映射,将页目录(PML4页表)中的前10个页表项清零(其实只要第一个清零就可以了)。虽然已将这些页表项清零,但是它们不会立即生效,必须使用FLUSH_TLB()函数才能让更改的页表项生效

1
2
3
4
5
6
7
8
9
#define FLUSH_TLB()                                                            \
do { \
unsigned long tmpreg; \
__asm__ __volatile__("movq %%cr3, %0 \n\t" \
"movq %0, %%cr3 \n\t" \
: "=r"(tmpreg) \
: \
: "memory"); \
} while (0)

函数实现非常简单,仅仅重新赋值了一次CR3控制寄存器,以使更改后的页表项生效。

在更改页表项后,原页表项依然缓存与TLB(Translation Lookaside Buffer,旁路转换缓冲存储器)内,重新加载页目录基地址到CR3控制寄存器将迫使TLB自动刷新,这样就达到了更新页表项的目的

get_gdt函数实现:

1
2
3
4
5
static inline unsigned long *get_gdt() {
unsigned long *tmp;
__asm__ __volatile__("movq %%cr3, %0 \n\t" : "=r"(tmp) : : "memory");
return tmp;
}

CR3控制寄存器里的页目录物理基地址读取出来,并将其传递给函数调用者。

page_init实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
unsigned long page_init(struct page *page, unsigned long flags) {
if (!page->attribute) {
*(memory_management_struct.bits_map +
((page->phy_address >> PAGE_2M_SHIFT) >> 6)) |=
(page->phy_address >> PAGE_2M_SHIFT) % 64;
page->attribute = flags;
page->reference_count++;
page->zone_struct->page_using_count++;
page->zone_struct->page_free_count++;
page->zone_struct->total_pages_link++;
} else if ((page->attribute & PG_REFRENCED) ||
(page->attribute & PG_K_SHARE_TO_U) || (flags & PG_REFRENCED) ||
(flags & PG_K_SHARE_TO_U)) {
page->attribute |= flags;
page->reference_count++;
page->zone_struct->total_pages_link++;
} else {
*(memory_management_struct.bits_map +
((page->phy_address >> PAGE_2M_SHIFT) >> 6)) |=
1UL << (page->phy_address >> PAGE_2M_SHIFT) % 64;
}
return 0;
}

struct page进行初始化,如果当前页面结构或参数flags中含有引用属性PG_REFRENCED或共享属性PG_K_SHARE_TO_U,那么就只增加struct page结构体的引用计数和struct zone结构体的页面被引用计数。否则就仅仅添加页表属性,并置位bit映射位图的相应位。

可用物理内存页的分配

alloc_pages实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
/**
* alloc page
* @param zone_select
* @param number
* @param page_flags
* @return
*/
struct page *alloc_pages(int zone_select, int number,
unsigned long page_flags) {
int i;
unsigned long page = 0;

int zone_start = 0;
int zone_end = 0;

switch (zone_select) {
case ZONE_DMA:
zone_start = 0;
zone_end = ZONE_DMA_INDEX;
break;
case ZONE_NOMAL:
zone_start = ZONE_DMA_INDEX;
zone_end = ZONE_NORMAL_INDEX;
break;
case ZONE_UNMAPED:
zone_start = ZONE_UNMAPED_INDEX;
zone_end = memory_management_struct.zones_size - 1;
break;
default:
color_printk(RED, BLACK, "alloc_pages error zone_select ZONE_DMA_INDE\n");
return NULL;
break;
}

for (i = zone_start; i <= zone_end; i++) {
struct zone *z;
unsigned long j;
unsigned long start, end, length;
unsigned long tmp;

if ((memory_management_struct.zones_struct + i)->page_free_count < number) {
continue;
}

z = memory_management_struct.zones_struct + i;
start = z->zone_start_address >> PAGE_2M_SHIFT;
end = z->zone_end_address >> PAGE_2M_SHIFT;
length = z->zone_length >> PAGE_2M_SHIFT;

tmp = 64 - start % 64;

for (j = start; j <= end; j += j % 64 ? tmp : 64) {
unsigned long *p = memory_management_struct.bits_map + (j >> 6);
unsigned long shift = j % 64;
unsigned long k;

for (k = shift; k < 64 - shift; k++) {
if (!(((*p >> k) | (*(p + 1) << (64 - k))) &
(number == 64 ? 0xffffffffffffffffUL : ((1UL << number) - 1)))) {
unsigned long l;
page = j + k - 1;
for (l = 0; l < number; l++) {
struct page *x = memory_management_struct.pages_struct + page + l;
page_init(x, page_flags);
}
goto find_free_pages;
}
}
}
}
return NULL;
find_free_pages:
return (struct page *)(memory_management_struct.pages_struct + page);
}

alloc_pages会根据zone_select参数来判定需要检索的内存区域空间。

当前Bochs虚拟机能开辟处2GB空间,以至于虚拟平台仅有一个可用物理内存段,因此ZONE_DMA_INDEXZONE_NORMAL_INDEXZONE_UNMAPED_INDEX三个变量均代表同一内存区域空间,即使用默认值0代表的内存区域空间。

分配内存代码从目标内存区域空间的起始内存页结构开始逐一遍历,直至内存区域空间的结尾。由于起始内存页结构对应的BIT映射位图往往位于非对齐(按UNSIGNED LONG类型对齐)位置处,而且每次将按UNSIGNED LONG类型作为遍历的步进长度,同时步进过程还会按UNSIGNED LONG类型对齐。因此起始页的BIT映射位图只能检索tmp = 64 - start % 64;次,随后借助代码j += j % 64 ? tmp : 64将索引变量j调整到对齐位置处。为了保证alloc_pages函数可以检索出64个连续的物理页,使用程序(*p >> k) | (*(p + 1) << (64 - k)),将后一个UNSIGNED LONG变量的低位部分补齐到正在检索的变量中,只有这样才能保证最多可申请连续的64个物理页。

如果检索出满足条件的物理页组,便将BIT映射位图对应的内存页结构struct page初始化,并返回第一个内存页结构地址。

main.c测试:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
struct page *page = NULL;

color_printk(RED, BLACK, "memory_management_struct.bits_map:%#018lx\n",
*memory_management_struct.bits_map);
color_printk(RED, BLACK, "memory_management_struct.bits_map:%#018lx\n",
*(memory_management_struct.bits_map + 1));

page = alloc_pages(ZONE_NOMAL, 64, PG_PTABLE_MAPED | PG_ACTIVE | PG_KERNEL);

for (i = 0; i <= 64; i++) {
color_printk(INDIGO, BLACK, "page%d\tattribute:%#018lx\taddress:%#018lx\t",
i, (page + i)->attribute, (page + i)->phy_address);
i++;
color_printk(INDIGO, BLACK, "page%d\tattribute:%#018lx\taddress:%#018lx\t\n",
i, (page + i)->attribute, (page + i)->phy_address);
}

color_printk(RED, BLACK, "memory_management_struct.bits_map:%#018lx\n",
*memory_management_struct.bits_map);
color_printk(RED, BLACK, "memory_management_struct.bits_map:%#018lx\n",
*(memory_management_struct.bits_map + 1));

结果如下:

Screenshot_20220819_003356