上篇文章 系统调用mmap的内核实现分析 中提到,当我们向操作系统申请内存时,操作系统并不是直接分配给我们物理内存,而是只标记当前进程拥有该段内存,当真正使用这段段内存时才会分配。
这种延迟分配物理内存的方式就通过page fault机制来实现的。
当我们访问一个内存地址时,如果该地址非法,或者我们对其没有访问权限,或者该地址对应的物理内存还未分配,cpu都会生成一个page fault,进而执行操作系统的page fault handler。
这个page fault handler里会检查该fault产生的原因,如果是地址非法或没有权限,则会向当前进程发送一个SIGSEGV signal,该signal默认会kill掉当前进程,并提示我们segmentation fault异常。
如果是因为还未分配物理内存,操作系统会立即分配物理内存给当前进程,然后重试产生这个page fault的内存访问指令,一般情况下都可以正常向下执行。
下面我们来看下对应的内核源码:
// arch/x86/mm/fault.c
dotraplinkage void notrace
do_page_fault(struct pt_regs *regs, unsigned long error_code)
{
unsigned long address = read_cr2(); /* Get the faulting address */
...
__do_page_fault(regs, error_code, address);
...
}
NOKPROBE_SYMBOL(do_page_fault);
该方法先从cr2寄存器中读出产生这个page fault的虚拟内存地址,然后再调用__do_page_fault方法。
// arch/x86/mm/fault.c
static noinline void
__do_page_fault(struct pt_regs *regs, unsigned long hw_error_code,
unsigned long address)
{
...
/* Was the fault on kernel-controlled part of the address space? */
if (unlikely(fault_in_kernel_space(address)))
do_kern_addr_fault(regs, hw_error_code, address);
else
do_user_addr_fault(regs, hw_error_code, address);
}
NOKPROBE_SYMBOL(__do_page_fault);
该方法会检查该地址是属于kernel space还是user space,如果是user space,则会调用do_user_addr_fault方法。
有关kernel space和user space的内存空间分布,可以看下这份文档:
https://www.kernel.org/doc/Documentation/x86/x86_64/mm.txt
继续do_user_addr_fault方法:
// arch/x86/mm/fault.c
static inline
void do_user_addr_fault(struct pt_regs *regs,
unsigned long hw_error_code,
unsigned long address)
{
struct vm_area_struct *vma;
struct task_struct *tsk;
struct mm_struct *mm;
...
tsk = current;
mm = tsk->mm;
...
vma = find_vma(mm, address);
if (unlikely(!vma)) {
bad_area(regs, hw_error_code, address);
return;
}
if (likely(vma->vm_start <= address))
goto good_area;
...
good_area:
...
fault = handle_mm_fault(vma, address, flags);
...
}
NOKPROBE_SYMBOL(do_user_addr_fault);
该方法会先从mm中找包含address的内存段,如果没有,则说明我们访问了一个非法地址,该方法进而会调用bad_area方法,向当前进程发送一个SIGSEGV signal。
如果找到了对应的内存段,则会调用handle_mm_fault方法继续处理。
// mm/memory.c
vm_fault_t handle_mm_fault(struct vm_area_struct *vma, unsigned long address,
unsigned int flags)
{
vm_fault_t ret;
...
if (unlikely(is_vm_hugetlb_page(vma)))
...
else
ret = __handle_mm_fault(vma, address, flags);
...
return ret;
}
EXPORT_SYMBOL_GPL(handle_mm_fault);
该方法又调用了__handle_mm_fault方法:
// mm/memory.c
static vm_fault_t __handle_mm_fault(struct vm_area_struct *vma,
unsigned long address, unsigned int flags)
{
struct vm_fault vmf = {
.vma = vma,
.address = address & PAGE_MASK,
...
};
...
struct mm_struct *mm = vma->vm_mm;
pgd_t *pgd;
p4d_t *p4d;
vm_fault_t ret;
pgd = pgd_offset(mm, address);
p4d = p4d_alloc(mm, pgd, address);
...
vmf.pud = pud_alloc(mm, p4d, address);
...
vmf.pmd = pmd_alloc(mm, vmf.pud, address);
...
return handle_pte_fault(&vmf);
}
该方法通过page walk,先找到pmd,有关什么是page walk,请看下面这篇文章:
https://lwn.net/Articles/717293/
再之后,又调用了handle_pte_fault方法:
// mm/memory.c
static vm_fault_t handle_pte_fault(struct vm_fault *vmf)
{
pte_t entry;
if (unlikely(pmd_none(*vmf->pmd))) {
vmf->pte = NULL;
} else {
...
vmf->pte = pte_offset_map(vmf->pmd, vmf->address);
vmf->orig_pte = *vmf->pte;
...
if (pte_none(vmf->orig_pte)) {
...
vmf->pte = NULL;
}
}
if (!vmf->pte) {
if (vma_is_anonymous(vmf->vma))
return do_anonymous_page(vmf);
else
return do_fault(vmf);
}
...
}
此时,vmf->pte应该为null。
该方法通过vma_is_anonymous方法,判断vmf->vma对应的内存段是否是anonymous的,如果是,则调用do_anonymous_page,如果不是,比如mmap file产生的vma,则调用do_fault。
因为上一篇文章的示例中,我们调用mmap指定的是anonymous,所以在这里我们继续看do_anonymous_page方法。
// mm/memory.c
static vm_fault_t do_anonymous_page(struct vm_fault *vmf)
{
struct vm_area_struct *vma = vmf->vma;
...
struct page *page;
...
pte_t entry;
...
page = alloc_zeroed_user_highpage_movable(vma, vmf->address);
...
entry = mk_pte(page, vma->vm_page_prot);
...
set_pte_at(vma->vm_mm, vmf->address, vmf->pte, entry);
...
return ret;
...
}
该方法先调用alloc_zeroed_user_highpage_movable分配一个新的page,这个就是物理内存了。
然后调用mk_pte方法,把page的地址信息等记录到entry里。
最后,把这个entry写入到vmf->pte指向的内存中。
这样在下次再访问这个page对应的虚拟内存地址时,page walk就可以在pte中找到这个page了。
到此,有关page fault的处理代码我们就分析完了,希望对你有所帮助。
完。
本文分享自 Linux内核及JVM底层相关技术研究 微信公众号,前往查看
如有侵权,请联系 cloudcommunity@tencent.com 删除。
本文参与?腾讯云自媒体分享计划? ,欢迎热爱写作的你一起参与!