分类: 嵌入式
2011-08-14 19:33:57
Linux用户进程内存分配及二级页表PTE的二三事
我们在用调试器看Linux用户进程代码时,发现了一件很有意思的事情,在一段内存空间中,有一整页(4K)都是data abort,如下:
第一页4011c000数据正常
... ...
4011cfec [0xe28dd014] add r13,r13,#0x14
4011cff0 [0xe8bd40f0] ldmfd r13!,{r4-r7,r14}
4011cff4 [0xe12fff1e] bx r14
4011cff8 [0xe92d41f0] stmfd r13!,{r4-r8,r14}
4011cffc [0xe59f4064] ldr r4,0x4011d068
第二页4011d000 都是Data abort
4011d000 *** Data abort ***
4011d004 *** Data abort ***
4011d008 *** Data abort ***
4011d00c *** Data abort ***
... ...
第三页 4011e000 数据正常
4011e000 数据正常
由于当时并不知道Linux是如何处理用户进程的内存分配,所以认为这是一个“错误”。既然有错误,我们决定找到这个问题发生的根源。
在追踪这个问题的过程中,leeming同学做了一个很BT的实验。我简单说两句,详细大家可以去看他的文章(http://blog.chinaunix.net/u3/99423/showart_2096904.html)。大概的方法就是将物理内存全部dump出来,通过第一页的代码,比如0xe59f4064,查找其在内存中的物理地址,再通过提取物理地址的前20位,就可以查找Linux系统的二级页表(对应ARM的TLB,Linux中叫PTE)。这个实验虽然看上去很不可思议,但实现起来并不复杂。最终得到了Linux存放在内存中的TLB数据。
每个表项是32bit
虚拟页地址 对应表项的内容
4011c000 3156caae
4011d000 00000000
4011e000 3156faae
很有意思,Data abort的数据段,对应的PTE也是空的,这难道是一个系统错误?
NO!经过进一步的学习后发现,在Linux系统中,这是一个很正常的现象。Linux在用户进程执行时并没有建立所有内存页面的映射,而是需要用到的时候再建立映射关系。当Linux用户进程访问到没有建立映射的页表(此时PTE指针为0),会调用相应的函数进行处理,或建立、或换出,具体执行这个操作的函数叫handle_pte_fault(),位于内核的mm/memory.c中。
但是,Linux是如何进入缺页处理的呢?
有两种情况,都是利用了ARM处理器的异常中断进行相应的处理。
第一种是程序顺序执行,正常页面的最后一条指令执行完后进入空页面,当空页面的第一条指令进入ARM处理器流水线的执行周期时,ARM处理器会报告一个指令预取异常中断,并跳转到地址0x0c,在Linux系统中由于使用了高地址向量表,所以会跳转到0xffff000c。此时ARM处理器进入ABORT状态,执行一系列代码保存现场(代码位于/arch/arm/kernel/entry-armv.s),然后进入SVC状态执行arch/arm/mm/fault.c中的do_PrefetchAbort(),最后会调用handle_pte_fault()处理缺页异常。
第二种情况,页面中的程序执行时需要使用未分配页面的数据,比如“ldr r0,未分配页地址”。遇到这种情况,就不是指令预取异常了,而是数据访问异常(Data abort)。此时处理器依然会进入ABORT状态,跳转到0xffff0010执行相应的vector_dabt代码(entry_armv.s)保存状态,进入SVC态,执行do_DataAbort()函数,最后同样调用handle_pte_fault()处理缺页异常。
因此,最开始遇到的情况:三个PTE,中间是空的,这是一个很正常的情况。因为第三页很可能由于前面的调用而已经建立,第二页却还没有建立。
至于handle_pte_fault()如何处理缺页异常,我还没有看完,就不在本文讨论了。已知至少有do_no_page()、do_swap_page()、do_wp_page等多种方式,此为后话。
通过跟踪用户程序,发现Linux用户进程基本所有的页面都是这样处理,因此处理器会很频繁的进出Abort状态,执行页面处理函数,这是会不会效率有点低了呢?待研究。
阿虚(Rockie Cheng)
2009-11-21