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,無需爲內核預留一部分空間,但是仍然能夠保證,用戶進程不會破壞操作系統的指令,數據。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章