x86使用页表实现虚拟内存原理分析---使用代码分析

分页机制

这一部分在手册第四章
视频讲解可以看这一个课程

在不使用分页机制的时候, 我们看到的是物理内存, 物理内存有多大, 我们就可以使用多大的内存

使用内存分页机制, 我们就可以扩充访问的地址范围, 也可以实现权限的细分, 实际上就是实现虚拟内存, 将地址进行映射, 看到的内存更大了, 但是实际上可以使用的内存的大小还是不变的

访问的内存==>从页表里面找物理内存==>访问实际的物理内存

开启以后得访问过程: 根据段寄存器找到对应的记录的GDT表, 之后根据表找到自己的使用的内存, 加上偏移量之后就是实际的地址, 这一个地址会通过分页机制里面的页表去获取实际的物理地址, 页表的地址放在CR3的寄存器里面

这样可以实现用户使用内存的时候感觉是一块连续的内存, 但是实际的内存可能是不连续的

处理器在访问数据、获取指令时,使用的都是线性地址,只要它是连续的就可以了,最终都能够通过映射表找到实际的物理地址。

x86使用页表实现虚拟内存原理分析---使用代码分析_第1张图片

实现虚拟内存

在使用多任务的时候不同任务感觉自己访问的地址是相同的, 但是实际是不同的, 这是通过页表实现的, 不同任务可以有不同的页表, 把一个相同的地址定位到不同的物理地址
这一个表记录在CR3里面, 在任务切换的时候可以进行切换

使用TSS进行任务切换

实际配置

实际使用的时候可以使用一级页表(每一块是4MB)和二级页表(每一块是4KB大小)

  • 使用4KB的模式, 使用二级页表

x86使用页表实现虚拟内存原理分析---使用代码分析_第2张图片

使用这一个地址的不同段去获取实际的页表

  • 使用4M的模式: 使用一级页表

x86使用页表实现虚拟内存原理分析---使用代码分析_第3张图片

会根据传过来的数据的地址(逻辑地址)分段之后进行访问不同的页表, 获得一个4KB的空间的地址, 最后通过偏移量进行实际的访问

这一个表的地址在CR3里面

x86使用页表实现虚拟内存原理分析---使用代码分析_第4张图片

CR3以及使用4M模式的一级页表格式(只有这一个表, 不需要二级页表)

image-20240204221254066

image-20240204221129884

使用4KB模式的时候的一二级页表格式

x86使用页表实现虚拟内存原理分析---使用代码分析_第5张图片

使用CR4以及CR0控制实际使用的模式以及页表的开启

image-20240205141932827

这一个位控制使用分页

image-20240204220801499
这一个是实际的打开时候的寄存器状态
image-20240204220257786

使用这一个位开启4MB的模式

image-20240204221645335

同时满足这两个条件的时候可以使用4MB的一级页表

实际的实现

第一级映射(页目录表PDE)有两种的格式, 一种是4MB的映射, 一种是4KB的映射使用4MB模式的时候, 就不需要二级页表了, 只有一个表, 最后可以使用的内存实际上是4MB, 使用4KB模式的时候会使用两级页表, 最后实际控制的内存大小是4GB

第二级映射(页表PTE)

实际实现一级映射(4MB)

需要在打开页表之前实现映射, 否则CPU会找不到对应的内存, 直接映射到0地址的位置

//这个表是否有效
#define PDE_P                   (1<<0)
//是否可写
#define PDE_W                   (1<<1)
//是否可以被低权限访问
#define PDE_U                   (1<<2)
//设置使用的模式(4M模式)
#define PDE_PS                  (1<<7)

//定义一个页表的结构体,需要设置低0的表项
//这一个设置的是逻辑地址0地址的分页, 是一个恒等的映射, 使得代码的访问正常, 映射 的地址还是0
uint32_t pg_dir[1024] __attribute__((aligned(4096))) = {
    [0] = (0) | PDE_P |  PDE_W | PDE_U | PDE_PS;
};

x86使用页表实现虚拟内存原理分析---使用代码分析_第6张图片

