盒子
盒子
文章目录
  1. 权限和错误隔离
  2. 初始化内核地址空间
    1. Exercise 5.
    2. Question
    3. Challenge 1
    4. Challenge 2
  3. Reference

Mit6.828 Lab2: Part3 Kernel Address Space

JOS将32位的地址空间划分成两部分:用户环境,使用低地址部分;内核地址空间位于高地址空间。用户空间和内核地址空间的分隔由inc/memlayout.h中的ULIM指定,为内核保留大概256MB的虚拟地址空间。

权限和错误隔离

由于内核和用户进程只能访问各自的地址空间,所以我们必须在x86页表中使用访问权限位(Permission Bits)来使用户进程的代码只能访问用户地址空间,而不是内核地址空间。否则用户代码中的一些错误可能会覆写内核中的数据,最终导致内核的崩溃。

用户地址空间中的代码不能访问高于ULIM的地址空间,但是内核可以读写这部分空间。而内核和用户对于地址范围[UTOP, ULIM)有着相同的访问权限,那就是可以读取但是不可以写入。这一个部分的地址空间通常被用于把一些只读的内核数据结构暴露给用户地址空间的代码。在UTOP之下的地址范围是给用户进程使用的,用户环境会为用户空间的地址设置访问权限。

初始化内核地址空间

现在我们要建立UTOP之上的地址空间:这也是整个虚拟地址空间中的内核地址空间部分。inc/memlayout.h文件中已经向你展示了这部分地址空间的布局。你可以使用你刚刚编写的函数来建立内核地址空间布局。

Exercise 5.

Fill in the missing code in mem_init() after the call to check_page().

Your code should now pass the check_kern_pgdir() and check_page_installed_pgdir() checks.

首先我们要将始于UPAGES,长度为PTSIZE的虚拟地址空间映射到pages数组的物理地址空间:

1
boot_map_region(kern_pgdir, UPAGES, PTSIZE, PADDR(pages), PTE_U);

其中perm变量之所以设置为PTE_U,是因为这部分空间是kernel space和user space中的代码都能访问的,所以要设置PTE_U。

然后映射内核的堆栈区域,把由bootstack变量所标记的物理地址范围映射给内核的堆栈。内核堆栈的虚拟地址范围是[KSTACKTOP-PTSIZE, KSTACKTOP),不过要把这个范围划分成两部分:

  • [KSTACKTOP-KSTKSIZE, KSTACKTOP) 这部分映射关系加入的页表中。
  • [KSTACKTOP-PTSIZE, KSTACKTOP-KSTKSIZE) 这部分不进行映射。

这里面采用了保护页的做法,就是在规定的栈大小的后面,防止的是invalid的内存部分,这样当栈溢出的时候会出发错误,而不是覆盖掉更低地址的内存。

对这部分地址的访问权限是,kernel space 可以读写,user space 无权访问,所以代码如下:

1
boot_map_region(kern_pgdir, KSTACKTOP-KSTKSIZE, KSTKSIZE, PADDR(bootstack), PTE_W);

最后映射整个操作系统内核,虚拟地址范围是[KERNBASE, 2^32),物理地址范围是[0,2^32 - KERNBASE)。访问权限是,kernel space 可以读写,user space 无权访问,所以代码如下:

1
boot_map_region(kern_pgdir, KERNBASE, 0x10000000, 0, PTE_W );

最后内核虚拟地址空间映射如下:
vaddr to paddr

Question

2.What entries (rows) in the page directory have been filled in at this point? What addresses do they map and where do they point? In other words, fill out this table as much as possible:

可以在qemu中使用info pg命令展示当前的页表,包含所有映射内存的范围、权限、标识:
page table entries

Entry Base Virtual Address Points to (logically)
1023 0xfffff000 Page table for top 4MB of phys memory
.
992 0xf8000000
991 0xf7fff000
. ..
961 0xf0400000
960 0xf0000000(KERNBASE) kernel space
959 0xefc00000(KSTACKTOP-KSTSIZE) bootstack
957 0xef400000(UVPT) kern_pgdir
956 0xef000000(UPAGES) pages
. ? ?
. ? ?
. ? ?
2 0x00800000 ?
1 0x00400000 ?
0 0x00000000 [see next question]

