获取物理内存信息
之前再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的情况,则是程序程序出现了脏数据,就跳出循环。
显示结果如下:

可以看出可用的物理内存空间(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
| #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,运行结果如下:

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

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_address和zone_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_group:struct 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_link与page_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_size:struct page结构体总数
pages_length:struct page结构体数组长度
zones_struct:指向全局struct zone结构体数组的指针
zones_size:struct zone结构体总数
zones_length:struct zone结构体数组长度
start_code:内核程序的起始代码段地址
end_code:内核程序的结束地址段地址
end_data:内核程序的结束数据段地址
end_brk:内核程序的结束地址
end_of_struct:内存页管理结构的结尾地址
bits_*相关字段是struct page结构体的位图映射,它们一一对应的关系。建立bits位图映射的目的是为方便检索pages_struct中的空闲页表,而pages_*和zones_*相关变量用来记录struct page和struct 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 page和struct 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() { ... if (p->type > 4 || p->length == 0 || p->type < 1) { break; } ...init total_memory = memory_management_struct.e820[memory_management_struct.e820_length] .address + memory_management_struct.e820[memory_management_struct.e820_length] .length;
memory_management_struct.bits_map = (unsigned long *)((memory_management_struct.end_brk + PAGE_4K_SIZE - 1) & PAGE_4K_MASK);
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)); 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
| 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
| 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; } 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));
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; *(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
|
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_INDEX与ZONE_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++) { 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));
|
显示区域空间结构体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_code、end_code、end_data、end_brk、end_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
|
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_INDEX、ZONE_NORMAL_INDEX和ZONE_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));
|
结果如下:
