MIT 6.828 (二) Lab 2: Memory management

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.hinc/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_pgdir4096B 一个页表项是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;
}

到此 物理内存分配实验全部结束了,总的来说其实就干了三件事:

  1. 建了了一个页目录,对所有页建了一个数据结构
  2. 把所有空闲的空间建成了一个空闲链表。
  3. 提供了一个物理内存,释放一个物理内存

这个是从我第一个资源获取那里面一个大佬那盗过来的。
在这里插入图片描述

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,无需为内核预留一部分空间,但是仍然能够保证,用户进程不会破坏操作系统的指令,数据。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值