3.We have placed the kernel and user environment in the same address space. Why will user programs not be able to read or write the kernel’s memory? What specific mechanisms protect the kernel memory?

因为页表的保护机制,规定了页表项的访问权限。Page Directory Entry和Page Table Entry中的低12位中保存了相关的权限位,包括PTE_UPTE_WPTE_U位表示用户特权,PTE_W位设置为1表示可写。根据Intel® 64 and IA-32 Architectures Software Developer’s Manual中,页目录项与页表项权限位设置与实际访问/读写权限的关系如下表所示:
page protection
在分页机制开启后,将虚拟内存映射到物理内存过程中,kernel会去检查权限位的设置,以实现保护。

4.What is the maximum amount of physical memory that this operating system can support? Why?

所有内存页都存储在pages数组中,pages数组存储在大小为PTSIZE的物理空间中,即4MB的物理内存空间,sizeof(struct PageInfo)得到的大小是8B,因此pages数组最大可以存储524288个页,每个页大小4KB,因此最大的物理空间:524288*4KB=2GB。因此jos最大支持2GB的内存。

5.How much space overhead is there for managing memory, if we actually had the maximum amount of physical memory? How is this overhead broken down?

分页机制中管理内存的开销源于页表以及PageInfo数组所占用的内存空间,PageInfo数组占用4MB的内存空间;Page Directory所占一个页的内存空间即4KB;Page Directory有1024项,每个页表所占用的内存空间为4KB,即所有的页表占用的内存空间为4MB。故总的内存开销是4MB+4MB+4KB=8MB+4KB。

  1. Revisit the page table setup in kern/entry.S and kern/entrypgdir.c. Immediately after we turn on paging, EIP is still a low number (a little over 1MB). At what point do we transition to running at an EIP above KERNBASE? What makes it possible for us to continue executing at a low EIP between when we enable paging and when we begin running at an EIP above KERNBASE? Why is this transition necessary?

kern/entry.S中的jmp *%eax语句在分页开启之后,改变EIP指针,使其指向高于KERNBASE的虚拟地址空间。

当分页开启后,使用的是kern/entrypgdir.c中设定的一个手写的静态初始化的页目录和页表,该页表将虚拟地址空间[0, 4MB)、[KERNBASE, KERNBASE+4MB)都映射到了物理地址空间[0, 4MB)中,所以在分页开启后,仍然可以使用低虚拟地址。

跳转是必要的,因为内核的虚拟地址空间位于高地址空间,之前在开启分页后仍然在低地址空间运行就是为了进入内核虚拟地址空间做准备,之后内核的所有命令都运行在高地址空间。

Challenge 1

We consumed many physical pages to hold the page tables for the KERNBASE mapping. Do a more space-efficient job using the PTE_PS (“Page Size”) bit in the page directory entries. This bit was not supported in the original 80386, but is supported on more recent x86 processors. You will therefore have to refer to Volume 3 of the current Intel manuals. Make sure you design the kernel to use this optimization only on processors that support it!

有Intel manual提供的信息:

3.7.3. Mixing 4-KByte and 4-MByte Pages When the PSE flag in CR4 is set, both 4-MByte pages and page tables for 4-KByte pages can be accessed from the same page directory. If the PSE flag is clear, only page tables for 4-KByte pages can be accessed (regardless of the setting of the PS flag in a page-directory entry). A typical example of mixing 4-KByte and 4-MByte pages is to place the operating system or executive’s kernel in a large page to reduce TLB misses and thus improve overall system performance. The processor maintains 4-MByte page entries and 4-KByte page entries in separate TLBs. So, placing often used code such as the kernel in a large page, frees up 4-KByte-page TLB entries for application programs and tasks.

混合使用4KB以及4MB的页的典型例子:将操作系统或者内核放到一个大页中去,减少TLB miss,处理器在不同的TLB中保存4KB的页、4MB的页。

要打开4M页表首先要设置cr4中的PSE标志。之后在若将页目录项中的7号位PS置为1则可将其设置为4M模式,其页目录项的页基址直接指向内存中大小为4MB的物理页,对于未设置PS标志的则与之前一样指向大小为4KB的页表。如果PSE标识位清零,无论页目录项中的PS位是否设置,只能使用二级页表访问4KB的页。
format of page directory entry

