Linux用户进程内存分配及二级页表PTE的二三事
2011-02-28 23:00
274 查看
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状态,执行页面处理函数,这是会不会效率有点低了呢?待研究。
handle_pte_fault()
上文最后提到了handle_pte_fault()这个函数,用来处理页错,分配PTE。为了更清楚的了解PTE是如何申请到的,还是有必要深究一下。
handle_pte_fault()有几个函数用来检查当前pte的状态:
pte_present() 检测页面是否在内存中
pte_none() 检测页表项是否为空
pte_file() 同一地址多映射(此函数不重要)
vm->ops->fault标记位
内核用likely对其做了标记,说明这个标记一般满足,适用于已经建立好虚拟内存和文件的映射关系的情况。
1)针对满足pte_present()函数,即PTE不在内存中,会在以4个下函数中选择一个进行处理:
(1)do_linear_fault();
最常见的情况,PTE表项为空,但满足vm->ops->fault,说明已经在内存中建立虚拟内存和文件的映射关系。
(2)do_anonymous_page();
PTE表项为空,但是没有建立和文件的映射关系,说明是第一次demanding page。
(3)do_nonlinear_fault();
PTE表项非空,满足pte_file()检查,对同一个物理地址做多个虚拟映射。
(4)do_swap_page();
PTE表项非空,不满足pte_file()检查,此页将会被换出。
2)如果不满足pte_present(),即PTE在内存中,则会执行下面的COW操作:
COW的全称叫做“copy on write”,即写时复制。这一块是涉及到两个进程共享操作的,简单的说两个进程可以共享页面(特别是fork出的进程),只有当一个进程需要写入文件时,才从同一页面复制一份副本。下面的函数调用do_wp_page(),将生成的复制页赋值给写进程。由于我仅仅跟了系统的“/sbin/init”,所以这里根本没有调用到。
附:
do_anonymous_page()函数的跟踪
——>mk_pte() 构建映射表
——>ptn_pte()
——>__pte((ptn << 12 | pgprot_val)
最终生成的PTE为32bit,其中20bit物理地址,12bit控制信息
相关文章推荐
- Linux用户进程内存分配及二级页表PTE的二三事
- 关于进程页表和页目录是存放在内核空间,还是用户空间,低端还是高端内存的思考和验证
- Linux用户进程内存空间分析
- Linux用户进程是如何释放内存的
- Linux内存点滴:用户进程内存空间
- Linux用户进程是如何释放内存的[zt]
- Linux内存点滴:用户进程内存空间
- Linux进程分配内存的两种方式--brk() 和mmap()
- Linux的用户进程是如何使用内存的
- C/C++内存分配与Linux内存管理进程所涉及到的五个数据段
- Linux内存点滴 用户进程内存空间
- Linux内存点滴:用户进程内存空间
- 进程的页表和页目录存储在内核空间还是用户空间?低端内存还是高端内存
- Linux的用户进程是如何使用内存
- Linux用户进程内存泄露一种检测方法
- Linux内存点滴 用户进程内存空间
- Linux的用户进程是如何使用内存的
- Linux内存点滴 用户进程内存空间
- linux c二级指针的内存分配和使用
- linux进程用户内存空间和内核空间