构建64位操作系统-物理页管理

1.论述

1.1.获取计算机系统所有实际可用物理区域信息

        计算机系统的物理区域并不是连续的,且有的物理区域映射到特定硬件内部区域以便实现特定功能。

        对计算机系统的物理区域进行分页管理,首先,要获取所有离散的物理区域信息。

INT 15h, AX=E820h
调用传入:
EAX=0000E820h
EDX=534D4150h
EBX=00000000(此值为起始映射结构体,其他值为后续映射结构体)
ECX=预设返回结果的缓存区结构体长度,以字节为单位(应该大于等于20B)
ES:DI=保存返回结果的缓存区地址
调用返回:
如果CF=0,则说明操作执行成功。
    EAX=534D4150h(字符串"SMAP")
    ES:DI=保存返回结果的缓存区地址
    EBX=00000000h(此值表示检测结束,其他值为后续映射信息结构体序号)
    ECX=保存实际操作的缓存区结构体长度,以字节为单位
如果CF=1,则说明操作执行失败
    AH=错误码(80h:无效命令;86h:不支持此功能)

        物理地址空间信息结构

偏移值字节数描述
00h8B起始地址
08h8B长度(以字节为单位)
10h4B内存类型

        内存类型介绍表

类型值描述
01h可用物理内存
02h保留或无效值(包括ROM,设备内存)
03hACPI的回收内存
04hACPINVS内存
其他值未定义,保留使用

2.实践

        1.1.获取计算机系统所有物理区域信息,并将信息连续存储到值定物理位置

; 这部分汇编采用的是Intel汇编格式
    mov	ebx,	0
	mov	ax,	0x00
	mov	es,	ax
	mov	di,	0x7E00	
Label_Get_Mem_Struct:
	mov	eax,	0x0E820
	mov	ecx,	20
	mov	edx,	0x534D4150
	int	15h
	jc	Label_Get_Mem_Fail
	add	di,	20

	cmp	ebx,	0
	jne	Label_Get_Mem_Struct
	jmp	Label_Get_Mem_OK

        上述指令的作用是,从物理位置0x7E00开始连续存储多个20字节的物理地址空间信息结构。

        1.2.通过一个全局的对象实例进行物理页管理

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_struct;
	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;	
};


// 1.对象实例初始化
struct Global_Memory_Descriptor memory_management_struct = {{0},0};

        1.3.为memory_management_struct 进行有意义的初始化

