内核地址空间 Kernel Address Space
JOS把32-bit 线性地址分成了两个部分,其中用户环境(进行运行环境)通常占据低地址空间,称作用户地址空间。而操作系统内核总是占据高地址的部分,即内核地址空间。两部分的分界线是定义在 inc/memlayout.h
中的宏 ULIM
。JOS为内核保留了接近256M的虚拟地址空间,这也就解释了为什么需要给kernel一个如此高的link address,否则的话kernel的虚拟地址空间就没有足够的空间用来映射用户地址空间。
为什么上面说内核地址空间还要保留一部分用于用户地址空间呢?
一个简单的例子是某程序需要打开某个文件,由于程序只能够读写自己的虚拟地址空间,是无法直接访问位于磁盘或其他存储设备上面的文件的,它会发起一个open系统调用,产生一个异常,将控制权交给内核。
内核地址由于和用户地址是相互隔离的,因此二者之间传递信息需要先进行一个操作:将用户地址空间映射到内存地址空间。
这之后内核便能够正确地读取用户传递给系统调用的参数,打开正确的文件;而后将返回值正确地返回到用户空间。
memlayout.h
/*
* Virtual memory map: Permissions
* kernel/user
*
* 4 Gig --------> +------------------------------+
* | | RW/--
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
* : . :
* : . :
* : . :
* |~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~| RW/--
* | | RW/--
* | Remapped Physical Memory | RW/--
* | | RW/--
* KERNBASE, ----> +------------------------------+ 0xf0000000 --+
* KSTACKTOP | CPU0's Kernel Stack | RW/-- KSTKSIZE |
* | - - - - - - - - - - - - - - -| |
* | Invalid Memory (*) | --/-- KSTKGAP |
* +------------------------------+ |
* | CPU1's Kernel Stack | RW/-- KSTKSIZE |
* | - - - - - - - - - - - - - - -| PTSIZE
* | Invalid Memory (*) | --/-- KSTKGAP |
* +------------------------------+ |
* : . : |
* : . : |
* MMIOLIM ------> +------------------------------+ 0xefc00000 --+
* | Memory-mapped I/O | RW/-- PTSIZE
* ULIM, MMIOBASE --> +------------------------------+ 0xef800000
* | Cur. Page Table (User R-) | R-/R- PTSIZE
* UVPT ----> +------------------------------+ 0xef400000
* | RO PAGES | R-/R- PTSIZE
* UPAGES ----> +------------------------------+ 0xef000000
* | RO ENVS | R-/R- PTSIZE
* UTOP,UENVS ------> +------------------------------+ 0xeec00000
* UXSTACKTOP -/ | User Exception Stack | RW/RW PGSIZE
* +------------------------------+ 0xeebff000
* | Empty Memory (*) | --/-- PGSIZE
* USTACKTOP ---> +------------------------------+ 0xeebfe000
* | Normal User Stack | RW/RW PGSIZE
* +------------------------------+ 0xeebfd000
* | |
* | |
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
* . .
* . .
* . .
* |~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~|
* | Program Data & Heap |
* UTEXT --------> +------------------------------+ 0x00800000
* PFTEMP -------> | Empty Memory (*) | PTSIZE
* | |
* UTEMP --------> +------------------------------+ 0x00400000 --+
* | Empty Memory (*) | |
* | - - - - - - - - - - - - - - -| |
* | User STAB Data (optional) | PTSIZE
* USTABDATA ----> +------------------------------+ 0x00200000 |
* | Empty Memory (*) | |
* 0 ------------> +------------------------------+ --+
*
* (*) Note: The kernel ensures that "Invalid Memory" is *never* mapped.
* "Empty Memory" is normally unmapped, but user programs may map pages
* there if desired. JOS user programs map pages temporarily at UTEMP.
*/
Permissions and Fault Isolation
在每个进程的地址空间中,都包含了用户内存和内核内存2。(这是因为进程的执行会遇到内核态和用户态的切换,如果没有在连续空间内,就要涉及切换页表的操作,是很耗时的)。这就使得我们需要控制页表中的访问权限标志位(permission bits)来实现用户进程的代码只能访问用户地址空间。
注意可写标志位(PTE_W)同时影响用户代码和内核代码
- 地址 > ULIM:用户进程不能够访问任何,但是内核可读可写。
- UTOP <= 地址 < ULIM:用户进程和内核都是只读。这部分地址是暴露一些只读的内核数据结构给用户进程。
- 地址 < UTOP:用户进程可以访问,修改这部分地址空间的内容。
Initializing the Kernel Address Space
现在设置一下 UTOP
之上的地址空间:这也是整个虚拟地址空间中的内核地址空间部分。inc/memlayout.h
文件中已经展示了这部分地址空间的布局。使用之前编写的函数来设置这些地址的布局。
Exercise5:
- 完善调用
cehck_page()
之后的代码,使其能够通过check_kern_pgdir() and check_page_installed_pgdir()
的验证
Question
Q:到目前为止页目录表中已经包含多少有效页目录项?他们都映射到哪里?换句话说就是尽可能完善下面这个表(ps: 页目录的每一项指示一个页表,一个页表项管理4KB的页面,所以一个页目录项管理1024 * 4KB = 4MB的地址空间)
Entry | Base Virtual Address | Points to (logically): |
---|---|---|
1023=0x3ff | ? | Page table for top 4MB of phys memory |
1022 | ? | ? |
. | ? | ? |
. | ? | ? |
960=0x3c0 | 0xf0000000=KERNBASE | 指向pyhs=0x00000000,和上面所有直到1023的表项存储了256MB的kernel |
. | ? | ? |
0x3BF | 0xefc00000 | 指向bootstack |
0x3BE | ? | ? |
0x3BD | 0xef400000 | 指向kern_pgdir |
0x3BC | 0xef000000 | 指向pages数组 |
. | ? | ? |
. | ? | ? |
2 | 0x00800000 | ? |
1 | 0x00400000 | ? |
0 | 0x00000000 | [see next question] |
Q:我们把kernel和user environment放在一个相同的地址空间中。为什么用户程序不能读取,写入内核的内存空间?用什么机制保护内核的地址范围。
A:通过设置权限标志位控制了每段内存空间的访问权限。
Q:这个操作系统最多可以挂载多大的物理内存,为什么?3(存疑)
A:每个struct PageInfo的大小是8B,所以存放它们的大小为4MB的UPAGES可以存放512个此struct。对应512个页面共512 * 4KB = 2GB,所以可以挂载的物理内存大小是2GB。ps:从KERNBASE开始,一共有256MB的虚拟地址空间留给物理地址一一映射,所以内核最多能使用的大小是256MB。
Q:如果挂载了最大数量的物理内存,那么管理内存有多少空间开销?这个开销是如何分解的?3
A:2GB的内存,共可以划分为2GB/4KB=512K个页面。总共需要 6MB + 4KB。具体分析如下:
- 一个页面需要一个struct PageInfo:共需 512K * 8B(sizeof(struct PageInfo)) = 4MB
- 512K个页面需要512个页表:共需 512 * 4KB(一个页表1024 * 32bits) = 2MB
- 512个页表需要1个页目录:共需 1 * 4KB(一个页目录1024 * 32bits) = 4KB
Q:回顾entry.S文件中,当分页机制开启时,寄存器EIP的值仍旧是一个小的值。在哪个位置代码才开始运行在高于KERNBASE的虚拟地址空间中的?当程序位于开启分页之后到运行在KERNBASE之上这之间的时候,EIP的值是小的值,怎么保证可以把这个值转换为真实物理地址的?1
A:在entry.S文件中有一个指令 jmp *%eax
,这个指令要完成跳转,就会重新设置EIP的值,把它设置为寄存器eax中的值,而这个值是大于KERNBASE的,所以就完成了EIP从小的值到大于KERNBASE的值的转换。在 kern/entrypgdir.c/[entry_pgdir]
这个页目录中,0号页目录项把虚拟地址空间[0, 4MB)映射到物理地址空间[0, 4MB)上,所以当访问位于[0, 4MB)之间的虚拟地址时,可以把它们转换为物理地址。开启分页机制到跳转到高地址的过程可以看lab1-exe9-entry.S