Lab 2: Memory management
做这个实验之前首先需要知道什么是分页。分段在这个实验里面没用到过。
前面是一大堆教你怎么获取lab2资源的,我不知道怎么弄,后来乱搞了一下,就把lab1的覆盖掉了,变成了lab2。这个我相信就我不懂。
Part 1: Physical Page Management
第一个是物理页面管理。
boot_alloc() //这个是系统加载前做个物理内存分配,也就初始化用了一下
mem_init() // 顾名思义,内存初始化
page_init() //页面初始化
page_alloc() //真正的分配物理页面
page_free() // 页面释放
首先,我们先观察一下
init.c
文件,这个是内核初始化调用的,上个实验已经清楚了。
init.c
void
i386_init(void)
{
extern char edata[], end[]; //这是内核数据 也就是加载ELF 里面的BSS段,全局变量和静态变量。
// Before doing anything else, complete the ELF loading process.
// Clear the uninitialized global data (BSS) section of our program.
// This ensures that all static/global variables start out zero.
memset(edata, 0, end - edata);//初始化为0
// Initialize the console.
// Can't call cprintf until after we do this!
//这个应该知道吧,上个实验讲过,初始化printf之内的
cons_init();
cprintf("6828 decimal is %o octal!\n", 6828); //不说了因为切换到lab2 又开始输出XX可以回去改一下。改也没事,这个实验用不上。
// Lab 2 memory management initialization functions
mem_init(); //这个就是这次实验的核心,主要就是这个 后面的就不管了。
// Drop into the kernel monitor.
while (1)
monitor(NULL);
}
看
mem_init()
我们先看看
kern/pmap.h
和
inc/memlayout.h
里面有什么,没看懂就算了,我也没看懂,大致知道有些啥就行了。补充一个
inc/mmu.h
不用知道具体实现,但是一些东西后面用的超多。
mmu.h
#ifndef JOS_INC_MMU_H
#define JOS_INC_MMU_H
/*
* This file contains definitions for the x86 memory management unit (MMU),
* including paging- and segmentation-related data structures and constants,
* the %cr0, %cr4, and %eflags registers, and traps.
* 这个文件 定义了X86 的内存管理单元,包括分段报函数,还有一些啥寄存器,和陷阱,先不管这些。
*/
/*
*
* Part 1. Paging data structures and constants.
*重点就是这个页的结构 ,主要就是这个 其他的以后再说
*/
// A linear address 'la' has a three-part structure as follows:
// 三部分结构 一个 链接地址 页目录 页表 偏移地址 ,如果这三个不知道,亲!这边建议重修操作系统和计算机组成原理。 然后 PDX PTX PGOFF 知道是做啥的了吧
// +--------10------+-------10-------+---------12----------+
// | Page Directory | Page Table | Offset within Page |
// | Index | Index | |
// +----------------+----------------+---------------------+
// \--- PDX(la) --/ \--- PTX(la) --/ \---- PGOFF(la) ----/
// \---------- PGNUM(la) ----------/
//
// The PDX, PTX, PGOFF, and PGNUM macros decompose linear addresses as shown.
// To construct a linear address la from PDX(la), PTX(la), and PGOFF(la),
// use PGADDR(PDX(la), PTX(la), PGOFF(la)).
// page number field of address
#define PGNUM(la) (((uintptr_t) (la)) >> PTXSHIFT)
// page directory index
#define PDX(la) ((((uintptr_t) (la)) >> PDXSHIFT) & 0x3FF)
// page table index
#define PTX(la) ((((uintptr_t) (la)) >> PTXSHIFT) & 0x3FF)
// offset in pag
#define PGOFF(la) (((uintptr_t) (la)) & 0xFFF)
// construct linear address from indexes and offset 通过3个值 构建虚拟地址
#define PGADDR(d, t, o) ((void*) ((d) << PDXSHIFT | (t) << PTXSHIFT | (o)))
// Page directory and page table constants. 页目录其实就是一个页表 下面是他们包含了啥
#define NPDENTRIES 1024 // page directory entries per page directory
#define NPTENTRIES 1024 // page table entries per page table
#define PGSIZE 4096 // bytes mapped by a page 一个页的大小
#define PGSHIFT 12 // log2(PGSIZE)
#define PTSIZE (PGSIZE*NPTENTRIES) // bytes mapped by a page directory entry
#define PTSHIFT 22 // log2(PTSIZE)
#define PTXSHIFT 12 // offset of PTX in a linear address
#define PDXSHIFT 22 // offset of PDX in a linear address
#define PTE_P 0x001 // Present 对应物理页面是否存在
#define PTE_W 0x002 // Writeable 对应物理页面是否可写
#define PTE_U 0x004 // User 对应物理页面用户态是否可以访问
#define PTE_PWT 0x008 // Write-Through 对应物理页面在写入时是否写透(即向更低级储存设备写入)
#define PTE_PCD 0x010 // Cache-Disable 对应物理页面是否能被放入高速缓存
#define PTE_A 0x020 // Accessed 对应物理页面是否被访问
#define PTE_D 0x040 // Dirty 对应物理页面是否被写
#define PTE_PS 0x080 // Page Size 对应物理页面的页面大小
#define PTE_G 0x100 // Global 这个我也不知道
// The PTE_AVAIL bits aren't used by the kernel or interpreted by the
// hardware, so user processes are allowed to set them arbitrarily.
#define PTE_AVAIL 0xE00 // Available for software use
// Flags in PTE_SYSCALL may be used in system calls. (Others may not.) 这两个没用到过
#define PTE_SYSCALL (PTE_AVAIL | PTE_P | PTE_W | PTE_U)
// Address in page table or page directory entry //取页表入口地址
#define PTE_ADDR(pte) ((physaddr_t) (pte) & ~0xFFF)0xFFF
//后面的东西 用的比较少,感兴趣的自己去送人头吧
// Control Register flags
#define CR0_PE 0x00000001 // Protection Enable
#define CR0_MP 0x00000002 // Monitor coProcessor
#define CR0_EM 0x00000004 // Emulation
#define CR0_TS 0x00000008 // Task Switched
#define CR0_ET 0x00000010 // Extension Type
#define CR0_NE 0x00000020 // Numeric Errror
#define CR0_WP 0x00010000 // Write Protect
#define CR0_AM 0x00040000 // Alignment Mask
#define CR0_NW 0x20000000 // Not Writethrough
#define CR0_CD 0x40000000 // Cache Disable
#define CR0_PG 0x80000000 // Paging
#define CR4_PCE 0x00000100 // Performance counter enable
#define CR4_MCE 0x00000040 // Machine Check Enable
#define CR4_PSE 0x00000010 // Page Size Extensions
#define CR4_DE 0x00000008 // Debugging Extensions
#define CR4_TSD 0x00000004 // Time Stamp Disable
#define CR4_PVI 0x00000002 // Protected-Mode Virtual Interrupts
#define CR4_VME 0x00000001 // V86 Mode Extensions
// Eflags register
#define FL_CF 0x00000001 // Carry Flag
#define FL_PF 0x00000004 // Parity Flag
#define FL_AF 0x00000010 // Auxiliary carry Flag
#define FL_ZF 0x00000040 // Zero Flag
#define FL_SF 0x00000080 // Sign Flag
#define FL_TF 0x00000100 // Trap Flag
#define FL_IF 0x00000200 // Interrupt Flag
#define FL_DF 0x00000400 // Direction Flag
#define FL_OF 0x00000800 // Overflow Flag
#define FL_IOPL_MASK 0x00003000 // I/O Privilege Level bitmask
#define FL_IOPL_0 0x00000000 // IOPL == 0
#define FL_IOPL_1 0x00001000 // IOPL == 1
#define FL_IOPL_2 0x00002000 // IOPL == 2
#define FL_IOPL_3 0x00003000 // IOPL == 3
#define FL_NT 0x00004000 // Nested Task
#define FL_RF 0x00010000 // Resume Flag
#define FL_VM 0x00020000 // Virtual 8086 mode
#define FL_AC 0x00040000 // Alignment Check
#define FL_VIF 0x00080000 // Virtual Interrupt Flag
#define FL_VIP 0x00100000 // Virtual Interrupt Pending
#define FL_ID 0x00200000 // ID flag
// Page fault error codes
#define FEC_PR 0x1 // Page fault caused by protection violation
#define FEC_WR 0x2 // Page fault caused by a write
#define FEC_U 0x4 // Page fault occured while in user mode
/*
*
* Part 2. Segmentation data structures and constants.
*
*/
#ifdef __ASSEMBLER__
/*
* Macros to build GDT entries in assembly.
*/
#define SEG_NULL \
.word 0, 0; \
.byte 0, 0, 0, 0
#define SEG(type,base,lim) \
.word (((lim) >> 12) & 0xffff), ((base) & 0xffff); \
.byte (((base) >> 16) & 0xff), (0x90 | (type)), \
(0xC0 | (((lim) >> 28) & 0xf)), (((base) >> 24) & 0xff)
#else // not __ASSEMBLER__
#include <inc/types.h>
// Segment Descriptors
struct Segdesc {
unsigned sd_lim_15_0 : 16; // Low bits of segment limit
unsigned sd_base_15_0 : 16; // Low bits of segment base address
unsigned sd_base_23_16 : 8; // Middle bits of segment base address
unsigned sd_type : 4; // Segment type (see STS_ constants)
unsigned sd_s : 1; // 0 = system, 1 = application
unsigned sd_dpl : 2; // Descriptor Privilege Level
unsigned sd_p : 1; // Present
unsigned sd_lim_19_16 : 4; // High bits of segment limit
unsigned sd_avl : 1; // Unused (available for software use)
unsigned sd_rsv1 : 1; // Reserved
unsigned sd_db : 1; // 0 = 16-bit segment, 1 = 32-bit segment
unsigned sd_g : 1; // Granularity: limit scaled by 4K when set
unsigned sd_base_31_24 : 8; // High bits of segment base address
};
// Null segment
#define SEG_NULL { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }
// Segment that is loadable but faults when used
#define SEG_FAULT { 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0 }
// Normal segment
#define SEG(type, base, lim, dpl) \
{ ((lim) >> 12) & 0xffff, (base) & 0xffff, ((base) >> 16) & 0xff, \
type, 1, dpl, 1, (unsigned) (lim) >> 28, 0, 0, 1, 1, \
(unsigned) (base) >> 24 }
#define SEG16(type, base, lim, dpl) (struct Segdesc) \
{ (lim) & 0xffff, (base) & 0xffff, ((base) >> 16) & 0xff, \
type, 1, dpl, 1, (unsigned) (lim) >> 16, 0, 0, 1, 0, \
(unsigned) (base) >> 24 }
#endif /* !__ASSEMBLER__ */
// Application segment type bits
#define STA_X 0x8 // Executable segment
#define STA_E 0x4 // Expand down (non-executable segments)
#define STA_C 0x4 // Conforming code segment (executable only)
#define STA_W 0x2 // Writeable (non-executable segments)
#define STA_R 0x2 // Readable (executable segments)
#define STA_A 0x1 // Accessed
// System segment type bits
#define STS_T16A 0x1 // Available 16-bit TSS
#define STS_LDT 0x2 // Local Descriptor Table
#define STS_T16B 0x3 // Busy 16-bit TSS
#define STS_CG16 0x4 // 16-bit Call Gate
#define STS_TG 0x5 // Task Gate / Coum Transmitions
#define STS_IG16 0x6 // 16-bit Interrupt Gate
#define STS_TG16 0x7 // 16-bit Trap Gate
#define STS_T32A 0x9 // Available 32-bit TSS
#define STS_T32B 0xB // Busy 32-bit TSS
#define STS_CG32 0xC // 32-bit Call Gate
#define STS_IG32 0xE // 32-bit Interrupt Gate
#define STS_TG32 0xF // 32-bit Trap Gate
/*
*
* Part 3. Traps.
*
*/
#ifndef __ASSEMBLER__
// Task state segment format (as described by the Pentium architecture book)
struct Taskstate {
uint32_t ts_link; // Old ts selector
uintptr_t ts_esp0; // Stack pointers and segment selectors
uint16_t ts_ss0; // after an increase in privilege level
uint16_t ts_padding1;
uintptr_t ts_esp1;
uint16_t ts_ss1;
uint16_t ts_padding2;
uintptr_t ts_esp2;
uint16_t ts_ss2;
uint16_t ts_padding3;
physaddr_t ts_cr3; // Page directory base
uintptr_t ts_eip; // Saved state from last task switch
uint32_t ts_eflags;
uint32_t ts_eax; // More saved state (registers)
uint32_t ts_ecx;
uint32_t ts_edx;
uint32_t ts_ebx;
uintptr_t ts_esp;
uintptr_t ts_ebp;
uint32_t ts_esi;
uint32_t ts_edi;
uint16_t ts_es; // Even more saved state (segment selectors)
uint16_t ts_padding4;
uint16_t ts_cs;
uint16_t ts_padding5;
uint16_t ts_ss;
uint16_t ts_padding6;
uint16_t ts_ds;
uint16_t ts_padding7;
uint16_t ts_fs;
uint16_t ts_padding8;
uint16_t ts_gs;
uint16_t ts_padding9;
uint16_t ts_ldt;
uint16_t ts_padding10;
uint16_t ts_t; // Trap on task switch
uint16_t ts_iomb; // I/O map base address
};
// Gate descriptors for interrupts and traps
struct Gatedesc {
unsigned gd_off_15_0 : 16; // low 16 bits of offset in segment
unsigned gd_sel : 16; // segment selector
unsigned gd_args : 5; // # args, 0 for interrupt/trap gates
unsigned gd_rsv1 : 3; // reserved(should be zero I guess)
unsigned gd_type : 4; // type(STS_{TG,IG32,TG32})
unsigned gd_s : 1; // must be 0 (system)
unsigned gd_dpl : 2; // descriptor(meaning new) privilege level
unsigned gd_p : 1; // Present
unsigned gd_off_31_16 : 16; // high bits of offset in segment
};
// Set up a normal interrupt/trap gate descriptor.
// - istrap: 1 for a trap (= exception) gate, 0 for an interrupt gate.
// see section 9.6.1.3 of the i386 reference: "The difference between
// an interrupt gate and a trap gate is in the effect on IF (the
// interrupt-enable flag). An interrupt that vectors through an
// interrupt gate resets IF, thereby preventing other interrupts from
// interfering with the current interrupt handler. A subsequent IRET
// instruction restores IF to the value in the EFLAGS image on the
// stack. An interrupt through a trap gate does not change IF."
// - sel: Code segment selector for interrupt/trap handler
// - off: Offset in code segment for interrupt/trap handler
// - dpl: Descriptor Privilege Level -
// the privilege level required for software to invoke
// this interrupt/trap gate explicitly using an int instruction.
#define SETGATE(gate, istrap, sel, off, dpl) \
{ \
(gate).gd_off_15_0 = (uint32_t) (off) & 0xffff; \
(gate).gd_sel = (sel); \
(gate).gd_args = 0; \
(gate).gd_rsv1 = 0; \
(gate).gd_type = (istrap) ? STS_TG32 : STS_IG32; \
(gate).gd_s = 0; \
(gate).gd_dpl = (dpl); \
(gate).gd_p = 1; \
(gate).gd_off_31_16 = (uint32_t) (off) >> 16; \
}
// Set up a call gate descriptor.
#define SETCALLGATE(gate, sel, off, dpl) \
{ \
(gate).gd_off_15_0 = (uint32_t) (off) & 0xffff; \
(gate).gd_sel = (sel); \
(gate).gd_args = 0; \
(gate).gd_rsv1 = 0; \
(gate).gd_type = STS_CG32; \
(gate).gd_s = 0; \
(gate).gd_dpl = (dpl); \
(gate).gd_p = 1; \
(gate).gd_off_31_16 = (uint32_t) (off) >> 16; \
}
// Pseudo-descriptors used for LGDT, LLDT and LIDT instructions.
struct Pseudodesc {
uint16_t pd_lim; // Limit
uint32_t pd_base; // Base address
} __attribute__ ((packed));
#endif /* !__ASSEMBLER__ */
#endif /* !JOS_INC_MMU_H */
memlayout.h
#ifndef JOS_INC_MEMLAYOUT_H
#define JOS_INC_MEMLAYOUT_H
#ifndef __ASSEMBLER__
#include <inc/types.h>
#include <inc/mmu.h>
#endif /* not __ASSEMBLER__ */
/*
* This file contains definitions for memory management in our OS,
* which are relevant to both the kernel and user-mode software.
*/
// Global descriptor numbers 一些全局描述用的东西,下面好像没怎么用到过
#define GD_KT 0x08 // kernel text
#define GD_KD 0x10 // kernel data
#define GD_UT 0x18 // user text
#define GD_UD 0x20 // user data
#define GD_TSS0 0x28 // Task segment selector for CPU 0
/*
1. 将虚拟内存共计4G的空间的最高位置的256M预留,用来作为物理内存的映射,在JOS的内存使用中不会直接使用这段空间。在JOS中使用的某个页面,会通过mmu映射到这段空间,再通过映射和实际的物理内存相对应。这也是JOS最多只能管理256M物理内存的原因。 (这我现在还没理解什么意思,映射难道不是通过页表吗?)
2. ULIM是区分内核和用户空间的位置。该位置以上为内核空间,用户程序不可见;而紧随其下的空间保存了用户空间的虚拟页表(UVPT)与环境参数,然后是异常处理栈,再其下为用户栈,向下增长。
3. 用户的程序数据与堆的位置从UTEXT=0x00800000=8M处开始。其下用于用户程序的临时页面映射时使用。同时避开了最下面的1M空间,因为该空间内640K-1M处为系统预留空间,无法使用,因此0-640K的内存与其上无法连续,使用起来会比较复杂。
4. 用于用户临时页面映射的空间为4M-8M处。而8M位置向下的4K为PFTEMP的空间,用于用户页面分配出错(page-fault)处理时作为映射空间。
5. 内核栈大小为KSTKSIZE=(8*PGSIZE)=32KB.
*/
/*这个是虚拟内存,映射的时候会用上
* 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.
*/
// All physical memory mapped at this address 所有的物理内存映射在此地址
#define KERNBASE 0xF0000000
// At IOPHYSMEM (640K) there is a 384K hole for I/O. From the kernel,
// IOPHYSMEM can be addressed at KERNBASE + IOPHYSMEM. The hole ends
// at physical address EXTPHYSMEM. 这个 就是上次实验说的 空洞
#define IOPHYSMEM 0x0A0000
#define EXTPHYSMEM 0x100000
// Kernel stack. 这个是栈,后面用的上的
#define KSTACKTOP KERNBASE
#define KSTKSIZE (8*PGSIZE) // size of a kernel stack
#define KSTKGAP (8*PGSIZE) // size of a kernel stack guard
// Memory-mapped IO.
#define MMIOLIM (KSTACKTOP - PTSIZE)
#define MMIOBASE (MMIOLIM - PTSIZE)
#define ULIM (MMIOBASE)
/*
* User read-only mappings! Anything below here til UTOP are readonly to user.
* They are global pages mapped in at env allocation time.
*/
// User read-only virtual page table (see 'uvpt' below)
#define UVPT (ULIM - PTSIZE)
// Read-only copies of the Page structures
#define UPAGES (UVPT - PTSIZE)
// Read-only copies of the global env structures
#define UENVS (UPAGES - PTSIZE)
/*
* Top of user VM. User can manipulate VA from UTOP-1 and down!
*/
// 这个地方就是用户态了,也不知道具体有什么用,我现在就知道大致分布,不知道后面实验会不会讲
// Top of user-accessible VM
#define UTOP UENVS
// Top of one-page user exception stack
#define UXSTACKTOP UTOP
// Next page left invalid to guard against exception stack overflow; then:
// Top of normal user stack
#define USTACKTOP (UTOP - 2*PGSIZE)
// Where user programs generally begin
#define UTEXT (2*PTSIZE)
// Used for temporary page mappings. Typed 'void*' for convenience
#define UTEMP ((void*) PTSIZE)
// Used for temporary page mappings for the user page-fault handler
// (should not conflict with other temporary page mappings)
#define PFTEMP (UTEMP + PTSIZE - PGSIZE)
// The location of the user-level STABS data structure
#define USTABDATA (PTSIZE / 2)
#ifndef __ASSEMBLER__
//下面这两个 一个 是页目录 一个页表
typedef uint32_t pte_t;
typedef uint32_t pde_t;
#if JOS_USER
/*
* The page directory entry corresponding to the virtual address range
* [UVPT, UVPT + PTSIZE) points to the page directory itself. Thus, the page
* directory is treated as a page table as well as a page directory.
*
* One result of treating the page directory as a page table is that all PTEs
* can be accessed through a "virtual page table" at virtual address UVPT (to
* which uvpt is set in lib/entry.S). The PTE for page number N is stored in
* uvpt[N]. (It's worth drawing a diagram of this!)
*
* A second consequence is that the contents of the current page directory
* will always be available at virtual address (UVPT + (UVPT >> PGSHIFT)), to
* which uvpd is set in lib/entry.S.
*/
extern volatile pte_t uvpt[]; // VA of "virtual page table"
extern volatile pde_t uvpd[]; // VA of current page directory
#endif
/*
* Page descriptor structures, mapped at UPAGES.
* Read/write to the kernel, read-only to user programs.
*
* Each struct PageInfo stores metadata for one physical page.
* Is it NOT the physical page itself, but there is a one-to-one
* correspondence between physical pages and struct PageInfo's.
* You can map a struct PageInfo * to the corresponding physical address
* with page2pa() in kern/pmap.h.
*/
//页 的数据结构
struct PageInfo {
// Next page on the free list.下一页
struct PageInfo *pp_link;
// pp_ref is the count of pointers (usually in page table entries)
// to this page, for pages allocated using page_alloc.
// Pages allocated at boot time using pmap.c's
// boot_alloc do not have valid reference count fields.
// 页表计数器
uint16_t pp_ref;
};
#endif /* !__ASSEMBLER__ */
#endif /* !JOS_INC_MEMLAYOUT_H */
kern/pmap.h
/* See COPYRIGHT for copyright information. */
#ifndef JOS_KERN_PMAP_H
#define JOS_KERN_PMAP_H
#ifndef JOS_KERNEL
# error "This is a JOS kernel header; user programs should not #include it"
#endif
#include <inc/memlayout.h>
#include <inc/assert.h>
//这个几个扩展变量范围到了具体定义再说
extern char bootstacktop[], bootstack[];
extern struct PageInfo *pages;
extern size_t npages;
extern pde_t *kern_pgdir;
/* This macro takes a kernel virtual address -- an address that points above
* KERNBASE, where the machine's maximum 256MB of physical memory is mapped --
* and returns the corresponding physical address. It panics if you pass it a
* non-kernel virtual address. 将虚拟地址转换成物理地址
*/
#define PADDR(kva) _paddr(__FILE__, __LINE__, kva)
static inline physaddr_t
_paddr(const char *file, int line, void *kva)
{//具体分析不过来告辞
if ((uint32_t)kva < KERNBASE)
_panic(file, line, "PADDR called with invalid kva %08lx", kva);
return (physaddr_t)kva - KERNBASE;
}
/* This macro takes a physical address and returns the corresponding kernel
* virtual address. It panics if you pass an invalid physical address. */
//这个是物理地址转换成虚拟地址
#define KADDR(pa) _kaddr(__FILE__, __LINE__, pa)
static inline void*
_kaddr(const char *file, int line, physaddr_t pa)
{
if (PGNUM(pa) >= npages)
_panic(file, line, "KADDR called with invalid pa %08lx", pa);
return (void *)(pa + KERNBASE);
}
enum {
// For page_alloc, zero the returned physical page.
ALLOC_ZERO = 1<<0,
};
// 后面就是几个函数的声明,后面会看到的
void mem_init(void);
void page_init(void);
struct PageInfo *page_alloc(int alloc_flags);
void page_free(struct PageInfo *pp);
int page_insert(pde_t *pgdir, struct PageInfo *pp, void *va, int perm);
void page_remove(pde_t *pgdir, void *va);
struct PageInfo *page_lookup(pde_t *pgdir, void *va, pte_t **pte_store);
void page_decref(struct PageInfo *pp);
void tlb_invalidate(pde_t *pgdir, void *va);
static inline physaddr_t
page2pa(struct PageInfo *pp)
{ //将 PagaInfo 转换成真正的物理地址
return (pp - pages) << PGSHIFT;
}
static inline struct PageInfo*
pa2page(physaddr_t pa)
{ // 或得物理地址的数据结构
if (PGNUM(pa) >= npages)
panic("pa2page called with invalid pa");
return &pages[PGNUM(pa)];
}
static inline void*
page2kva(struct PageInfo *pp)
{ //将页的数据结构转换成虚拟地址
return KADDR(page2pa(pp));
}
pte_t *pgdir_walk(pde_t *pgdir, const void *va, int create);
#endif /* !JOS_KERN_PMAP_H */
接下来我们就开始看内存怎么初始化的了。这个时候就要打开
kern/pmap.c
,看里面的
mem_int()
;
我们一段一段的来。
先看看定义了啥
// These variables are set by i386_detect_memory()
size_t npages; // Amount of physical memory (in pages) 物理内存的页数
static size_t npages_basemem; // Amount of base memory (in pages) basemem的页数
// These variables are set in mem_init() 这几个变量就是原本pmap.h扩展的那几个
pde_t *kern_pgdir; // Kernel's initial page directory 内核初始化页目录
struct PageInfo *pages; // Physical page state array 物理内存页表数组
static struct PageInfo *page_free_list; // Free list of physical pages 空闲页表描述结构体指针
然后我们直接跟着
mem_init()
的过程走。
uint32_t cr0; //定义了两个变量,干啥的还不清楚接着走
size_t n;
// Find out how much memory the machine has (npages & npages_basemem).
i386_detect_memory(); //这个是查看有多少个页 还有个页基础内存 这个函数并没有要我们实现的意思就不管他了,看看,也就是帮我们查看有多少内存,不过不知道为啥这个查出来只有 128 M 少了一半。
// Remove this line when you're ready to test this function.
// panic("mem_init: This function is not finished\n"); 这个注释就行了...
后面运行了这个
boot_alloc
作用很明显,就是创建一个页目录。
//
// create initial page directory.
kern_pgdir = (pde_t *) boot_alloc(PGSIZE);
memset(kern_pgdir, 0, PGSIZE);
boot_alloc()
// This simple physical memory allocator is used only while JOS is setting
// up its virtual memory system. page_alloc() is the real allocator.
// 这个只是简单的物理内存分配,在建立虚拟存储系统的时候使用,page_alloc 才是真正的内存分配
// If n>0, allocates enough pages of contiguous physical memory to hold 'n'
// bytes. Doesn't initialize the memory. Returns a kernel virtual address.
//当n>0 分配一个n字节的内存内存,返回一个虚拟地址
// If n==0, returns the address of the next free page without allocating
// anything. 如果n==0 返回 下一个空闲的页啥都不做
//
// If we're out of memory, boot_alloc should panic. 如果超出内存 就panic
// This function may ONLY be used during initialization, 函数只用于初始化
// before the page_free_list list has been set up.
static void *
boot_alloc(uint32_t n)
{
static char *nextfree; // virtual address of next byte of free memory
char *result;
// Initialize nextfree if this is the first time.
// 'end' is a magic symbol automatically generated by the linker,
// which points to the end of the kernel's bss segment:
// the first virtual address that the linker did *not* assign
// to any kernel code or global variables. 在这之前,通过ELF 文件我们已经加载了一部分内存
//所以我们如果是第一次分配内存,就要先找到上一次的,没有要我们实现他已经帮我们写好了
if (!nextfree) {
extern char end[];
nextfree = ROUNDUP((char *) end, PGSIZE);
}
// Allocate a chunk large enough to hold 'n' bytes, then update
// nextfree. Make sure nextfree is kept aligned
// to a multiple of PGSIZE. 分配n 字节,分配的空间要是PGSIZE的倍数。
//
// LAB 2: Your code here.
result = nextfree;
nextfree=ROUNDUP(nextfree+n,PGSIZE);
if((uint32_t)nextfree - KERNBASE > (npages*PGSIZE))//如果分配超出内存panic
panic("Out of memory!\n");
return result; //没有就返回这个
}
看代码实现还是挺容易理解的。
kern_pgdir = (pde_t *) boot_alloc(PGSIZE)
这句就相当于直接在后面开了一个
PGSIZE
大小的作为初始化页目录,然后把他初始化为0了。
PGSIZE =4096
的定义是在上一次的实验。也就是4096个字节,所以
kern_pgdir
占
4096B
一个页表项是
4B
,所以总共是
1024
个页表。(这个时候我在想们是不是 每个页表也是
1024
个页,一个页
4KB
这样内存就是 102410244KB 就是4G,不知道是不是这样,纯属猜测。)
后面有这么一段
//
// Recursively insert PD in itself as a page table, to form
// a virtual page table at virtual address UVPT.
// (For now, you don't have understand the greater purpose of the
// following line.)
// Permissions: kernel R, user R
kern_pgdir[PDX(UVPT)] = PADDR(kern_pgdir) | PTE_U | PTE_P;//后面这两个参要看mmu.h
自己本身就是页表,所以把自己插入进去。大家可以试试输出这个几个值看看,再对照前面那个内存。
紧接着 就是分配页了
//
// Allocate an array of npages 'struct PageInfo's and store it in 'pages'.
// The kernel uses this array to keep track of physical pages: for
// each physical page, there is a corresponding struct PageInfo in this
// array. 'npages' is the number of physical pages in memory. Use memset
// to initialize all fields of each struct PageInfo to 0.
// Your code goes here:
// 把每个页的结构存下来,放到pages里面,npages 是 页表的个数,知道这些 也就简单了。
pages=(struct PageInfo *) boot_alloc(npages *sizeof(struct PageInfo));
memset(pages,0,npages*sizeof(struct PageInfo));//初始化
大家自行输出 这个空间的大小。如果没错的话,
n=32768 ,PageInfo=8
256KB
。这是最后一次使用
boot_alloc
,他的作用也就干了两件事,一件事是分配页目录,第二个是为每个页分配数据结构。
接着就运行了
page_init()
这个时候你需要知道空闲列表,前面加载内核的时候有一部分内存是不能用的。
//
// Now that we've allocated the initial kernel data structures, we set
// up the list of free physical pages. Once we've done so, all further
// memory management will go through the page_* functions. In
// particular, we can now map memory using boot_map_region
// or page_insert 我们已经初始化了数据结构现在,需要知道空闲列表,以后使用内存就通过page_*函数,尤其是的是我们可以用 boot_map_region 和page_insert 进行映射。
page_init();
check_page_free_list(1);
check_page_alloc();
check_page();
接下来我们就要实现
page_init()
page_init()
// --------------------------------------------------------------
// Tracking of physical pages.
// The 'pages' array has one 'struct PageInfo' entry per physical page.
// Pages are reference counted, and free pages are kept on a linked list.
// --------------------------------------------------------------
//追踪物理内存,pages 保存的每一个页的信息,有些页实际上是不能用的。
//
// Initialize page structure and memory free list.初始化页面结构和空闲内存
// After this is done, NEVER use boot_alloc again. ONLY use the page
// allocator functions below to allocate and deallocate physical
// memory via the page_free_list.
//从这以后 就再也不会用 boot_alloc 只有page分配函数在 page_free_list 上面进行操作了,你也可以理解为,这个时候就开始了真正的分页了,后面所有的操作都是虚拟地址映射。
void
page_init(void)
{
// The example code here marks all physical pages as free.实例代码帮你把所有页都变成了空闲页
// However this is not truly the case. What memory is free? 然后其中有些不是空闲的
// 1) Mark physical page 0 as in use. 0号页 他存了实模式下面的IDT 和BIOS 结构虽然我们从未用过,但是你还是要留下。
// This way we preserve the real-mode IDT and BIOS structures
// in case we ever need them. (Currently we don't, but...)
// 2) The rest of base memory, [PGSIZE, npages_basemem * PGSIZE)
// is free. 就是这一段低地址的其他部分是可以用的。
// 3) Then comes the IO hole [IOPHYSMEM, EXTPHYSMEM), which must
// never be allocated. 不是有一块是给IO 用的 内存空洞,不能分配
// 4) Then extended memory [EXTPHYSMEM, ...). 然后就是扩展内存,有一部分已经被内核用了,我一直在思考,那个页表不也是被用了么,为啥这个地方没有考虑。也没有大佬可以问...也就自己猜测一下,应该也被算进那个内核内存额。
// Some of it is in use, some is free. Where is the kernel
// in physical memory? Which pages are already in use for
// page tables and other data structures?
//
// Change the code to reflect this.
// NB: DO NOT actually touch the physical memory corresponding to
// free pages!
size_t i;
for (i = 0; i < npages; i++) {
pages[i].pp_ref = 0;
pages[i].pp_link = page_free_list;
page_free_list = &pages[i]; //不知道为啥这个list是倒过来连接的
}
// 根据上面他给的提示写,1) 是 0 号 页是实模式的IDT 和 BIOS 不应该添加到空闲页,所以
pages[1].pp_link=pages[0].pp_link;
pages[0].pp_ref = 1;//可以不同设置,因为这个 页都没有进free list 永远都不可能用去分配
pages[0].pp_link=NULL;
//2)是说那一块可以用,也就是上一次实验说的低地址,所以不用做修改
//3)是说 上节课讲的有一部分 是不能用的,存IO的那一块,他告诉你地址是从[IOPHYSMEM,EXTPHYSMEM)
size_t range_io=PGNUM(IOPHYSMEM),range_ext=PGNUM(EXTPHYSMEM);
pages[range_ext].pp_link=pages[range_io].pp_link;
for (i = range_io; i < range_ext; i++) pages[i].pp_link = NULL;
//4)后面分配了一些内存页面给内核,所以那一块也是不能用的,看了半天,和上面是连续的...突然发现,大佬写额代码里面 是直接 找到了 boot_alloc(0),瞬间明白..这个直接把页表的空间也算上去了,所以准确来说应该是内核+页表+页目录的内存(可能内核包括页表和页目录..)。
size_t free_top = PGNUM(PADDR(boot_alloc(0)));
pages[free_top].pp_link = pages[range_ext].pp_link;
for(i = range_ext; i < free_top; i++) pages[i].pp_link = NULL;
}
后面就要实现两个函数一个是内存分配
page_alloc
,一个是内存释放
page_free
。
page_alloc
//
// Allocates a physical page. If (alloc_flags & ALLOC_ZERO), fills the entire
// returned physical page with '\0' bytes. Does NOT increment the reference
// count of the page - the caller must do these if necessary (either explicitly
// or via page_insert).
// 分配 一个页 然后返回一个页结构,如果 啥 就初始化为0 不用增加 计数。
// Be sure to set the pp_link field of the allocated page to NULL so
// page_free can check for double-free bugs.
// 有两种 检查
// Returns NULL if out of free memory.
//
// Hint: use page2kva and memset
struct PageInfo *
page_alloc(int alloc_flags)
{
// Fill this function in
//这个就是真正的内存分配函数了
if(page_free_list){ //是否有空闲=页
struct PageInfo *allocated = page_free_list;
page_free_list = allocated->pp_link;// 有就把这个取出来
allocated->pp_link = NULL;
if (alloc_flags & ALLOC_ZERO) //需不需要初始化????
memset(page2kva(allocated), 0, PGSIZE);
return allocated;
}
else return NULL;
//return 0;
}
page_free()
//
// Return a page to the free list. 把page 重新加入 空闲列表
// (This function should only be called when pp->pp_ref reaches 0.)
//
void
page_free(struct PageInfo *pp)
{
// Fill this function in
// Hint: You may want to panic if pp->pp_ref is nonzero or
// pp->pp_link is not NULL.
// 前面两个提示你了,一个判断 pp_ref 是不是非0 ,一个是pp_link 是不是非空
if(pp->pp_ref > 0||pp->pp_link != NULL)panic("Page table entries point to this physical page.");
pp->pp_link = page_free_list;
page_free_list = pp;
}
到此 物理内存分配实验全部结束了,总的来说其实就干了三件事:
- 建了了一个页目录,对所有页建了一个数据结构
- 把所有空闲的空间建成了一个空闲链表。
- 提供了一个物理内存,释放一个物理内存
这个是从我第一个资源获取那里面一个大佬那盗过来的。
Part 2: Virtual Memory
首先这个实验让你 先试试水,让你了解下物理地址和虚拟地址的差距。在虚拟内存里面都是连续的空间,转换成了物理地址就是一页一页的了。本来还有分段操作,但是呢这个里面没有用上,给禁用了。
是否记得 那年夏天我们所做过的
Lab 1 part3
用了一个简单的页表,就映射了 4MB,而现在我们要映射256MB。
Question 1
肯定是虚拟地址啊。
然后讲了
KADDR,PADDR
,前面代码那个啥文件里面有,看一下就可以知道了。
后面又扯了一大堆,看一看了解一下就行了。
然后又继续我的看源码大业了。
这次函数并没有在
mem_init()
里面使用,但是呢写了一些测试的东西。我们就照着实验上来一个个实现函数。
pgdir_walk()
boot_map_region()
page_lookup()
page_remove()
page_insert()
这个函数看懂了会受益很大的。
pgdir_walk()
// Given 'pgdir', a pointer to a page directory, pgdir_walk returns
// a pointer to the page table entry (PTE) for linear address 'va'.
// This requires walking the two-level page table structure.
// 给一个 页目录 返回一个页表项,两级页表结构
// The relevant page table page might not exist yet. 相关页表可能不存在
// If this is true, and create == false, then pgdir_walk returns NULL.如果真的不存在 且create 标志为false 就返回NULL
// Otherwise, pgdir_walk allocates a new page table page with page_alloc. 否则用paga_alloc创建一个
// - If the allocation fails, pgdir_walk returns NULL. 创建失败返回NULL
// - Otherwise, the new page's reference count is incremented, 否则新的页引用次数++
// the page is cleared,页清空
// and pgdir_walk returns a pointer into the new page table page.返回页指针
//
// Hint 1: you can turn a PageInfo * into the physical address of the
// page it refers to with page2pa() from kern/pmap.h. 你可以通过 page2pa() 转换成物理地址,
//这个去里面看看 就知道为什么了。
// Hint 2: the x86 MMU checks permission bits in both the page directory
// and the page table, so it's safe to leave permissions in the page
// directory more permissive than strictly necessary.
// x86 MMU 检查页目录 和页表,所以 页目录比 页表权限更严格
// Hint 3: look at inc/mmu.h for useful macros that manipulate page
// table and page directory entries.
//去 mmu.h 看看有用的宏
pte_t *
pgdir_walk(pde_t *pgdir, const void *va, int create)
{
// Fill this function in
struct PageInfo * np;
//这可能有点绕
//PDX 是页目录里面的索引, pgdir 是 页目录,用一个 指针指向这个地址
pte_t * pd_entry =&pgdir[PDX(va)];
//PTE_P 判断是不是已经存在该页,是的话就直接返回,返回的这个地址,就是页地址+偏移地址
if(*pd_entry & PTE_P) //如果这个项存在就直接返回页地址,看了半天,如果PTX(va) 只取了页偏移地址,所以 这个时候返回的实际上是一个 页的地址,而不是页表的入口地址。这个地方返回的值应该有点欠缺。
return (pte_t *)KADDR(PTE_ADDR(*pd_entry))+PTX(va);//PTE_ADDR 取页表项里面的值 然后转换成虚拟地址 + 上偏移量就是页表的位置 就相当于替换了虚拟地址里面的 页目录索引。
else if(create == true && (np=page_alloc(ALLOC_ZERO))){
//如果可以创建就创建一个
np->pp_ref++;
// page2pa 把PageInfo 结构转换成 物理地址。
*pd_entry=page2pa(np)|PTE_P|PTE_U|PTE_W; //设置一些值
return (pte_t *)KADDR(PTE_ADDR(*pd_entry)) + PTX(va);
}
else return NULL;
}
boot_map_region()
//
// Map [va, va+size) of virtual address space to physical [pa, pa+size)
// in the page table rooted at pgdir. Size is a multiple of PGSIZE, and
// va and pa are both page-aligned. 页对齐,把pa 映射到 va
// Use permission bits perm|PTE_P for the entries. 使用这个权限?
//
// This function is only intended to set up the ``static'' mappings
// above UTOP. As such, it should *not* change the pp_ref field on the
// mapped pages.
//这个函数 建立一个静态映射,只用在 UTOP 以上,不应该改变映射区域
// Hint: the TA solution uses pgdir_walk
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;
for(vStep=0;vStep<size;vStep+=PGSIZE){
// 不知道为啥我总感觉 pgdir_walk 总感觉返回的是具体页,而不是页表入口地址。可能pa就是一个页表入口地址吧...也有可能页表本身也是一个页,这地方直接当做一级页表用了,也不是没有可能
ptep=pgdir_walk(pgdir,(void *)va+vStep,true);//找到 va虚拟地址对应的页表入口地址
if(ptep)*ptep=pa|perm|PTE_P;//然后把这个入口地址 指向 物理地址 pa
pa+=PGSIZE;
}
}
page_lookup()
//
// Return the page mapped at virtual address 'va'.
// If pte_store is not zero, then we store in it the address
// of the pte for this page. This is used by page_remove and
// can be used to verify page permissions for syscall arguments,
// but should not be used by most callers.
//如果 pte_store 不是0 将物理页对应的页表项指针存储于其中
// Return NULL if there is no page mapped at va.
//如果没有映射 返回空
// Hint: the TA solution uses pgdir_walk and pa2page.
//
struct PageInfo *
page_lookup(pde_t *pgdir, void *va, pte_t **pte_store)
{
// Fill this function in
//就当做 ptep 指向页表入口地址
pte_t *ptep =pgdir_walk(pgdir,va,false);
if(ptep&&(*ptep&PTE_P)){
if(pte_store){
*pte_store=ptep;
}
//返回对应PageInfo
return pa2page(PTE_ADDR(*ptep));
}
return NULL;
}
page_remove()
//
// Unmaps the physical page at virtual address 'va'.
// If there is no physical page at that address, silently does nothing.
//如果没有映射物理地址就啥都不做
// Details:
// - The ref count on the physical page should decrement. ref应该--
// - The physical page should be freed if the refcount reaches 0. 如果到0应该释放
// - The pg table entry corresponding to 'va' should be set to 0.页表 入口地址应该置0
// (if such a PTE exists)
// - The TLB must be invalidated if you remove an entry from
// the page table.
// TLB 应该删除入口地址
// Hint: The TA solution is implemented using page_lookup,
// tlb_invalidate, and page_decref.
//
void
page_remove(pde_t *pgdir, void *va)
{
// Fill this function in
pte_t* pte_store;
struct PageInfo *pgit=page_lookup(pgdir, va, &pte_store);
if(pgit){
page_decref(pgit);
*pte_store=0;
tlb_invalidate(pgdir,va);//这个函数是不用我们实现的
}
}
page_insert()
//
// Map the physical page 'pp' at virtual address 'va'.
// The permissions (the low 12 bits) of the page table entry
// should be set to 'perm|PTE_P'.
//
// Requirements
// - If there is already a page mapped at 'va', it should be page_remove()d.如果一级存在就需要 把他删除
// - If necessary, on demand, a page table should be allocated and inserted
// into 'pgdir'. 如果有必要,一个页表需要被分配,插入到 pgdir里面
// - pp->pp_ref should be incremented if the insertion succeeds. ref应该递增
// - The TLB must be invalidated if a page was formerly present at 'va'.
// TLB 应该被删除 如果存在va 的页
// Corner-case hint: Make sure to consider what happens when the same
// pp is re-inserted at the same virtual address in the same pgdir.
// However, try not to distinguish this case in your code, as this
// frequently leads to subtle bugs; there's an elegant way to handle
// everything in one code path.
// 极端意识 确保在相同的页表再次插入到页目录中,翻译不过来告辞。
// RETURNS:
// 0 on success
// -E_NO_MEM, if page table couldn't be allocated
//
// Hint: The TA solution is implemented using pgdir_walk, page_remove,
// and page2pa.
//
int
page_insert(pde_t *pgdir, struct PageInfo *pp, void *va, int perm)
{
// Fill this function in
pte_t *ptep=pgdir_walk(pgdir, va, true);
if(ptep){
pp->pp_ref++;
if(*ptep&PTE_P)page_remove(pgdir, va);//如果已经有了 就先删了..
*ptep = page2pa(pp) | perm | PTE_P;
return 0;
}
return -E_NO_MEM;
}
Permissions and Fault Isolation
现在 就是让你映射内核区域了。
//
// Map 'pages' read-only by the user at linear address UPAGES
// Permissions:
// - the new image at UPAGES -- kernel R, user R
// (ie. perm = PTE_U | PTE_P)
// - pages itself -- kernel RW, user NONE
// Your code goes here:
//仔细分析了下,好像是把 UPAGES 虚拟内存 指向 pages。映射大小是 PTSIZE 一个页表的大小 4M,
boot_map_region(kern_pgdir, UPAGES, PTSIZE, PADDR(pages), PTE_U|PTE_P);
//
// Use the physical memory that 'bootstack' refers to as the kernel
// stack. The kernel stack grows down from virtual address KSTACKTOP.
// We consider the entire range from [KSTACKTOP-PTSIZE, KSTACKTOP)
// to be the kernel stack, but break this into two pieces:
// * [KSTACKTOP-KSTKSIZE, KSTACKTOP) -- backed by physical memory
// * [KSTACKTOP-PTSIZE, KSTACKTOP-KSTKSIZE) -- not backed; so if
// the kernel overflows its stack, it will fault rather than
// overwrite memory. Known as a "guard page".
// Permissions: kernel RW, user NONE
// 使用物理内存 bootstack 指向 内核的栈,内核的栈 从KSTACKTOP 开始向下增长
//分了两块,第一块[KSTACKTOP-KSTKSIZE, KSTACKTOP),这一块需要映射
//[KSTACKTOP-PTSIZE, KSTACKTOP-KSTKSIZE)这一块不映射,这样如果炸栈了就直接报RE错误,而不是覆盖低地址的数据。
// Your code goes here:
// 因为是从高到底,所以映射就从 KSTACKTOP-KSTKSIZE 到 KSTACKTOP。
boot_map_region(kern_pgdir, KSTACKTOP-KSTKSIZE, KSTKSIZE, PADDR(bootstack), PTE_W);
//
// Map all of physical memory at KERNBASE.
// Ie. the VA range [KERNBASE, 2^32) should map to
// the PA range [0, 2^32 - KERNBASE)
// We might not have 2^32 - KERNBASE bytes of physical memory, but
// we just set up the mapping anyway.
// Permissions: kernel RW, user NONE
// Your code goes here:
//这个就是内核态,里面可以通用的内存,总共256M
boot_map_region(kern_pgdir, KERNBASE, 0x10000000, 0, PTE_W);
//到此位置实验要写的代码已经写完成了。 这个时候运行已经没什么问题了。
这个也是盗的:
Question:
2. 到目前为止页目录表中已经包含多少有效页目录项?他们都映射到哪里?
3BD号页目录项,指向的是kern_pgdir
3BC号页目录项,指向的是pages数组
3BF号页目录项,指向的是bootstack
3C0~3FF号页目录项,指向的是kernel
3. 如果我们把kernel和user environment放在一个相同的地址空间中。为什么用户程序不同读取,写入内核的内存空间?用什么机制保护内核的地址范围。
用户程序不能去随意修改内核中的代码,数据,否则可能会破坏内核,造成程序崩溃。
正常的操作系统通常采用两个部件来完成对内核地址的保护,一个是通过段机制来实现的,但是JOS中的分段功能并没有实现。二就是通过分页机制来实现,通过把页表项中的 Supervisor/User位置0,那么用户态的代码就不能访问内存中的这个页。
4. 这个操作系统的可以支持的最大数量的物理内存是多大?
由于这个操作系统利用一个大小为4MB的空间UPAGES来存放所有的页的PageInfo结构体信息,每个结构体的大小为8B,所以一共可以存放512K个PageInfo结构体,所以一共可以出现512K个物理页,每个物理页大小为4KB,自然总的物理内存占2GB。
5. 如果现在的物理内存页达到最大个数,那么管理这些内存所需要的额外空间开销有多少?
这里不太明白,参考别的答案是,首先需要存放所有的PageInfo,需要4MB,需要存放页目录表,kern_pgdir,4KB,还需要存放当前的页表,大小为2MB。所以总的开销就是6MB + 4KB。
6. 回顾entry.S文件中,当分页机制开启时,寄存器EIP的值仍旧是一个小的值。在哪个位置代码才开始运行在高于KERNBASE的虚拟地址空间中的?当程序位于开启分页之后到运行在KERNBASE之上这之间的时候,EIP的值是小的值,怎么保证可以把这个值转换为真实物理地址的?
在entry.S文件中有一个指令 jmp *%eax,这个指令要完成跳转,就会重新设置EIP的值,把它设置为寄存器eax中的值,而这个值是大于KERNBASE的,所以就完成了EIP从小的值到大于KERNBASE的值的转换。
在entry_pgdir这个页表中,也把虚拟地址空间[0, 4MB)映射到物理地址空间[0, 4MB)上,所以当访问位于[0, 4MB)之间的虚拟地址时,可以把它们转换为物理地址。
Address Space Layout Alternatives
进程的虚拟地址空间的布局不是只有我们讨论的这种唯一的情况,我们也可以把内核映射到低地址处。但是JOS之所以要这么做,是为了保证x86的向后兼容性。
只要我们能够仔细设计,虽然很难,但是我们也能设计出来一种内核的布局方式,使得进程的地址空间就是从0到4GB,无需为内核预留一部分空间,但是仍然能够保证,用户进程不会破坏操作系统的指令,数据。