void init_memory()
{
	// 1.为实例对象的下述字段赋值
	// start_code表示内核代码段起始位置线性地址
	memory_management_struct.start_code = (unsigned long)& _text;
	// end_code表示内核代码段尾后位置线性地址
	memory_management_struct.end_code   = (unsigned long)& _etext;
	// end_data表示内核数据段尾后位置线性地址
	memory_management_struct.end_data   = (unsigned long)& _edata;
	// end_brk是一个动态变化值,表示当前可供分配区域起始线性地址
	memory_management_struct.end_brk    = (unsigned long)& _end;

	int i,j;
	unsigned long TotalMem = 0 ;
	struct E820 *p = NULL;	
	color_printk(BLUE,BLACK,"Display Physics Address MAP,Type(1:RAM,2:ROM or Reserved,3:ACPI Reclaim Memory,4:ACPI NVS Memory,Others:Undefine)\n");
	p = (struct E820 *)0xffff800000007e00;
	// 2.从7e00物理内存位置依次取出若干个20字节。并依据20字节内容设置实例对象的e820数组元素。
	// 这里假定得到物理区域数量不会超过32个。
	for(i = 0; i < 32; i++)
	{
		if(p->type == 1)
			color_printk(ORANGE,BLACK,"Address:%#018lx\tLength:%#018lx\tType:%#010x\n",p->address,p->length,p->type);
		if(p->type == 1)
			TotalMem +=  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;
		// 实例对象e820_length表示实际拥有的物理区域数量。这个数值随着遍历来统计出来的。
		memory_management_struct.e820_length = i;
		p++;
		if(p->type > 4 || p->length == 0 || p->type < 1)
			break;		
	}

	TotalMem = 0;
	// 3.这里无实际功能上的意义。目的是遍历所有物理区域,统计出来这些物理区域中存在多少可被分配的2MB物理页
	// 对于每个可供分配的2MB物理页要求物理页基地址必须按2MB对齐。
	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;
		TotalMem += (end - start) >> PAGE_2M_SHIFT;
	}
	
	color_printk(ORANGE,BLACK,"OS Can Used Total 2M PAGEs:%#018lx=%018ld\n",TotalMem,TotalMem);
	// 4.实例对象通过引入bit_map来加快物理页分配和回收的速度。
	// bits_map按64比特位作为一组。每个比特位代表一个2MB物理页。
	//bits map construction init
	// 这里假定最后一个物理区域的起始地址是所有区域中最大的。所以,TotalMem就是物理区域最大可达物理地址
	TotalMem = memory_management_struct.e820[memory_management_struct.e820_length].address 
		+ memory_management_struct.e820[memory_management_struct.e820_length].length;
	
	// 实例对象的end_brk是当前可供分配区域起始线性地址。
	// 从此起始开始寻找首个可以对齐到4KB的线性地址作为bits_map起始存储位置
	memory_management_struct.bits_map = 
		(unsigned long *)((memory_management_struct.end_brk + PAGE_4K_SIZE - 1) & PAGE_4K_MASK);
	// bits_map中需要管理的比特位个数计算,这些数量的比特位包含了所有可用2MB物理页
	memory_management_struct.bits_size = TotalMem >> PAGE_2M_SHIFT;
	// bits_map部分占据尺寸,一方面需要能容纳指定数量比特位,另一方面,这个尺寸需要是8字节对齐的。
	memory_management_struct.bits_length = 
		(((unsigned long)(TotalMem >> PAGE_2M_SHIFT) + sizeof(long) * 8 - 1) / 8) & ( ~ (sizeof(long) - 1));
	// bits_map中管理的所有比特位设置为1,这样表示所有物理页都不可被分配
	memset(memory_management_struct.bits_map,0xff,memory_management_struct.bits_length);		

	// 5.实例对象通过引入pages_struct来对每个物理页进行管理
	//pages construction init
	// pages_struct区域起始线性地址是bits_map区域后首个可以与4KB对齐的线性地址
	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);
	// pages_structs中需要管理的物理页数量
	memory_management_struct.pages_size = TotalMem >> PAGE_2M_SHIFT;
	// pages_struct部分占据尺寸,一方面需要能容纳指定数量的Page对象,另一方面,这个尺寸需要是8字节对齐的。
	memory_management_struct.pages_length = 
		((TotalMem >> PAGE_2M_SHIFT) * sizeof(struct Page) + sizeof(long) - 1) & ( ~ (sizeof(long) - 1));
	// 对每个Page对象内存置空来完成初始化
	memset(memory_management_struct.pages_struct,0x00,memory_management_struct.pages_length);	

	// 6.实例对象通过引入zones_struct来对每个物理区域进行管理。
	//zones construction init
	// zones_struct区域起始线性地址是pages_struct区域后首个可以与4KB对齐的线性地址
	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;
	// 先假定至多只有10个物理区域需要管理。
	// zones_struct部分占据尺寸,一方面需要能容纳指定数量的Zone对象,另一方面,这个尺寸需要是8字节对齐的。
	memory_management_struct.zones_length = (10 * sizeof(struct Zone) + sizeof(long) - 1) & (~(sizeof(long) - 1));
	// 对每个Zone对象内存置空来完成初始化
	memset(memory_management_struct.zones_struct,0x00,memory_management_struct.zones_length);	
	
	// 7.对原始收集的物理区域进行分析来得到需要被管理的物理区域。并为其构造Zone对象。
	for(i = 0; i <= memory_management_struct.e820_length; i++)
	{
		unsigned long start,end;
		struct Zone * z;
		struct Page * p;
		// 可以被管理的物理区域需要满足条件
		// 1.物理区域的类型需要是1--可分配内存
		// 2.物理区域内部至少有一个基地址2MB对齐的物理页可供使用。
		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对象。
		// 这里找到该区域关联的Zone对象线性地址		
		z = memory_management_struct.zones_struct + memory_management_struct.zones_size;
		// 更新实例对象zones_size字段
		memory_management_struct.zones_size++;
		// 对Zone对象进行符合物理区域实际的初始化
		// 区域起始物理地址(已经按2MB对齐)
		z->zone_start_address = start;
		// 区域尾后物理地址(已经按2MB对齐)
		z->zone_end_address = end;
		// 尺寸。这是区域内可分配物理页尺寸和
		z->zone_length = end - start;
		// 区域内物理页中被使用页数量
		z->page_using_count = 0;
		// 区域内物理页中可供分配页数量
		z->page_free_count = (end - start) >> PAGE_2M_SHIFT;
		z->total_pages_link = 0;
		// 区域属性
		z->attribute = 0;
		// Zone隶属的实例对象地址
		z->GMD_struct = &memory_management_struct;
		// Zone内可供分配物理页数量
		z->pages_length = (end - start) >> PAGE_2M_SHIFT;
		// Zone内首个可供分配物理页关联的Page对象线性地址
		z->pages_group =  (struct Page *)(memory_management_struct.pages_struct + (start >> PAGE_2M_SHIFT));

		// 对区域内可供分配的每个Page对象进行和实际情况相符的初始化
		p = z->pages_group;
		for(j = 0; j < z->pages_length; j++, p++)
		{
			// 设置Page对象隶属的Zone对象地址
			p->zone_struct = z;
			// 设置Page对象代表的物理页的基地址
			p->PHY_address = start + PAGE_2M_SIZE * j;
			// 设置Page对象属性
			p->attribute = 0;
			// 设置Page对象引用数
			p->reference_count = 0;
			// 设置Page对象age
			p->age = 0;

			// 找到Page对象在bits_map中的比特位,并将此比特位设置为0
			// 比特位为0表示此比特位代表的物理页可被分配
			*(memory_management_struct.bits_map + ((p->PHY_address >> PAGE_2M_SHIFT) >> 6)) ^= 1UL << (p->PHY_address >> PAGE_2M_SHIFT) % 64;
		}		
	}

	// 8.根据实际管理的Zone对象数量,计算zones_struct占据尺寸。
	// 尺寸需要能容纳指定数量的Zone对象,且需满足8字节对齐。
	memory_management_struct.zones_length = (memory_management_struct.zones_size * sizeof(struct Zone) + sizeof(long) - 1) & ( ~ (sizeof(long) - 1));

	//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);
	// 9.对区域分类。区域起始物理地址低于4GB的属于一类。高于或等于4GB的属于一类。
	ZONE_DMA_INDEX = 0;	
	ZONE_NORMAL_INDEX = 0;	
	ZONE_UNMAPED_INDEX = 0;
	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)
			ZONE_UNMAPED_INDEX = i;
	}
	
	//color_printk(ORANGE,BLACK,"ZONE_DMA_INDEX:%d\tZONE_NORMAL_INDEX:%d\tZONE_UNMAPED_INDEX:%d\n",ZONE_DMA_INDEX,ZONE_NORMAL_INDEX,ZONE_UNMAPED_INDEX);
	// 10.重新计算当前可供分配区域起始线性地址
	// 因为在用的物理区域除了内核固有的,前面处理中还向前扩展出了bits_map,pages_struct,zones_struct区域
	// 所以,在zones_struct区域后,再隔开一点空间,找一个8字节对齐的线性地址作为后续可用区域起始线性地址。
	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
	//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;

	// 对我们目前已经使用的部分。分解到每个Page对象。对这些Page对象进行相应设置来反映它们的被使用情况。
	for(j = 0; j <= i; j++)
	{
		struct Page * tmp_page =  memory_management_struct.pages_struct + j;
		// 设置Page对象属性
		page_init(tmp_page, PG_PTable_Maped | PG_Kernel_Init | PG_Kernel);
		// 将Page对象对应物理页所对应的bits_map中比特位设置为1。表示物理页已被使用。
		*(memory_management_struct.bits_map + ((tmp_page->PHY_address >> PAGE_2M_SHIFT) >> 6)) |= 1UL << (tmp_page->PHY_address >> PAGE_2M_SHIFT) % 64;
		// 这里做这个判断的原因是,在物理内存区域,尤其是前面部分。常常存在一个或多个不足2MB小区域。
		// 因为我们按2MB来管理物理页。所以物理区域[0,2MB)本来因为由离散的多个小区域组成,是无法被我们管理的。
		// 但我们确实在boot,loader,kernel处理中均使用了这块区域。所以,我们对其特殊处理。
		// 既这样的物理页没有关联的zone,但关联的Page对象依然被设置来物理页的使用情况。
		if(tmp_page->zone_struct == NULL)
		{
			tmp_page->zone_struct = NULL;
			tmp_page->PHY_address = j << PAGE_2M_SHIFT;
			tmp_page->reference_count = 1;
			tmp_page->age = 0;
		}
		else
		{
			tmp_page->zone_struct->page_using_count++;
			tmp_page->zone_struct->page_free_count--;
		}
	}

	//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));
	//color_printk(ORANGE,BLACK,"1.memory_management_struct.bits_map:%#018lx\tzone_struct->page_using_count:%d\tzone_struct->page_free_count:%d\n",*memory_management_struct.bits_map,memory_management_struct.zones_struct->page_using_count,memory_management_struct.zones_struct->page_free_count);
	//flush_tlb();
}

        上述过程展开具体介绍可以参考注释部分。

        1.4.执行到这里我们为memory_management_struct实例对象进行了有意义的初始化。后续物理页管理将借助memory_management_struct来完成。

        但是我们接下来继续再做一件事。

        当我们要访问一个虚拟地址时,系统得通过页表,得到相应得物理地址,再基于物理地址进行实际得访问工作。

        前面我们初始化memory_management_struct对象得过程中,处理了每个可以分配物理页得物理区域。如果想我们后续分配出去得物理页,可以直接基于线性地址进行访问。我们还需为线性地址能转换为物理地址进行页表注册工作。

