页异常中断

 

       此次实验的任务是:通过调试来查找Linux0.11内核启动后第一次引发页异常中断Page Fault的代码,并根据Linux0.11内核代码和intel386手册分析出现中断的原因。

       有关调试环境的建立请参考:linux0.11引导代码小窥内存分段机制。一般中断的处理过程可参考:实例分析Linux0.11内核中断机制

问题1:内核启动后第一次页异常中断发生在什么时候

       查找引发第一次缺页中断的代码位置很简单,依据理论:发生中断后,cpu将代码段选择符和返回地址的偏移值压入堆栈(进程内核态堆栈)。这个返回地址就是引发中断的代码地址。

 

       设置页异常中断处理函数入口代码如下:

/*摘自kernel/traps.ctrap_init函数*/

       set_trap_gate(14,&page_fault);

 

       通过查看System.map文件可知page_fault函数地址为0xb858。启动bochsdgb进行调试,命令行如下:

<bochs:1> b 0xb858

<bochs:2> c

(0) Breakpoint 1, 0xb858 in ?? ()

Next at t=16886891

(0) [0x0000b858] 0008:0000b858 (unk. ctxt): xchg dword ptr ss:[esp], eax ; 870424

<bochs:3> dump_cpu

……

esp:0xffffe8 // 任务1内核态堆栈位置

……

tr:s=0x30, dl=0xf2e80068, dh=0x89ff, valid=1  // 由此可知任务1代码引发页异常中断

……

cr2:0x401f244  // 引发页异常中断的线性地址

……

<bochs:4> print-stack

   00ffffe8 [00ffffe8]  0007

   00ffffec [00ffffec]  6790

   00fffff0 [00fffff0]  000f

   00fffff4 [00fffff4]  10646

   00fffff8 [00fffff8]  1f248

   00fffffc [00fffffc]  0017

   01000000   <could not translate>

……

      

       按照堆栈向下增长方向整理调试信息,如下表所示:

0x0000

SS

0x00000017

ESP

0x0001f248

EFLAGS

0x00010646

0x0000

CS

0x0000000f

EIP

0x00006790

 

       因此,第一次引发页异常中断的代码地址为0x6790

       接下来确定处于该位置的代码内容,重新启动bochsdgb,在0x6790处设置断点,命令行如下:

<bochs:1> b 0x6790

<bochs:2> c

(0) Breakpoint 1, 0x4006790 in ?? ()

Next at t=16886891

(0) [0x00006790] 000f:00006790 (unk. ctxt): call .+0x6884             ; e8ef000000

 

       0x6790处的代码是 call 0x6884,查看System.map文件可知init函数的入口地址是0x6884,该函数仅在Init/main.c中的main函数中得到调用:

void main(void)            

{                  

// 省略部分代码

       sti();  // 打开中断标志

       move_to_user_mode(); //0号任务移到用户态执行

       if (!fork()) {         

              init();  // 此处引发第一次页异常中断

       }

       for(;;) pause();

}

问题2:引发第一次页异常中断的原因

       这个问题很有意思,牵涉很多方面的知识,感兴趣的同志可以先考虑一下,然后再参考下面的答案。

1)        CR2寄存器保存引发页异常中断的线性地址。

       第一次引发页异常中断的线性地址为0x401f244,这个线性地址位于任务1的地址空间。

2)        任务0调用fork函数创建任务1,在创建结束后,任务1和任务0共享代码和数据。

       线性地址0x401f244对应任务0的线性地址0x1f244,而任务0的线性地址可以认为是实模式下的物理地址。

       查看System.map文件,在启动的Linux界面输入以下命令:

 [/usr/root]# cat System.map |grep 1e

Allocating common __ctmp: 4 at 1e258

Allocating common _user_stack: 1000 at 1e25c  // 0x1f244user_stack的地址范围0x1e25c~0x1f25c之内

……

3)        任务0的用户态堆栈即user_stack,任务1也共享任务0的用户态堆栈。

4)        进程间共享代码和数据的同时,它们相应的页目录和页目录表项都设置成只读的页面,当有写操作时就利用页异常中断调用,执行写时复制。

