MIT6.828——Lab2内存管理准备知识

编程

内存管理" title="内存管理">内存管理机制">保护模式内存管理机制

MIT6.828——Lab1 PartA

MIT6.828——Lab1 PartB

分段机制的问题

​ 分段的主要问题,出现在内存不足或者内存碎片过多的情况下。对于一个程序而言,例如其代码段长度就和其代码的长度直接相关,各个段的大小是不固定的,不能拆分的,要装入内存便一次性将一个整段都装入,因此在内存紧张时,就会出现问题。可以想象到的解决方法有这样一种:将段换出到磁盘上,从而空出一部分的物理内存空间。但是同样的,如果段长度过长,内存过小,在频繁的换入换出也无济于事。

​ 问题的本质在于,分段机制下产生的连续线性地址,被认为在物理内存上也是连续的,线性地址就是物理地址。但是我们可用的物理地址并不是连续的,因此就会产生冲突。所以解决这个问题的关键在于,是否可以:线性地址连续,对应的物理地址不连续?为了解除这种一一映射的关系,便可以通过地址映射。

​ 即 线性地址(虚拟地址)——页表——> 物理地址。

​ 为了效率的问题,这种映射关系写在页表里,页表在内存中,查表的工作由硬件完成。

分页机制的思想:通过映射,可以使得连续的线性地址与任意的物理地址相关联,逻辑上连续的线性地址对应的物理地址可以不连续。

分段机制的作用:将线性地址转化为物理地址;用大小相等(4KB)的页代替大小不等的段。

页表结构

一级页表

首先针对一级页表而言,寻址过程可以由下图表示

可以看到,分页机制仍旧是基于分段基础上的。将分段形成的线性地址,进行划分,利用高20位作为在页表内寻址的偏移量,寻址页表项,其中页表项的基址(物理地址)放在CR3寄存器中。再利用低12位,结合页表项给出的基址,合成物理地址,送上地址总线,即可寻址物理内存单元。可以看到,因为划分了低12位为页内偏移,因此页表的大小也就是4KB,这是一个常用的页大小值。而高20位,则说明,页表中含1M项页表项,占内存位4MB。

二级页表

​ 在一级页表的铺垫下,便有了另一个问题。页表的大小为4M,且必须提前建立好,每个进程都有自己的页表,如果进程数很多,页表的内存开销便很可观,因此是否可以动态的创建页表项呢?解决这个问题的答案就是二级页表。二级页表的思想是,将1M页平均放到1K个页表中,每个页表1K个页表项,占据内存位4KB,刚好为一页的大小。为了存储这些页表,引入了页目录。每个页表的物理地址,都在页目录中以页目录项的形式存储。因为最多1K页表,因此页目录大小也为4KB,一页的大小。

​ 二级页表下的寻址过程如下:

​ 现在需要了解一下,页表项和页目录项的详细信息了。这部分信息,可以在Intel系统开发手册上得到详细说明。

这里直接截取部分说明了:

开启分页机制

开启分页机制,需要做三件事:

  • 准备好页表和页目录
  • 将页目录的物理地址写入CR3
  • 寄存器CR0的PG位置1

编程实例

在之前的mit6.828实验1中,已经看到了一个比较基本的实例,如何进入保护模式,并进行分页操作。为了对于lab2有一个更好的理解,这里截取一部分《操作系统真象还原》的代码进行解释说明。这里可以和lab1部分结合来看

建立GDT进入保护模式

GDT_BASE:   dd    0x00000000 

dd 0x00000000

CODE_DESC: dd 0x0000FFFF

dd DESC_CODE_HIGH4

DATA_STACK_DESC:dd 0x0000FFFF

dd DESC_DATA_HIGH4

VIDEO_DESC: dd 0x80000007

dd DESC_VIDEO_HIGH4

GDT_SIZE equ $ - GDT_BASE

GDT_LIMIT equ GDT_SIZE -1

gdt_ptr dw GDT_LIMIT

dd GDT_BASE

;----------------- 打开A20 ----------------

in al,0x92

or al,0000_0010B

out 0x92,al

;----------------- 加载GDT ----------------

lgdt [gdt_ptr]

;----------------- cr0第0位置1 ----------------

mov eax, cr0

or eax, 0x00000001

mov cr0, eax

jmp dword SELECTOR_CODE:p_mode_start ; 刷新流水线

[bits 32]

p_mode_start:

mov ax, SELECTOR_DATA

mov ds, ax

mov es, ax

mov ss, ax

mov esp,LOADER_STACK_TOP

mov ax, SELECTOR_VIDEO

mov gs, ax

按约定,GDT的第一个段描述符为空,这里建立了三个段,都是按照段描述符的规格进行性质填充,对照相关位的含义既可以知道段的信息。为了装载GDT,使用命令lgdt即可。进入保护模式后,寻址就需要使用段选择子,这在之前的lab1中也说到了。

开启分页

