@@ -33,7 +33,7 @@ lazy_static! {
33
33
pub static ref KERNEL_SPACE : Arc <UPSafeCell <MemorySet >> =
34
34
Arc :: new( unsafe { UPSafeCell :: new( MemorySet :: new_kernel( ) ) } ) ;
35
35
}
36
- /// address space
36
+ /// 地址空间
37
37
pub struct MemorySet {
38
38
page_table : PageTable ,
39
39
areas : Vec < MapArea > ,
@@ -63,22 +63,31 @@ impl MemorySet {
63
63
None ,
64
64
) ;
65
65
}
66
+ ///@ push 将一个MapArea push到自己的areas,如果有数据就写入数据到内存。
67
+ /// 把MapArea内的每个都页都放入页表(map)
68
+ /// 对于每个应用程序,其trampoline虚拟地址对应的物理页号也都放入了自己的地址空间,这样它才能跳转到trap处理程序
66
69
fn push ( & mut self , mut map_area : MapArea , data : Option < & [ u8 ] > ) {
67
70
map_area. map ( & mut self . page_table ) ;
68
71
if let Some ( data) = data {
69
72
map_area. copy_data ( & mut self . page_table , data) ;
70
73
}
71
74
self . areas . push ( map_area) ;
72
75
}
73
- /// Mention that trampoline is not collected by areas.
76
+ /// 将跳板页面映射到地址空间.
77
+ /// 注意: 跳板页面不由 MemoryArea 管理.
74
78
fn map_trampoline ( & mut self ) {
79
+
80
+ //@ 将TRAMPOLINE->strampoline放入页表
75
81
self . page_table . map (
82
+ // 虚拟地址: TRAMPOLINE (最高虚拟页,说最高是因为 TRAMPOLINE 定义为 usizeMAX - 1Page)
76
83
VirtAddr :: from ( TRAMPOLINE ) . into ( ) ,
84
+ // 物理地址: strampoline 符号的地址 (代码所在物理页帧)
77
85
PhysAddr :: from ( strampoline as usize ) . into ( ) ,
86
+ // 权限: 可读 (R) | 可执行 (X)
78
87
PTEFlags :: R | PTEFlags :: X ,
79
88
) ;
80
89
}
81
- /// Without kernel stacks.
90
+ ///@ 建立页表恒等映射 (Identity Mapping)
82
91
pub fn new_kernel ( ) -> Self {
83
92
let mut memory_set = Self :: new_bare ( ) ;
84
93
// map trampoline
@@ -94,8 +103,10 @@ impl MemorySet {
94
103
info ! ( "mapping .text section" ) ;
95
104
memory_set. push (
96
105
MapArea :: new (
106
+ // 指定虚拟地址范围
97
107
( stext as usize ) . into ( ) ,
98
108
( etext as usize ) . into ( ) ,
109
+ // 指定恒等映射
99
110
MapType :: Identical ,
100
111
MapPermission :: R | MapPermission :: X ,
101
112
) ,
@@ -132,6 +143,7 @@ impl MemorySet {
132
143
None ,
133
144
) ;
134
145
info ! ( "mapping physical memory" ) ;
146
+ // ekernel 到 MEMORY_END 的映射,使内核可以用简单的、与物理地址相同的虚拟地址来访问任意物理内存(例如,用于物理页帧分配、访问设备寄存器等)。
135
147
memory_set. push (
136
148
MapArea :: new (
137
149
( ekernel as usize ) . into ( ) ,
@@ -141,26 +153,49 @@ impl MemorySet {
141
153
) ,
142
154
None ,
143
155
) ;
156
+ // 上述一通push完成,当系统启用分页后:
157
+ // CPU 产生的任何虚拟地址,如果落在了 .text, .rodata, .data, .bss 或 ekernel 到 MEMORY_END 的范围内,
158
+ // MMU 通过查询内核页表进行地址转换时,找到的物理地址与该虚拟地址完全相同。
159
+ // 恒等映射也保证了我们启动分页的平滑过渡:启动分页前最后一条指令和启动分页后第一条指令连续。
160
+
161
+ //? 问题:恒等映射的这些页表占多少空间?
162
+ // 含这个问题的上下文的ai: https://gemini.google.com/app/51ce53f706953582
144
163
memory_set
145
164
}
146
165
/// Include sections in elf and trampoline and TrapContext and user stack,
147
166
/// also returns user_sp_base and entry point.
167
+ /// 解析 ELF 文件,根据其中的信息创建用户程序的代码、数据、栈、Trap 上下文等内存区域,
168
+ /// 并建立这些区域对应的页表映射。
169
+ /// 返回一个元组,包含:
170
+ /// - 构建好的 MemorySet 实例,代表用户程序的地址空间。
171
+ /// - 用户栈的栈顶虚拟地址,作为用户程序启动时的栈指针。
172
+ /// - 用户程序的入口点虚拟地址,即程序开始执行的第一条指令的地址。
148
173
pub fn from_elf ( elf_data : & [ u8 ] ) -> ( Self , usize , usize ) {
149
174
let mut memory_set = Self :: new_bare ( ) ;
150
175
// map trampoline
176
+ // 跳板页用于用户态和内核态之间的切换。它在所有用户地址空间中映射到TRAMPOLINE位置
151
177
memory_set. map_trampoline ( ) ;
178
+
152
179
// map program headers of elf, with U flag
180
+ // 使用 xmas_elf 库解析输入的 ELF 文件数据。
153
181
let elf = xmas_elf:: ElfFile :: new ( elf_data) . unwrap ( ) ;
154
182
let elf_header = elf. header ;
155
183
let magic = elf_header. pt1 . magic ;
184
+ // 检查 ELF 文件头的 magic number ([0x7f, 'E', 'L', 'F']),确保是有效的 ELF 格式。
156
185
assert_eq ! ( magic, [ 0x7f , 0x45 , 0x4c , 0x46 ] , "invalid elf!" ) ;
157
186
let ph_count = elf_header. pt2 . ph_count ( ) ;
158
187
let mut max_end_vpn = VirtPageNum ( 0 ) ;
188
+
189
+ // 遍历 ELF 文件中的程序头 (Program Headers),程序头描述了 ELF 文件中各个段(如代码段、数据段)应该如何被加载到内存中。
159
190
for i in 0 ..ph_count {
160
191
let ph = elf. program_header ( i) . unwrap ( ) ;
192
+ // 只处理类型为 'Load' 的程序头,其要被加载到虚拟地址空间并建立映射的。
161
193
if ph. get_type ( ) . unwrap ( ) == xmas_elf:: program:: Type :: Load {
194
+ // 获取该加载段在虚拟地址空间中的起始地址和结束地址 (基于其内存大小)。
162
195
let start_va: VirtAddr = ( ph. virtual_addr ( ) as usize ) . into ( ) ;
163
196
let end_va: VirtAddr = ( ( ph. virtual_addr ( ) + ph. mem_size ( ) ) as usize ) . into ( ) ;
197
+
198
+ // 根据 ELF 段的标志 (flags) 确定该内存区域的访问权限。
164
199
let mut map_perm = MapPermission :: U ;
165
200
let ph_flags = ph. flags ( ) ;
166
201
if ph_flags. is_read ( ) {
@@ -172,20 +207,38 @@ impl MemorySet {
172
207
if ph_flags. is_execute ( ) {
173
208
map_perm |= MapPermission :: X ;
174
209
}
210
+ // 创建一个 MapArea 实例,添加到 MemorySet 中,并提供段的初始数据。
175
211
let map_area = MapArea :: new ( start_va, end_va, MapType :: Framed , map_perm) ;
176
- max_end_vpn = map_area. vpn_range . get_end ( ) ;
212
+ max_end_vpn = map_area. vpn_range . get_end ( ) ; // 最大虚拟页号
213
+ // push 方法会执行以下操作:
214
+ // - 调用 map_area.map():遍历 MapArea 范围内的虚拟页,
215
+ // 为每个页分配物理页帧 (因为 MapType::Framed),并在页表中建立 vpn -> ppn 的映射。
216
+ // - 调用 map_area.copy_data():将 ELF 文件中该段的内容复制到刚刚分配的物理页帧中。
217
+ // - 将 map_area 添加到 memory_set.areas 列表中。
218
+
177
219
memory_set. push (
178
- map_area,
179
- Some ( & elf. input [ ph. offset ( ) as usize .. ( ph. offset ( ) + ph. file_size ( ) ) as usize ] ) ,
220
+ map_area, // data: Some(&[u8])
221
+ Some ( & elf. input [ ph. offset ( ) as usize .. ( ph. offset ( ) + ph. file_size ( ) ) as usize ] ) ,
180
222
) ;
181
223
}
182
224
}
183
225
// map user stack with U flags
226
+ // 映射用户栈区域。
184
227
let max_end_va: VirtAddr = max_end_vpn. into ( ) ;
185
228
let mut user_stack_bottom: usize = max_end_va. into ( ) ;
186
229
// guard page
230
+ // 在用户栈和 ELF 段之间留出一个页大小的未映射或特殊映射区域。
231
+ // 这样,如果用户栈溢出,触碰到这个 Guard Page 会立即触发页错误,而不是覆盖到前面的代码/数据段。
232
+ // 这里通过简单地将栈底地址跳过一个页大小PAGE_SIZE来实现,Guard Page 本身没有被 push 到 areas 中,因此是未映射的。
187
233
user_stack_bottom += PAGE_SIZE ;
234
+
235
+ // 计算用户栈的栈顶地址 (栈从高地址向低地址增长,所以栈顶地址 > 栈底地址)
188
236
let user_stack_top = user_stack_bottom + USER_STACK_SIZE ;
237
+
238
+ // 创建并映射用户栈的 MapArea。
239
+ // 映射类型为 Framed (需要分配新的物理页帧作为栈空间)。
240
+ // 权限为 用户可读写 (R | W | U),用户栈不需要可执行。
241
+ // 初始数据为 None,因为栈内容由程序运行时动态生成。
189
242
memory_set. push (
190
243
MapArea :: new (
191
244
user_stack_bottom. into ( ) ,
@@ -195,17 +248,22 @@ impl MemorySet {
195
248
) ,
196
249
None ,
197
250
) ;
198
- // used in sbrk
251
+ // 映射 sbrk 区域 (用于支持动态堆扩展)。
252
+ // 通常在用户栈之后放置一个零大小的 MapArea,标记动态内存分配(如堆)的起始位置。
253
+ // 用户程序通过 sbrk 等系统调用请求更多内存时,OS 会扩展这个 MapArea
199
254
memory_set. push (
200
255
MapArea :: new (
201
- user_stack_top. into ( ) ,
202
- user_stack_top. into ( ) ,
256
+ user_stack_top. into ( ) , // sbrk 区域起始地址,通常紧随用户栈顶
257
+ user_stack_top. into ( ) , // sbrk 区域结束地址,初始大小为 0
203
258
MapType :: Framed ,
204
- MapPermission :: R | MapPermission :: W | MapPermission :: U ,
259
+ MapPermission :: R | MapPermission :: W | MapPermission :: U , // 用户可读写权限
205
260
) ,
206
261
None ,
207
262
) ;
208
- // map TrapContext
263
+ // 映射 Trap 上下文 (TrapContext) 区域。
264
+ // TrapContext 用于在用户/内核模式切换时保存用户态的 CPU 寄存器状态。
265
+ // 它被放置在用户地址空间中一个固定的高地址 (TRAP_CONTEXT_BASE),紧邻跳板下方。
266
+ // 这样在内核处理 Trap 时,可以通过一个固定的虚拟地址访问到保存的用户上下文。
209
267
memory_set. push (
210
268
MapArea :: new (
211
269
TRAP_CONTEXT_BASE . into ( ) ,
@@ -215,16 +273,22 @@ impl MemorySet {
215
273
) ,
216
274
None ,
217
275
) ;
276
+ // 返回构建好的 MemorySet、初始用户栈顶地址和程序入口点。
277
+ // 操作系统调度器在第一次运行这个用户进程时会使用这些信息:
278
+ // - 将 memory_set 的根页表地址加载到 satp 寄存器。
279
+ // - 将 user_stack_top 加载到用户栈指针寄存器 (如 sp)。
280
+ // - 将 entry_point 加载到程序计数器 (PC),开始执行用户程序。
218
281
(
219
282
memory_set,
220
283
user_stack_top,
221
- elf. header . pt2 . entry_point ( ) as usize ,
284
+ elf. header . pt2 . entry_point ( ) as usize , // 用户进程执行的第一个指令地址
222
285
)
223
286
}
224
287
/// Change page table by writing satp CSR Register.
225
288
pub fn activate ( & self ) {
226
289
let satp = self . page_table . token ( ) ;
227
290
unsafe {
291
+ // 从执行 satp::write 指令的时刻起,SV39 分页模式就被启用了,MMU 开始使用内核地址空间的多级页表进行后续的地址转换。
228
292
satp:: write ( satp) ;
229
293
asm ! ( "sfence.vma" ) ;
230
294
}
@@ -287,23 +351,23 @@ impl MapArea {
287
351
map_perm,
288
352
}
289
353
}
290
- /// 建立单页VPN的映射
354
+ /// 建立单页VPN的映射:向自身添加VPN->PPN(直接映射)、向页表里添加一个VPN->PPN(三级映射)。
291
355
pub fn map_one ( & mut self , page_table : & mut PageTable , vpn : VirtPageNum ) {
292
356
let ppn: PhysPageNum ;
293
357
//1. 更新data_frames
294
358
match self . map_type {
295
359
MapType :: Identical => {
296
- // 不向data_frames里推数据
360
+ // 不向data_frames里推数据(恒等映射)
297
361
ppn = PhysPageNum ( vpn. 0 ) ;
298
362
}
299
363
MapType :: Framed => {
300
- let frame = frame_alloc ( ) . unwrap ( ) ;
364
+ let frame = frame_alloc ( ) . unwrap ( ) ; // 自己分配一个物理页
301
365
ppn = frame. ppn ;
302
366
self . data_frames . insert ( vpn, frame) ;
303
367
}
304
368
}
305
369
let pte_flags = PTEFlags :: from_bits ( self . map_perm . bits ) . unwrap ( ) ;
306
- //2. 更新页表
370
+ // 页表内添加vpn->ppn的映射
307
371
page_table. map ( vpn, ppn, pte_flags) ;
308
372
}
309
373
#[ allow( unused) ]
@@ -340,7 +404,9 @@ impl MapArea {
340
404
}
341
405
/// data: start-aligned but maybe with shorter length
342
406
/// assume that all frames were cleared before
343
- /// 将数据放入物理页,一页一页的放,顺序是虚拟页递增的顺序。
407
+ /// 向已经分配并映射好的物理页帧中写入数据
408
+ /// 通常是在MapArea 调用了 map 方法完成所有页的映射后(此时 map 内部多次调用了 map_one),data_frames 被填充了需要追踪的物理页帧信息,
409
+ /// 然后才会调用 copy_data 来向这些已分配并映射的物理页写入数据。
344
410
pub fn copy_data ( & mut self , page_table : & mut PageTable , data : & [ u8 ] ) {
345
411
assert_eq ! ( self . map_type, MapType :: Framed ) ;
346
412
let mut start: usize = 0 ;
@@ -384,8 +450,9 @@ bitflags! {
384
450
}
385
451
}
386
452
387
- /// Return (bottom, top) of a kernel stack in kernel space.
453
+ /// 根据 app_id 计算内核栈在内核地址空间中的位置 (bottom, top)
388
454
pub fn kernel_stack_position ( app_id : usize ) -> ( usize , usize ) {
455
+ // 内核栈位于跳板页下方,每个栈之间有一个 PADDING 页
389
456
let top = TRAMPOLINE - app_id * ( KERNEL_STACK_SIZE + PAGE_SIZE ) ;
390
457
let bottom = top - KERNEL_STACK_SIZE ;
391
458
( bottom, top)
@@ -394,10 +461,12 @@ pub fn kernel_stack_position(app_id: usize) -> (usize, usize) {
394
461
/// remap test in kernel space
395
462
#[ allow( unused) ]
396
463
pub fn remap_test ( ) {
464
+ // 获取代码段、只读数据段、数据段中间位置的虚拟地址
397
465
let mut kernel_space = KERNEL_SPACE . exclusive_access ( ) ;
398
466
let mid_text: VirtAddr = ( ( stext as usize + etext as usize ) / 2 ) . into ( ) ;
399
467
let mid_rodata: VirtAddr = ( ( srodata as usize + erodata as usize ) / 2 ) . into ( ) ;
400
468
let mid_data: VirtAddr = ( ( sdata as usize + edata as usize ) / 2 ) . into ( ) ;
469
+ // 检查页面是否不可写(代码段和只读数据段的页面不可写)
401
470
assert ! ( !kernel_space
402
471
. page_table
403
472
. translate( mid_text. floor( ) )
@@ -408,6 +477,7 @@ pub fn remap_test() {
408
477
. translate( mid_rodata. floor( ) )
409
478
. unwrap( )
410
479
. writable( ) , ) ;
480
+ // 检查数据段页面不可执行
411
481
assert ! ( !kernel_space
412
482
. page_table
413
483
. translate( mid_data. floor( ) )
0 commit comments