前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Linux下Page Fault的处理流程

Linux下Page Fault的处理流程

作者头像
KINGYT
发布2019-07-08 18:53:42
7.7K0
发布2019-07-08 18:53:42
举报

上篇文章 系统调用mmap的内核实现分析 中提到,当我们向操作系统申请内存时,操作系统并不是直接分配给我们物理内存,而是只标记当前进程拥有该段内存,当真正使用这段段内存时才会分配。

这种延迟分配物理内存的方式就通过page fault机制来实现的。

当我们访问一个内存地址时,如果该地址非法,或者我们对其没有访问权限,或者该地址对应的物理内存还未分配,cpu都会生成一个page fault,进而执行操作系统的page fault handler。

这个page fault handler里会检查该fault产生的原因,如果是地址非法或没有权限,则会向当前进程发送一个SIGSEGV signal,该signal默认会kill掉当前进程,并提示我们segmentation fault异常。

如果是因为还未分配物理内存,操作系统会立即分配物理内存给当前进程,然后重试产生这个page fault的内存访问指令,一般情况下都可以正常向下执行。

下面我们来看下对应的内核源码:

代码语言:javascript
复制
// 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方法。

代码语言:javascript
复制
// 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方法:

代码语言:javascript
复制
// 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方法继续处理。

代码语言:javascript
复制
// 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方法:

代码语言:javascript
复制
// 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方法:

代码语言:javascript
复制
// 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方法。

代码语言:javascript
复制
// 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的处理代码我们就分析完了,希望对你有所帮助。

完。

本文参与?腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-07-03,如有侵权请联系?cloudcommunity@tencent.com 删除

本文分享自 Linux内核及JVM底层相关技术研究 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与?腾讯云自媒体分享计划? ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
http://www.vxiaotou.com