_start_32:
	//在这里设置段地址
	mov $KERNEL_DATA_SEG, %ax
	mov %ax, %ds
	mov %ax, %es
	mov %ax, %ss
	mov %ax, %gs
	mov %ax, %fs
	mov $_start, %esp

	//打开页表, 记录位置
	mov $pg_dir, %eax
	mov %eax, %cr3
	//CR4里面有一个位控制是否允许这一个模式 
	mov %cr4, %eax
	orl $(1<<4), %eax
	mov %eax, %cr4
	//还需要控制PR0最高位w为1
	mov %cr0, %eax
	orl $(1<<31), %eax
	mov %eax, %cr0

	jmp .

x86使用页表实现虚拟内存原理分析---使用代码分析_第7张图片

x86使用页表实现虚拟内存原理分析---使用代码分析_第8张图片

分页打开后, 可以使用这一个命令查看映射关系, 权限是u: 用户 r: 读 w: 写

实际实现二级映射

x86使用页表实现虚拟内存原理分析---使用代码分析_第9张图片

x86使用页表实现虚拟内存原理分析---使用代码分析_第10张图片

一级表, bit7为0

x86使用页表实现虚拟内存原理分析---使用代码分析_第11张图片

二级表, bit7为1

//这个表是否有效
#define PDE_P                   (1<<0)
//是否可写
#define PDE_W                   (1<<1)
//是否可以被低权限访问
#define PDE_U                   (1<<2)
//设置使用的模式4KB/4MB
#define PDE_PS                  (1<<7)
//新建另一个映射的地址
#define MAG_ADDR                0x80000000
//使用二级表进行控制内存测试, 这里是实际上的地址
uint8_t map_phy_buffer[4096] __attribute__((aligned(4096))) = {0x36};
//创建一个二级表项,随便给一个值,在后面会进行设置,随便初始化一个值连接器会把其他的位置设置为0,否则会为随机的
static uint32_t page_table[1024] __attribute__((aligned(4096))) = {PDE_U};

//定义一个页表的结构体,需要设置低0的表项
uint32_t pg_dir[1024] __attribute__((aligned(4096))) = {
    [0] = (0) | PDE_P |  PDE_W | PDE_U | PDE_PS,
};

void os_init(void){
    //设置一级表,使用的是表的高10位,这里会找到想要的虚拟地址所在的位置,设置为二级表的位置
    /********************************************************************************/
    //计算一下4KB的话对应的表项 = 二级表项地址+权限(这里没有使用4M的映射(PDE_PS))
    pg_dir[MAG_ADDR>>22] = (uint32_t)page_table | PDE_P | PDE_W | PDE_U;
    /********************************************************************************/
    //初始化表的二级,这里是实际的地址,之后需要设置对应的位置,这里会设置二级表指向的是上面的数组
    page_table[(MAG_ADDR>>12)&0x3ff] = (uint32_t)map_phy_buffer | PDE_P | PDE_W | PDE_U;

}

实际的地址计算

x86使用页表实现虚拟内存原理分析---使用代码分析_第12张图片

_start_32:
	//在这里设置段地址
	mov $KERNEL_DATA_SEG, %ax
	mov %ax, %ds
	mov %ax, %es
	mov %ax, %ss
	mov %ax, %gs
	mov %ax, %fs
	mov $_start, %esp
	//在这里调用设置4KB的分页表
	call os_init
	# 打开页表机制
	mov $pg_dir, %eax
	mov %eax, %cr3
	//CR4里面有一个位控制是否允许这一个模式(这一个没用上)
	mov %cr4, %eax
	orl $(1<<4), %eax
	mov %eax, %cr4
	//还需要控制PR0最高位w为1
	mov %cr0, %eax
	orl $(1<<31), %eax
	mov %eax, %cr0

	jmp .

image-20240205144130516

使用这一个命令查看现有的映射

x86使用页表实现虚拟内存原理分析---使用代码分析_第13张图片

x86使用页表实现虚拟内存原理分析---使用代码分析_第14张图片

在修改之后发现两个位置是同步的, 可以直接操控第二个映射地址或者采用第一个映射的地址

总结

也就是说,在没有开启分页机制时,由程序员给出的逻辑地址,需要先通过分段机制(GDT表)转换成物理地址。但在开启分页机制后,逻辑地址仍然要先通过分段机制进行转换,只不过转换后不再是最终的物理地址,而是线性地址,然后再通过一次分页机制转换,得到最终的物理地址。

GDT表使用

你可能感兴趣的:(手写操作系统,数据库,服务器,经验分享,linux,汇编,windows,ubuntu)