void pagetable_init()
{
	unsigned long i,j;
	unsigned long * tmp = NULL;
	// 1.获得CR3寄存器值
	Global_CR3 = Get_gdt();
	// 从CR3得到PML4页表物理基地址
	// 页表物理基地址+8*256得到索引为256得表项得地址
	tmp = (unsigned long *)(((unsigned long)Phy_To_Virt((unsigned long)Global_CR3 & (~ 0xfffUL))) + 8 * 256);
	//	color_printk(YELLOW,BLACK,"1:%#018lx,%#018lx\t\t\n",(unsigned long)tmp,*tmp);
	// 通过表项内容得到PDPT页表得物理基地址
	tmp = Phy_To_Virt(*tmp & (~0xfffUL));
	//	color_printk(YELLOW,BLACK,"2:%#018lx,%#018lx\t\t\n",(unsigned long)tmp,*tmp);
	// 取得PDPT索引0表项内容,通过表项内容得到PDT页表物理基地址
	tmp = Phy_To_Virt(*tmp & (~0xfffUL));
	//	color_printk(YELLOW,BLACK,"3:%#018lx,%#018lx\t\t\n",(unsigned long)tmp,*tmp);

	// 2.对我们所管理得每个区域
	// 这样使得我们实例对象里每个zone下可供分配得page对象,
	// 一旦被分配出去后,可以直接拿到此page对应物理页得虚拟地址进行地址访问,无需再进行页表注册
	for(i = 0; i < memory_management_struct.zones_size; i++)
	{
		struct Zone * z = memory_management_struct.zones_struct + i;
		struct Page * p = z->pages_group;
		// 如果存在起始地址超过或等于4GB得物理区域,这类区域不需一开始就进行页表注册。
		if(ZONE_UNMAPED_INDEX && i == ZONE_UNMAPED_INDEX)
			break;

		// 对区域内每个物理页
		for(j = 0; j < z->pages_length; j++,p++)
		{
			// 从物理页Page对象得到物理页基地址
			// 通过物理页基地址得到线性地址
			// 通过线性地址拆分出该地址在PML4页表得索引
			// 结合CR3得到PML4页表物理基地址
			// 得到PML4页表项线性地址
			tmp = (unsigned long *)(((unsigned long)Phy_To_Virt((unsigned long)Global_CR3 & (~ 0xfffUL))) + (((unsigned long)Phy_To_Virt(p->PHY_address) >> PAGE_GDT_SHIFT) & 0x1ff) * 8);
			// 取得PML4页表项内容
			if(*tmp == 0)
			{			
				// 动态分配4KB区域
				unsigned long * virtual = kmalloc(PAGE_4K_SIZE,0);
				// 将此4KB区域作为PDPT表
				// 据此设置PML4页表项内容
				set_mpl4t(tmp, mk_mpl4t(Virt_To_Phy(virtual), PAGE_KERNEL_GDT));
			}

			// 从物理页Page对象得到物理页基地址
			// 通过物理页基地址得到线性地址
			// 通过线性地址拆分出该地址在PDPT页表得索引
			// 结合PDPT页表物理基地址
			// 取得PDPT页表项内容
			tmp = (unsigned long *)((unsigned long)Phy_To_Virt(*tmp & (~ 0xfffUL)) + (((unsigned long)Phy_To_Virt(p->PHY_address) >> PAGE_1G_SHIFT) & 0x1ff) * 8);
			if(*tmp == 0)
			{
				// 动态分配4KB区域
				unsigned long * virtual = kmalloc(PAGE_4K_SIZE,0);
				// 将此4KB区域作为PDT表
				// 据此设置PDPT页表项内容
				set_pdpt(tmp, mk_pdpt(Virt_To_Phy(virtual), PAGE_KERNEL_Dir));
			}

			// 从物理页Page对象得到物理页基地址
			// 通过物理页基地址得到线性地址
			// 通过线性地址拆分出该地址在PDT页表得索引
			// 结合PDT页表物理基地址
			// 得到PDT页表项内容
			tmp = (unsigned long *)((unsigned long)Phy_To_Virt(*tmp & (~ 0xfffUL)) + (((unsigned long)Phy_To_Virt(p->PHY_address) >> PAGE_2M_SHIFT) & 0x1ff) * 8);
			// 设置PDT页表项内容
			set_pdt(tmp,mk_pdt(p->PHY_address,PAGE_KERNEL_Page));
		}
	}


	// 一旦对多级页表中得一级或多级进行了修改后,需要执行flush_tlb()
	// 使得缓存得页表项失效
	flush_tlb();
}

        通过上述处理可以使得我们实例对象里每个zone下可供分配得page对象,

        一旦被分配出去后,可以直接拿到此page对应物理页得虚拟地址进行地址访问,无需再进行页表注册。

        我们实例对象里每个zone下可供分配得page对象还有一个特点是,这些page所代表得物理页中,虚拟地址和物理地址是如下换算关系。

