美文网首页
1.用户空间内存之缺页分析

1.用户空间内存之缺页分析

作者: 柯基是只dog | 来源:发表于2019-01-07 15:05 被阅读0次

    在linux中使用了分页机制,分页机制把线性地址空间分成固定大小的页面,如果包含线性地址的页面当前不在物理内存中,处理器就会产生一个页错误异常。然后交给内核处理。

    在init/main.c中,内核调用了trap_init方法初始化中断处理,其中就设置了缺页异常,page_fault是一个用汇编实现的方法,它只是把实际处理方法do_page_fault入栈,然后跳转到统一的错误中断入口,所以我们可以直接跳到do_page_fault去看看

    void trap_init(void)
    {
        int i;
    
        // ......
        set_trap_gate(13,&general_protection);
        set_trap_gate(14,&page_fault);      /*缺页中断*/
        // ......
    }
    
    // sys_call.S 中的page_fault方法,只是把do_page_fault入栈
    .align 4
    _page_fault:
        pushl $_do_page_fault
        jmp error_code
    

    do_page_fault方法中,我们现在仅仅需要看用户空间的,内核空间的缺页暂时不管

    asmlinkage void do_page_fault(struct pt_regs *regs, unsigned long error_code)
    {
        unsigned long address;
        unsigned long user_esp = 0;
        unsigned int bit;
    
        /*从cr2中读取引起页错误的地址*/
        __asm__("movl %%cr2,%0":"=r" (address));
        /*如果是用户空间*/
        if (address < TASK_SIZE) {
            if (error_code & 4) {   /* user mode access? */
                if (regs->eflags & VM_MASK) {
                    bit = (address - 0xA0000) >> PAGE_SHIFT;
                    if (bit < 32)
                        current->screen_bitmap |= 1 << bit;
                } else 
                    user_esp = regs->esp;
            }
            // 错误码如果是1代表访问了非法的物理地址
            if (error_code & 1)
                do_wp_page(error_code, address, current, user_esp);
            // 错误码为0代表访问了不存在的物理地址
            else
                do_no_page(error_code, address, current, user_esp);
            return;
        }
    }
    
    页表项

    先看页不存在吧,也就是do_no_page,该版本的linux还是使用二级目录,也就是把32位线性地址分成3部分(目录,页面,页内偏移)。get_empty_pgtable方法就是取前10位取出目录页

    page是目录页,接下来就从中间10位取出页表项,如果页表项存在,直接返回该页

    /* 要访问的地址不在内存当中
     */
    void do_no_page(unsigned long error_code, unsigned long address,
        struct task_struct *tsk, unsigned long user_esp)
    {
        unsigned long tmp;
        unsigned long page;
        struct vm_area_struct * mpnt;
    
        /* 
         * 根据前10位目录标记取出目录页
         */
        page = get_empty_pgtable(tsk,address);
        if (!page)
            return;
        page &= PAGE_MASK;
    
        // 根据中间10位从目录页中取地址页
        page += PAGE_PTR(address);
        tmp = *(unsigned long *) page;
        // 如果地址页存在直接返回
        if (tmp & PAGE_PRESENT)
            return;
    
        /*增加进程在内核中占用的物理页的数量*/
        ++tsk->rss;
        /*如果缺页的内存在交换区中则将交换区中的内存交换到内存*/
        if (tmp) {
            ++tsk->maj_flt;
            /*注意此处的page是页表项的地址*/
            swap_in((unsigned long *) page);
            return;
        }
        
        /* vm_area_struct中的地址是和4KB对齐的
         */
        address &= 0xfffff000;
        tmp = 0;
        /* 此处注意虚拟地址链表是按照地址大小顺序来排列的,目前版本内核是链表的,
         * 在高版本内核中是二叉树结构(AVL),
         */
        for (mpnt = tsk->mmap; mpnt != NULL; mpnt = mpnt->vm_next) {
            if (address < mpnt->vm_start)
                break;
            if (address >= mpnt->vm_end) {
                tmp = mpnt->vm_end;
                continue;
            }
            // 执行到该处说明找到了一个地址处于mmap区间内的虚拟地址段
    
            // vm_ops是该内存段的操作属性,类似file的op
            // 里面涵盖了该内存的一些方法执行的动作,其中就有缺页nopage
            if (!mpnt->vm_ops || !mpnt->vm_ops->nopage) {
                ++tsk->min_flt;
                get_empty_page(tsk,address);
                return;
            }
            // 如果指定了nopage方法则调用
            mpnt->vm_ops->nopage(error_code, mpnt, address);
            return;
        }
        
        // 执行到再最终检查
        // 如果进程不是当前进程
        // 或者地址大于进程end_data又小于brk
        // 其他情况都杀死进程
        if (tsk != current)
            goto ok_no_page;
        if (address >= tsk->end_data && address < tsk->brk)
            goto ok_no_page;
        if (mpnt && mpnt == tsk->stk_vma &&
            address - tmp > mpnt->vm_start - address &&
            tsk->rlim[RLIMIT_STACK].rlim_cur > mpnt->vm_end - address) {
            mpnt->vm_start = address;
            goto ok_no_page;
        }
        /*cr2记录缺页地址*/
        tsk->tss.cr2 = address;
        current->tss.error_code = error_code;
        current->tss.trap_no = 14;
        /*发送段错误信号,杀死进程*/
        send_sig(SIGSEGV,tsk,1);
        if (error_code & 4) /* user level access? */
            return;
    ok_no_page:
        ++tsk->min_flt;
        get_empty_page(tsk,address);
    }
    

    交换区,上面代码中,从目录页取出对应的页表项的时候,如果页表项的P位存在则直接返回对应的页,如果P不存在但页表项又不全为0,则从交换区内换到内存。在swap_in方法中,entry是页表项指针对应的内容,也就是下图中32位的数据,用一个long型结构保存着。


    image.png
    void swap_in(unsigned long *table_ptr)
    {
        unsigned long entry;
        unsigned long page;
    
        entry = *table_ptr;
        // 如果该页的P位为1直接返回
        if (PAGE_PRESENT & entry) {
            printk("trying to swap in present page\n");
            return;
        }
        // 如果entry全0,也直接返回
        if (!entry) {
            printk("No swap page in swap_in\n");
            return;
        }
    
        // SWP_TYPE是宏,取后8位并右移1位(也就是取跳过P位的后7位)
        // 如果type=SHM_SWP_TYPE代表属于共享内存
        if (SWP_TYPE(entry) == SHM_SWP_TYPE) {
            shm_no_page ((unsigned long *) table_ptr);
            return;
        }
        
        // 申请一页内存,如果申请失败则oom退出进程ß
        if (!(page = get_free_page(GFP_KERNEL))) {
            oom(current);
            page = BAD_PAGE;
        } else  
            read_swap_page(entry, (char *) page);
        if (*table_ptr != entry) {
            free_page(page);
            return;
        }
        /* 设置表项和内存页的映射关系*/
        *table_ptr = page | (PAGE_DIRTY | PAGE_PRIVATE);
        swap_free(entry);
    }
    

    参考文章:
    swap机制概述

    相关文章

      网友评论

          本文标题:1.用户空间内存之缺页分析

          本文链接:https://www.haomeiwen.com/subject/tnoyrqtx.html