首先规划内存的整体布局,可以先画出下面这张图

人为规定的,将页目录放在了物理地址0x100000处,将第一个页表,放在了物理地址0x101000处。同时划分进程的虚拟地址空间位高端1GB内核空间和低端3GB用户控件。首先需要注意的是,在虚拟内存空间中,将高端1GB完全分给了内核。这对于每个进程都是一样的,为了实现所有进程的内核共享,这部分空间固定占据了页目录项的第0xc00项至第1023项。对于页目录而言,第0项存储了第0个页表的位置,最后一项存储了页目录自身在物理内存中的位置。

值得注意的是,页表0和页表c00都映射到了物理内存的低端1MB。这么做的原因是,在内核加载到内存空间之前,运行的一直是loader程序,它运行在低端1MB。为了保证之前段机制下的地址和现在分页后的地址一致,内核的前1MB也需要映射到物理内存低端1MB空间。低端的1MB=256*4KB,因此占据了256页,需要256个页表项。

在这部分说明之后,下面便是具体的实现:

setup_page:

;先把页目录占用的空间逐字节清0

mov ecx, 4096

mov esi, 0

.clear_page_dir:

mov byte [PAGE_DIR_TABLE_POS + esi], 0

inc esi

loop .clear_page_dir

;开始创建页目录项(PDE)

.create_pde: ; 创建Page Directory Entry

mov eax, PAGE_DIR_TABLE_POS

add eax, 0x1000 ; 此时eax为第一个页表的位置及属性

mov ebx, eax ; 此处为ebx赋值,是为.create_pte做准备,ebx为基址。

; 下面将页目录项0和0xc00都存为第一个页表的地址,

; 一个页表可表示4MB内存,这样0xc03fffff以下的地址和0x003fffff以下的地址都指向相同的页表,

; 这是为将地址映射为内核地址做准备

or eax, PG_US_U | PG_RW_W | PG_P ; 页目录项的属性RW和P位为1,US为1,表示用户属性,所有特权级别都可以访问.

mov [PAGE_DIR_TABLE_POS + 0x0], eax ; 第1个目录项,在页目录表中的第1个目录项写入第一个页表的位置(0x101000)及属性

mov [PAGE_DIR_TABLE_POS + 0xc00], eax ; 一个页表项占用4字节,0xc00表示第768个页表占用的目录项,0xc00以上的目录项用于内核空间,

; 也就是页表的0xc0000000~0xffffffff共计1G属于内核,0x0~0xbfffffff共计3G属于用户进程.

sub eax, 0x1000

mov [PAGE_DIR_TABLE_POS + 4092], eax ; 使最后一个目录项指向页目录表自己的地址

;下面创建页表项(PTE)

mov ecx, 256 ; 1M低端内存 / 每页大小4k = 256

mov esi, 0

mov edx, PG_US_U | PG_RW_W | PG_P ; 属性为7,US=1,RW=1,P=1

.create_pte: ; 创建Page Table Entry

mov [ebx+esi*4],edx ; 此时的ebx已经在上面通过eax赋值为0x101000,也就是第一个页表的地址

add edx,4096

inc esi

loop .create_pte

;创建内核其它页表的PDE

mov eax, PAGE_DIR_TABLE_POS

add eax, 0x2000 ; 此时eax为第二个页表的位置

or eax, PG_US_U | PG_RW_W | PG_P ; 页目录项的属性RW和P位为1,US为0

mov ebx, PAGE_DIR_TABLE_POS

mov ecx, 254 ; 范围为第769~1022的所有目录项数量

mov esi, 769

.create_kernel_pde:

mov [ebx+esi*4], eax

inc esi

add eax, 0x1000

loop .create_kernel_pde

ret

在建立好了虚拟内存的布局之后,就可以正式开启分页机制

call setup_page

;要将描述符表地址及偏移量写入内存gdt_ptr,一会用新地址重新加载

sgdt [gdt_ptr] ; 存储到原来gdt所有的位置

;将gdt描述符中视频段描述符中的段基址+0xc0000000

mov ebx, [gdt_ptr + 2]

or dword [ebx + 0x18 + 4], 0xc0000000 ;视频段是第3个段描述符,每个描述符是8字节,故0x18。

;段描述符的高4字节的最高位是段基址的31~24位

;将gdt的基址加上0xc0000000使其成为内核所在的高地址

add dword [gdt_ptr + 2], 0xc0000000

add esp, 0xc0000000 ; 将栈指针同样映射到内核地址

; 把页目录地址赋给cr3

mov eax, PAGE_DIR_TABLE_POS

mov cr3, eax

; 打开cr0的pg位(第31位)

mov eax, cr0

or eax, 0x80000000

mov cr0, eax

;在开启分页后,用gdt新的地址重新加载

lgdt [gdt_ptr] ; 重新加载

以上是 MIT6.828——Lab2内存管理准备知识 的全部内容, 来源链接: utcz.com/z/519988.html

回到顶部