验证缺页中断机制
问题背景
在x86系列的cpu上开启分页机制后,指令涉及到”内存读写”操作时,用到的”内存地址”都是”虚拟地址”。”mmu硬件”会按照”分段”、”分页”的计算规则,将”虚拟地址”转换成”物理地址”。最终指令可以对”物理地址”读写数据。
上面这段关于内存的”理论知识”,我觉得如果可以实践看一看,就能多一些”感觉”。
本文记录我对下面两个结论的调试和验证过程,也是我对极客时间的操作系统实战 45 讲课程中”内存管理”章节的部分学习和实践,基于第26课代码。
“mmu硬件”在转换”虚拟地址”时有可能发生”缺页异常”,包括:
- 访问”未向内核申请的虚拟地址”会导致”缺页异常”
- 当第一次访问申请的虚拟地址时,也会产生”缺页异常”;第二次访问同样的虚拟地址时,不会产生”缺页异常”
验证过程
访问”未向内核申请的虚拟地址”会导致”缺页异常”
按照理论来说:访问未申请的虚拟地址时,”mmu硬件”在转换”虚拟地址”时会发出”缺页异常”(可能因为找不到对应的”页表项”)
在验证这个理论前,先介绍一下cosmos(课程自制的内核系统名称)相关的代码,以方便读者大概理解文中出现的函数作用
1
2
3
4
5
6
7
8
9
10
11void test_vadr()
{
adr_t vadr = vma_new_vadrs(&initmmadrsdsc, NULL, 0x1000, 0, 0); // 0x1000是想要申请的空间大小,vadr是申请的内存地址
if(NULL == vadr)
{
kprint("分配虚拟地址空间失败\n");
}
...
hal_memset((void*)vadr, 0, 0x1000); // 初始化刚申请的内存为0
...
}上面
test_vadr
是cosmos中用来验证”虚拟内存”相关功能的测试函数。在cosmos中,想要使用虚拟空间,需要先调用
vma_new_vadrs
申请一个虚拟地址。cosmos相关代码介绍完毕,下面用gdb调试课程代码,来实际验证一下”理论”:
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(gdb) break test_vadr // 在 test_vadr 函数下断点(选择在test_vadr函数下断点,是因为此时 虚拟内存管理、物理内存管理、缺页异常处理 等功能都已经实现)
Breakpoint 1 at 0xffff80000202444a: file ../kernel/krlvadrsmem.c, line 289.
(gdb) continue
Continuing.
Breakpoint 1, test_vadr () at ../kernel/krlvadrsmem.c:289
289 {
(gdb) break krluserspace_accessfailed // 在 krluserspace_accessfailed 函数下断点
Breakpoint 2 at 0xffff800002026588: file ../kernel/krlvadrsmem.c, line 1145.
(gdb) call hal_memset(0x400,0,0x1) // 调用hal_memset函数,向 0x400地址 写一个字节内容
Breakpoint 2, krluserspace_accessfailed (fairvadrs=0) at ../kernel/krlvadrsmem.c:1145 //
...
(gdb) where // 可以看到 当前代码在../kernel/krlvadrsmem.c文件1145行的krluserspace_accessfailed
#0 krluserspace_accessfailed (fairvadrs=0) at ../kernel/krlvadrsmem.c:1145
#1 0xffff8000020135d2 in hal_fault_allocator (faultnumb=14, krnlsframp=0xffff80000008fe18)
at ../hal/x86/halintupt.c:171
#2 0xffff80000200d830 in exc_page_fault ()
...
(gdb) continue
Continuing.
^C // 因为在这里卡死了,所以我按了ctrl+c
Program received signal SIGINT, Interrupt.
hal_sysdie (errmsg=0xffff80000203a237 "缺页处理失败\n") at ../hal/x86/halcpuctrl.c:185
185 ;从上面的调试信息可以看出来:cpu在执行
hal_memset
函数访问0x400
虚拟地址时,跳到了krluserspace_accessfailed
函数,最后卡死在halcpuctrl.c
第185行这里的
0x400
地址不是通过vma_new_vadrs
申请的地址。krluserspace_accessfailed
函数只有cpu遇到中断并且中断号为14(也就是”缺页异常”)的时候才会被调用1
2
3
4
5
6
7
8
9
10
11
12
13
14
15// 异常处理
void hal_fault_allocator(uint_t faultnumb, void *krnlsframp) //eax,edx
{
...
if (faultnumb == 14) // 缺页异常 中断号是14
{
...
if (krluserspace_accessfailed(fairvadrs) != 0)
{
system_error("缺页处理失败\n");
}
...
}
...
}所以可以推测出来:访问
0x400
时,mmu硬件发出了”缺页中断”,接着cpu处理”缺页中断”就会到krluserspace_accessfailed
函数,因为krluserspace_accessfailed
函数处理”页表映射”失败,所以会调用system_error
函数,最终执行for循环
卡死。这样可以看出来理论没有问题。
访问”已申请的虚拟地址”
按照理论:在第一次访问”已申请的虚拟地址”,因为”虚拟内存”和”物理内存”的映射关系还没写到”页表”中,所以”mmu硬件”在转换”虚拟地址”时会发出”缺页异常”,然后”缺页异常”处理程序会写这个映射关系到”页表”中;第二次访问同一个”已申请的虚拟地址”,因为”虚拟内存”和”物理内存”的映射关系已经写到”页表”,”mmu硬件”就可以正常转换,不会发出”缺页异常”。
下面同样用gdb调试来验证一下:
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(gdb) continue
Continuing.
Breakpoint 1, test_vadr () at ../kernel/krlvadrsmem.c:289
289 {
(gdb) break krluserspace_accessfailed
Breakpoint 2 at 0xffff800002026588: file ../kernel/krlvadrsmem.c, line 1145.
(gdb) x /bx 0x40000
0x40000: Cannot access memory at address 0x40000
(gdb) call vma_new_vadrs(&initmmadrsdsc,0x40000,0x1000, 0, 0) // 向内核申请虚拟空间,地址是0x40000,大小是0x1000
$1 = 0
(gdb) call hal_memset(0x40000,0,0x1) // 第一次访问0x40000时进入到"中断处理函数"
Breakpoint 2, krluserspace_accessfailed (fairvadrs=0) at ../kernel/krlvadrsmem.c:1145
...
(gdb) finish
Run till exit from #0 krluserspace_accessfailed (fairvadrs=2097282) at ../kernel/krlvadrsmem.c:1145
...
(gdb) finish
Run till exit from #0 0xffff8000020135d2 in hal_fault_allocator (faultnumb=14,
krnlsframp=0xffff80000008fe18) at ../hal/x86/halintupt.c:171
0xffff80000200d830 in exc_page_fault ()
(gdb) finish
Run till exit from #0 0xffff80000200d830 in exc_page_fault ()
(gdb) where
#0 test_vadr () at ../kernel/krlvadrsmem.c:289
...
(gdb) call hal_memset(0x40000,0,0x1) // 第二次访问0x40000时没有产生"中断"
(gdb) x /bx 0x40000
0x40000: 0x00从上面可以看到:调用
vma_new_vadrs
申请0x40000
地址后,第一次调用hal_memset
去访问0x40000
时会进入到”缺页异常”处理程序(krluserspace_accessfailed
函数)中,第二次再访问0x40000
时就不会进入到”缺页异常”处理程序。
总结
通过调试,很容易对cosmos中”虚拟内存”访问时的业务流程做一点研究。
同样我们可以通过”调试”去观察”分页”机制,来验证课程中的知识点。
PS:后面会有一篇文章讲解怎么调试cosmos代码