5)        当任务1执行函数调用init的时候,任务1将对与任务0共享的用户态堆栈执行写操作,因此引发页异常中断。

      

       调试验证:1. 引发页异常中断之前,0x401f244对应的物理页地址与0x1f244对应的物理页地址一致;2. 0x401f244对应的页表项读写位标志为只读;3. 页异常中断处理完毕后,0x401f244对应的物理页地址与0x1f244对应的物理页地址不一致;4. 0x401f244对应的页表项读写位标志为读写。

       计算线性地址对应的页表项地址,计算公式为(unsigned long *)  (((address>>10) & 0xffc) + (0xfffff000 & *((unsigned long *) ((address>>20) &0xffc))))。(注:address>>12&0x3ff获得页表项索引,每条页表项4字节,要获得页表偏移值还需要将页表项索引值左移2位,同理可得页目录项偏移值,而页目录基址为0,页目录项偏移值即页目录项的实际物理地址。)

       启动bochdgb进行调试,命令行如下:

<bochs:1> b 0x6790

<bochs:2> c

(0) Breakpoint 1, 0x4006790 in ?? ()

Next at t=16886891

(0) [0x00006790] 000f:00006790 (unk. ctxt): call .+0x6884             ; e8ef000000

 

       (0x401f244>>20) & 0xffc = 0x40(0x1f244>>20) & 0xffc = 0x0,查看0x400x0处的4字节内容,即相应的页表地址:

<bochs:3> x /1 0x40

[bochs]:

0x00000040 <bogus+       0>:    0x00ffe027

<bochs:4> x /1 0x0

[bochs]:

0x00000000 <bogus+       0>:    0x00001027

 

       (0x401f244>>10) & 0xffc = 0x7c(0x1f244>>10) & 0xffc = 0x7c0x7c + (0xfffff000 & 0xffe027) = 0xffe07c0x7c + (0xfffff000 & 0x1027) = 0x107c,查看0xffe07c0x107c4字节内容,即相应的物理地址:

<bochs:5> x /1 0xffe07c

[bochs]:

0x00ffe07c <bogus+       0>:    0x0001f065   //页基址0x1f000

<bochs:6> x /1 0x107c

[bochs]:

0x0000107c <bogus+       0>:    0x0001f067  //页基址0x1f000

 

       可以看到0x401f244的页面地址为0x1f000,第0位置1标志可用,第1位置0标志只读;0x1f244的页面地址也为0x1f000,第0位置1标志可用,第1位置1标志可读写。查看页中断处理之后的页表项内容:

<bochs:7> n   // 简单执行一条next语句即可,中断处理完毕将重启0x6790处的指令

(0) Breakpoint 1, 0x4006790 in ?? ()

Next at t=16889019

(0) [0x00006790] 000f:00006790 (unk. ctxt): call .+0x6884             ; e8ef0000

00

<bochs:8> x /1 0xffe07c

[bochs]:

0x00ffe07c <bogus+       0>:    0x00ffd007  // 重新申请到物理页,基址0xffd000,可读写

<bochs:9> x /1 0x107c

[bochs]:

0x0000107c <bogus+       0>:    0x0001f067

页异常中断概述

       下面的内容摘自Intel80386程序员手册9.8.14节。

This exception occurs when paging is enabled (PG=1) and the processor detects one of the following conditions while translating a linear address to a physical address:

● The page-directory or page-table entry needed for the address

translation has zero in its present bit.

● The current procedure does not have sufficient privilege to access the

indicated page.

 

The processor makes available to the page fault handler two items of information that aid in diagnosing the exception and recovering from it:

● An error code on the stack. The error code for a page fault has a format different from that for other exceptions (see Figure 9-8). The error code tells the exception handler three things:

1. Whether the exception was due to a not present page or to an access rights violation.

2. Whether the processor was executing at user or supervisor level at the time of the exception.

3. Whether the memory access that caused the exception was a read or write.

● CR2 (control register two). The processor stores in CR2 the linear address used in the access that caused the exception (see Figure 9-9). The exception handler can use this address to locate the corresponding page directory and page table entries. If another page fault can occur during execution of the page fault handler, the handler should push CR2 onto the stack.

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值