1.用戶空間內(nèi)存之缺頁(yè)分析

在linux中使用了分頁(yè)機(jī)制,分頁(yè)機(jī)制把線性地址空間分成固定大小的頁(yè)面,如果包含線性地址的頁(yè)面當(dāng)前不在物理內(nèi)存中,處理器就會(huì)產(chǎn)生一個(gè)頁(yè)錯(cuò)誤異常。然后交給內(nèi)核處理。

在init/main.c中,內(nèi)核調(diào)用了trap_init方法初始化中斷處理,其中就設(shè)置了缺頁(yè)異常,page_fault是一個(gè)用匯編實(shí)現(xiàn)的方法,它只是把實(shí)際處理方法do_page_fault入棧,然后跳轉(zhuǎn)到統(tǒng)一的錯(cuò)誤中斷入口,所以我們可以直接跳到do_page_fault去看看

void trap_init(void)
{
    int i;

    // ......
    set_trap_gate(13,&general_protection);
    set_trap_gate(14,&page_fault);      /*缺頁(yè)中斷*/
    // ......
}

// sys_call.S 中的page_fault方法,只是把do_page_fault入棧
.align 4
_page_fault:
    pushl $_do_page_fault
    jmp error_code

do_page_fault方法中,我們現(xiàn)在僅僅需要看用戶空間的,內(nèi)核空間的缺頁(yè)暫時(shí)不管

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中讀取引起頁(yè)錯(cuò)誤的地址*/
    __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;
        }
        // 錯(cuò)誤碼如果是1代表訪問(wèn)了非法的物理地址
        if (error_code & 1)
            do_wp_page(error_code, address, current, user_esp);
        // 錯(cuò)誤碼為0代表訪問(wèn)了不存在的物理地址
        else
            do_no_page(error_code, address, current, user_esp);
        return;
    }
}
頁(yè)表項(xiàng)

先看頁(yè)不存在吧,也就是do_no_page,該版本的linux還是使用二級(jí)目錄,也就是把32位線性地址分成3部分(目錄,頁(yè)面,頁(yè)內(nèi)偏移)。get_empty_pgtable方法就是取前10位取出目錄頁(yè)

page是目錄頁(yè),接下來(lái)就從中間10位取出頁(yè)表項(xiàng),如果頁(yè)表項(xiàng)存在,直接返回該頁(yè)

/* 要訪問(wèn)的地址不在內(nèi)存當(dāng)中
 */
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;

    /* 
     * 根據(jù)前10位目錄標(biāo)記取出目錄頁(yè)
     */
    page = get_empty_pgtable(tsk,address);
    if (!page)
        return;
    page &= PAGE_MASK;

    // 根據(jù)中間10位從目錄頁(yè)中取地址頁(yè)
    page += PAGE_PTR(address);
    tmp = *(unsigned long *) page;
    // 如果地址頁(yè)存在直接返回
    if (tmp & PAGE_PRESENT)
        return;

    /*增加進(jìn)程在內(nèi)核中占用的物理頁(yè)的數(shù)量*/
    ++tsk->rss;
    /*如果缺頁(yè)的內(nèi)存在交換區(qū)中則將交換區(qū)中的內(nèi)存交換到內(nèi)存*/
    if (tmp) {
        ++tsk->maj_flt;
        /*注意此處的page是頁(yè)表項(xiàng)的地址*/
        swap_in((unsigned long *) page);
        return;
    }
    
    /* vm_area_struct中的地址是和4KB對(duì)齊的
     */
    address &= 0xfffff000;
    tmp = 0;
    /* 此處注意虛擬地址鏈表是按照地址大小順序來(lái)排列的,目前版本內(nèi)核是鏈表的,
     * 在高版本內(nèi)核中是二叉樹(shù)結(jié)構(gòu)(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;
        }
        // 執(zhí)行到該處說(shuō)明找到了一個(gè)地址處于mmap區(qū)間內(nèi)的虛擬地址段

        // vm_ops是該內(nèi)存段的操作屬性,類(lèi)似file的op
        // 里面涵蓋了該內(nèi)存的一些方法執(zhí)行的動(dòng)作,其中就有缺頁(yè)nopage
        if (!mpnt->vm_ops || !mpnt->vm_ops->nopage) {
            ++tsk->min_flt;
            get_empty_page(tsk,address);
            return;
        }
        // 如果指定了nopage方法則調(diào)用
        mpnt->vm_ops->nopage(error_code, mpnt, address);
        return;
    }
    
    // 執(zhí)行到再最終檢查
    // 如果進(jìn)程不是當(dāng)前進(jìn)程
    // 或者地址大于進(jìn)程end_data又小于brk
    // 其他情況都?xì)⑺肋M(jìn)程
    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記錄缺頁(yè)地址*/
    tsk->tss.cr2 = address;
    current->tss.error_code = error_code;
    current->tss.trap_no = 14;
    /*發(fā)送段錯(cuò)誤信號(hào),殺死進(jìn)程*/
    send_sig(SIGSEGV,tsk,1);
    if (error_code & 4) /* user level access? */
        return;