#define PAGE_OFFSET	((unsigned long)0xffff800000000000)
#define Phy_To_Virt(addr)	((unsigned long *)((unsigned long)(addr) + PAGE_OFFSET))

        1.5.下面来实现物理页分配

// 参数1:指定区域
// 参数2:页面数量
// 参数3:标志信息
// 这里分配出来的Page,通过Page得到物理页基地址,再得到物理页线性地址。
// 这样得到的线性地址是可以直接访问使用的。因为在此之前,我们已经为我们管理的zone下的每个page均进行了页表注册。
struct Page * alloc_pages(int zone_select,int number,unsigned long page_flags)
{
	int i;
	unsigned long page = 0;
	unsigned long attribute = 0;
	int zone_start = 0;
	int zone_end = 0;
	// 对一次性分配的物理页数量做了限制。最多一次分配63个连续物理页。
	if(number >= 64 || number <= 0)
	{
		color_printk(RED,BLACK,"alloc_pages() ERROR: number is invalid\n");
		return NULL;		
	}

	switch(zone_select)
	{
		case ZONE_DMA:
				zone_start = 0;
				zone_end = ZONE_DMA_INDEX;
				attribute = PG_PTable_Maped;
			break;
		case ZONE_NORMAL:
				zone_start = ZONE_DMA_INDEX;
				zone_end = ZONE_NORMAL_INDEX;
				attribute = PG_PTable_Maped;
			break;
		case ZONE_UNMAPED:
				zone_start = ZONE_UNMAPED_INDEX;
				zone_end = memory_management_struct.zones_size - 1;
				attribute = 0;
			break;
		default:
			color_printk(RED,BLACK,"alloc_pages() ERROR: zone_select index is invalid\n");
			return NULL;
			break;
	}

