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,無需為核心預留一部分空間,但是仍然能夠保證,使用者程序不會破壞作業系統的指令,資料。