ok_no_page:
    ++tsk->min_flt;
    get_empty_page(tsk,address);
}

交換區(qū),上面代碼中,從目錄頁(yè)取出對(duì)應(yīng)的頁(yè)表項(xiàng)的時(shí)候,如果頁(yè)表項(xiàng)的P位存在則直接返回對(duì)應(yīng)的頁(yè),如果P不存在但頁(yè)表項(xiàng)又不全為0,則從交換區(qū)內(nèi)換到內(nèi)存。在swap_in方法中,entry是頁(yè)表項(xiàng)指針對(duì)應(yīng)的內(nèi)容,也就是下圖中32位的數(shù)據(jù),用一個(gè)long型結(jié)構(gòu)保存著。


image.png
void swap_in(unsigned long *table_ptr)
{
    unsigned long entry;
    unsigned long page;

    entry = *table_ptr;
    // 如果該頁(yè)的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位(也就是取跳過(guò)P位的后7位)
    // 如果type=SHM_SWP_TYPE代表屬于共享內(nèi)存
    if (SWP_TYPE(entry) == SHM_SWP_TYPE) {
        shm_no_page ((unsigned long *) table_ptr);
        return;
    }
    
    // 申請(qǐng)一頁(yè)內(nèi)存,如果申請(qǐng)失敗則oom退出進(jìn)程?
    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;
    }
    /* 設(shè)置表項(xiàng)和內(nèi)存頁(yè)的映射關(guān)系*/
    *table_ptr = page | (PAGE_DIRTY | PAGE_PRIVATE);
    swap_free(entry);
}

參考文章:
swap機(jī)制概述

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 前言 內(nèi)存管理一向是所有操作系統(tǒng)書(shū)籍不惜筆墨重點(diǎn)討論的內(nèi)容,無(wú)論市面上或是網(wǎng)上都充斥著大量涉及內(nèi)存管理的教材和資料...
    木有sky閱讀 963評(píng)論 0 1
  • 1 內(nèi)存尋址 1.1 物理地址、虛擬地址以及線性地址 物理地址: 物理內(nèi)存的內(nèi)存單元地址 虛擬地址: 程序員看到的...
    瘋狂小王子閱讀 3,129評(píng)論 3 21
  • >計(jì)算機(jī)系統(tǒng)中有幾類(lèi)存儲(chǔ)設(shè)備:cache、內(nèi)存、外存。cache的存取速度最高,可以和CPU匹配,因此其代價(jià)最高,...
    一生信仰閱讀 1,282評(píng)論 0 0
  • 最近開(kāi)始想稍微深入一點(diǎn)地學(xué)習(xí)Linux內(nèi)核,主要參考內(nèi)容是《深入理解Linux內(nèi)核》和《深入理解Linux內(nèi)核架構(gòu)...
    ice_camel閱讀 1,932評(píng)論 0 2
  • Introduction 該 lab 主要需要編寫(xiě)操作系統(tǒng)的內(nèi)存管理部分。內(nèi)存管理分為兩個(gè)部分: 內(nèi)核的物理內(nèi)存分...
    找不到工作閱讀 12,854評(píng)論 0 12

友情鏈接更多精彩內(nèi)容