	color_printk(RED,BLACK,"alloc_pages() area_min:%#018lx area_max:%#018lx\n", zone_start, zone_end);
	for(i = zone_start; i <= zone_end; i++)
	{
		struct Zone * z;
		unsigned long j;
		unsigned long start,end;
		unsigned long tmp;
		// 对zone进行刷选
		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;
		tmp = 64 - start % 64;
		// 对zone内每个page借助bits_map快速定位
		for(j = start; j < end; j += j % 64 ? tmp : 64)
		{
			unsigned long * p = memory_management_struct.bits_map + (j >> 6);
			unsigned long k = 0;
			unsigned long shift = j % 64;
			unsigned long num = (1UL << number) - 1;

			// 从page对应的比特位开始
			for(k = shift; k < 64; k++)
			{
				// 一次性检测出从page开始是否存在连续数量的空闲比特位
				if( !( (k ? ((*p >> k) | (*(p + 1) << (64 - k))) : *p) & (num) ) )
				{
					unsigned long	l;
					page = j + k - shift;
					for(l = 0;l < number;l++)
					{
						struct Page * pageptr = memory_management_struct.pages_struct + page + l;
						// 分配出去的Page对应的比特位设置为1
						*(memory_management_struct.bits_map + ((pageptr->PHY_address >> PAGE_2M_SHIFT) >> 6)) |= 1UL << (pageptr->PHY_address >> PAGE_2M_SHIFT) % 64;
						// 隶属zone字段更新
						z->page_using_count++;
						z->page_free_count--;
						// page属性设置
						pageptr->attribute = attribute;
					}

					goto find_free_pages;
				}
			}
		}
	}

