MIT6.828(二)Lab2:Memory management

Lab 2: Memory management

做这个实验之前首先需要知道什么是分页。分段在这个实验里面没用到过。

前面是一大堆教你怎么获取lab2资源的,我不知道怎么弄,后来乱搞了一下,就把lab1的覆盖掉了,变成了lab2。这个我相信就我不懂。

Part 1: Physical Page Management

第一个是物理页面管理。

1
2
3
4
5
boot_alloc() 	//这个是系统加载前做个物理内存分配,也就初始化用了一下
mem_init() // 顾名思义,内存初始化
page_init() //页面初始化
page_alloc() //真正的分配物理页面
page_free() // 页面释放

首先,我们先观察一下init.c文件,这个是内核初始化调用的,上个实验已经清楚了。

init.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
#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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
#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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
/* 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();
我们一段一段的来。
先看看定义了啥

1
2
3
4
5
6
7
8
 // 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()的过程走。

1
2
3
4
5
6
	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 作用很明显,就是创建一个页目录。

1
2
3
4
//////////////////////////////////////////////////////////////////////
// create initial page directory.
kern_pgdir = (pde_t *) boot_alloc(PGSIZE);
memset(kern_pgdir, 0, PGSIZE);

boot_alloc()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
// 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,不知道是不是这样,纯属猜测。)
后面有这么一段

1
2
3
4
5
6
7
8
//////////////////////////////////////////////////////////////////////
// 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

自己本身就是页表,所以把自己插入进去。大家可以试试输出这个几个值看看,再对照前面那个内存。
紧接着 就是分配页了

1
2
3
4
5
6
7
8
9
10
//////////////////////////////////////////////////////////////////////
// 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()这个时候你需要知道空闲列表,前面加载内核的时候有一部分内存是不能用的。

1
2
3
4
5
6
7
8
9
10
11
//////////////////////////////////////////////////////////////////////
// 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()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
// --------------------------------------------------------------
// 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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
//
// 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()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//
// 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() 里面使用,但是呢写了一些测试的东西。我们就照着实验上来一个个实现函数。

1
2
3
4
5
pgdir_walk()
boot_map_region()
page_lookup()
page_remove()
page_insert()

这个函数看懂了会受益很大的。

pgdir_walk()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
// 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()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//
// 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()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
//
// 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()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
//
// 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()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
//
// 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

现在 就是让你映射内核区域了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
//////////////////////////////////////////////////////////////////////
// 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,无需为内核预留一部分空间,但是仍然能够保证,用户进程不会破坏操作系统的指令,数据。