4MB的页目录项结构如上图所示,与4KB的页目录项和页表项的主要区别在于其中[13,21]位被设置为0,这是因为虚拟地址(如下图所示)中低21位[0,20]都要用被用来作为Offset,为了兼容性依然只有[0,12]位用于各种标记,因此[13,21]位就被保留为了0。
4MB linear address translation

正如Intel manual所言,经常将操作系统内核放到大页中去,减少TLB miss。故,我们将内核放入到4MB的物理页中。

具体实现
mem_init函数中设置cr4寄存器,使能PSE标志:

1
2
3
4
// Set Cr4 to enable PSE
uint32_t cr4 = rcr4();
cr4 |= CR4_PSE;
lcr4(cr4);

并修改KERNBASE以上区域的映射时设置页目录项标志PTE_PS

1
boot_map_region(kern_pgdir, KERNBASE, 0x10000000, 0, PTE_W | PTE_PS);

然后修改boot_map_region函数的代码,区分页目录项存在标志PTE_PS的情况:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
static void
boot_map_region(pde_t *pgdir, uintptr_t va, size_t size, physaddr_t pa, int perm)
{
// Fill this function in
uintptr_t vStep;
pte_t *ptep;
pde_t *pdep;
if (perm & PTE_PS){
for(vStep = 0; vStep < size; vStep+=PTSIZE) {
pdep = &pgdir[PDX(va + vStep)];
*pdep = pa | perm | PTE_P;
pa += PTSIZE;
}
} else {
for(vStep = 0; vStep < size; vStep+=PGSIZE) {
ptep = pgdir_walk(pgdir, (const void *)va+vStep, true);
if (ptep)
*ptep = pa | perm | PTE_P;
pa += PGSIZE;
}
}
}

为了防止check_va2pa出错,修改代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
static physaddr_t
check_va2pa(pde_t *pgdir, uintptr_t va)
{
pte_t *p;
pgdir = &pgdir[PDX(va)];
if (!(*pgdir & PTE_P))
return ~0;
p = (pte_t*) KADDR(PTE_ADDR(*pgdir));
if (!(p[PTX(va)] & PTE_P))
return ~0;
if (*pgdir & PTE_PS) {
return (*pgdir) & ~0x3fffff;
}
else
return PTE_ADDR(p[PTX(va)]);
}

最后注释掉check_kern_pgdir函数的执行,因为这个函数里面所有的检查都默认将页目录项当做二级映射的页目录项,懒得去修改。我们编译运行虚拟机,然后进入qemu,查看页目录的映射内容:
PSE enabled page directory
其中PDE[3bd]即为页目录的自映射,其中每一项对应着页目录中的每一项。PDE[3c0-3d0]PDE[3d1-3ff]即为KERNBASE之上,也就是刚才设置为了4MB页模式的目录项。可以看到这两个目录项下并没有PTE,并且对应的物理页恰好覆盖了物理内存的前256MB。

现在4MB的静态映射虽然已经成功了,但是由于JOS的代码中目前物理页分配完全基于针对4KB页的PageInfo数组和空闲页链表,无法直接进行4MB页的动态分配。如果要实现对4MB页的分配就需要将整个物理页管理的方式进行重写过于复杂了。[1]

Challenge 2

Extend the JOS kernel monitor with commands to:

  1. Display in a useful and easy-to-read format all of the physical page mappings (or lack thereof) that apply to a particular range of virtual/linear addresses in the currently active address space. For example, you might enter ‘showmappings 0x3000 0x5000’ to display the physical page mappings and corresponding permission bits that apply to the pages at virtual addresses 0x3000, 0x4000, and 0x5000.

Explicitly set, clear, or change the permissions of any mapping in the current address space.
Dump the contents of a range of memory given either a virtual or physical address range. Be sure the dump code behaves correctly when the range extends across page boundaries!
Do anything else that you think might be useful later for debugging the kernel. (There’s a good chance it will be!)

Reference

[1] https://github.com/jtyuan/JOS2014/tree/lab2

支持一下
扫一扫,支持buwei