	// 遭遇到了无法分配场景
	color_printk(RED,BLACK,"alloc_pages() ERROR: no page can alloc\n");
	return NULL;

find_free_pages:
	return (struct Page *)(memory_management_struct.pages_struct + page);
}

        这里分配出来的Page,通过Page得到物理页基地址,再得到物理页线性地址。

        这样得到的线性地址是可以直接访问使用的。

        因为在此之前,我们已经为我们管理的zone下的每个page均进行了页表注册。 

        如果页表管理4GB区域,采用2MB物理页,其实对所有4GB区域进行页表注册,只需一个PML4页表,1个PDPT页表,4个PDT页表即可。

        1.6.下面实现物理页释放

void free_pages(struct Page * page,int number)
{	
	int i = 0;
	if(page == NULL)
	{
		color_printk(RED,BLACK,"free_pages() ERROR: page is invalid\n");
		return ;
	}	

	if(number >= 64 || number <= 0)
	{
		color_printk(RED,BLACK,"free_pages() ERROR: number is invalid\n");
		return ;	
	}
	
	for(i = 0;i<number;i++,page++)
	{
		// bits_map中对应比特位设置为0
		*(memory_management_struct.bits_map + ((page->PHY_address >> PAGE_2M_SHIFT) >> 6)) &= ~(1UL << (page->PHY_address >> PAGE_2M_SHIFT) % 64);
		// 所属zone字段设置
		page->zone_struct->page_using_count--;
		page->zone_struct->page_free_count++;
		// 属性清理
		page->attribute = 0;
	}
}

        1.7.下面是几个物理页操作函数

unsigned long page_init(struct Page * page,unsigned long flags)
{
	page->attribute |= flags;
	if(!page->reference_count || (page->attribute & PG_Shared))
	{
		page->reference_count++;
		page->zone_struct->total_pages_link++;		
	}	
	
	return 1;
}

unsigned long page_clean(struct Page * page)
{
	page->reference_count--;
	page->zone_struct->total_pages_link--;
	if(!page->reference_count)
	{
		page->attribute &= PG_PTable_Maped;
	}
	
	return 1;
}

unsigned long get_page_attribute(struct Page * page)
{
	if(page == NULL)
	{
		color_printk(RED,BLACK,"get_page_attribute() ERROR: page == NULL\n");
		return 0;
	}
	else
		return page->attribute;
}

unsigned long set_page_attribute(struct Page * page,unsigned long flags)
{
	if(page == NULL)
	{
		color_printk(RED,BLACK,"set_page_attribute() ERROR: page == NULL\n");
		return 0;
	}
	else
	{
		page->attribute = flags;
		return 1;
	}
}

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

raindayinrain

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值