在线不卡日本ⅴ一区v二区_精品一区二区中文字幕_天堂v在线视频_亚洲五月天婷婷中文网站

  • <menu id="lky3g"></menu>
  • <style id="lky3g"></style>
    <pre id="lky3g"><tt id="lky3g"></tt></pre>

    「技術(shù)干貨」ARM64內(nèi)核源碼解讀:mmu-gather操作

    「技術(shù)干貨」ARM64內(nèi)核源碼解讀:mmu-gather操作

    環(huán)境:

    • 處理器架構(gòu):arm64
    • 內(nèi)核源碼:linux-5.10.50
    • ubuntu版本:20.04.1
    • 代碼閱讀工具:vim+ctags+cscope

    本文講解Linux內(nèi)核虛擬內(nèi)存管理中的mmu_gather操作,看看它是如何保證刷tlb和釋放物理頁的順序的,又是如何將更多的頁面聚集起來統(tǒng)一釋放的。

    嵌入式進階教程分門別類整理好了,看的時候十分方便,由于內(nèi)容較多,這里就截取一部分圖吧。

    需要的朋友私信【內(nèi)核】即可領(lǐng)取。

    通常在進程退出或者執(zhí)行munmap的時候,內(nèi)核會解除相關(guān)虛擬內(nèi)存區(qū)域的頁表映射,刷/無效tlb,并釋放/回收相關(guān)的物理頁面,這一過程的正確順序如下:

    1)解除頁表映射

    2)刷相關(guān)tlb

    3)釋放物理頁面

    在刷相關(guān)虛擬內(nèi)存區(qū)域tlb之前,絕對不能先釋放物理頁面,否則可能導(dǎo)致不正確的結(jié)果,而mmu-gather(mmu 積聚)的作用就是保證這種順序,并將需要釋放的相關(guān)的物理頁面聚集起來統(tǒng)一釋放。

    2.源代碼解讀

    2.1 重要數(shù)據(jù)結(jié)構(gòu)

    首先我們先介紹一下,與mmu-gather相關(guān)的一些重要結(jié)構(gòu)體,對于理解源碼很有幫助。

    相關(guān)的主要數(shù)據(jù)結(jié)構(gòu)有三個:

    struct mmu_gather

    struct mmu_table_batch

    struct mmu_gather_batch

    1)mmu_gather

    來表示一次mmu積聚操作,在每次解除相關(guān)虛擬內(nèi)存區(qū)域時使用。

    struct mmu_gather { struct mm_struct *mm;#ifdef CONFIG_MMU_GATHER_TABLE_FREE struct mmu_table_batch *batch;#endif unsigned long start; unsigned long end; /* * we are in the middle of an operation to clear * a full mm and can make some optimizations */ unsigned int fullmm : 1; /* * we have performed an operation which * requires a complete flush of the tlb */ unsigned int need_flush_all : 1; /* * we have removed page directories */ unsigned int freed_tables : 1; /* * at which levels have we cleared entries? */ unsigned int cleared_ptes : 1; unsigned int cleared_pmds : 1; unsigned int cleared_puds : 1; unsigned int cleared_p4ds : 1; /* * tracks VM_EXEC | VM_HUGETLB in tlb_start_vma */ unsigned int vma_exec : 1; unsigned int vma_huge : 1; unsigned int batch_count; #ifndef CONFIG_MMU_GATHER_NO_GATHER struct mmu_gather_batch *active; struct mmu_gather_batch local; struct page *__pages[MMU_GATHER_BUNDLE]; … #endif };

    其中, mm 表示操作哪個進程的虛擬內(nèi)存;batch 用于積聚進程各級頁目錄的物理頁;start和end 表示操作的起始和結(jié)束虛擬地址,這兩個地址在處理過程中會被相應(yīng)的賦值;fullmm 表示是否操作整個用戶地址空間;freed_tables 表示我們已經(jīng)釋放了相關(guān)的目錄;cleared_ptes/pmds/puds/p4ds 表示我們在哪個級別上清除了表項;vma_exec 表示操作的是否為可執(zhí)行的vma;vma_huge 表示操作的是否為hugetlb的vma;batch_count 表示積聚了多少個“批次”,后面會講到 ;active、local和__pages 和多批次釋放物理頁面相關(guān);active表示當(dāng)前處理的批次,local表示“本地”批次,__pages表示“本地”批次積聚的物理頁面。

    這里需要說明一點就是,mmu積聚操作會涉及到local批次和多批次操作,local批次操作的物理頁面相關(guān)的struct page數(shù)組內(nèi)嵌到mmu_gather結(jié)構(gòu)的__pages中,且我們發(fā)現(xiàn)這個數(shù)組大小為8,也就是local批次最大積聚8 * 4k = 32k的內(nèi)存大小,這因為mmu_gather結(jié)構(gòu)通常在內(nèi)核棧中分配,不能占用太多的內(nèi)核??臻g,而多批次由于動態(tài)分配批次積聚結(jié)構(gòu)所以每個批次能積聚更多的頁面。

    2)mmu_table_batch

    用于積聚進程使用的各級頁目錄的物理頁,在釋放進程相關(guān)的頁目錄的物理頁時使用(文章中稱為頁表批次的積聚結(jié)構(gòu))。

    struct mmu_table_batch {#ifdef CONFIG_MMU_GATHER_RCU_TABLE_FREE struct rcu_head rcu; #endif unsigned int nr; void *tables[0]; };

    rcu 用于rcu延遲釋放頁目錄的物理頁;

    nr 表示頁目錄的物理頁的積聚結(jié)構(gòu)的page數(shù)組中頁面?zhèn)€數(shù);

    tables 表示頁表積聚結(jié)構(gòu)的page數(shù)組。

    3)mmu_gather_batch

    表示物理頁的積聚批次,用于積聚進程映射到用戶空間物理頁(文章中稱為批次的積聚結(jié)構(gòu))。

    struct mmu_gather_batch { struct mmu_gather_batch *next; unsigned int nr; unsigned int max; struct page *pages[0]; };

    next 用于多批次積聚物理頁時,連接下一個積聚批次結(jié)構(gòu) ;

    nr 表示本次批次的積聚數(shù)組的頁面?zhèn)€數(shù);

    max 表示本次批次的積聚數(shù)組最大的頁面?zhèn)€數(shù);

    pages 表示本次批次積聚結(jié)構(gòu)的page數(shù)組。

    2.2 總體調(diào)用

    通常mmu-gather操作由以下幾部分函數(shù)組成:

    tlb_gather_mmu

    unmap_vmas

    free_pgtables

    tlb_finish_mmu

    其中tlb_gather_mmu表示mmu-gather初始化,也就是struct mmu_gather的初始化;

    unmap_vmas 表示解除相關(guān)虛擬內(nèi)存區(qū)域的頁表映射;

    free_pgtables 表示釋放頁表操作 ;

    tlb_finish_mmu 表示進行刷tlb和釋放物理按鈕操作。

    2.3 tlb_gather_mmu

    這個函數(shù)主要是初始化從進程內(nèi)核棧中傳遞過來的mmu_gather結(jié)構(gòu)。

    void tlb_gather_mmu(struct mmu_gather *tlb, struct mm_struct *mm, unsigned long start, unsigned long end) { tlb->mm = mm; //賦值進程的內(nèi)存描述符 /* Is it from 0 to ~0? */ tlb->fullmm = !(start | (end+1)); //如果是操作進程整個地址空間,則 start=0, end=-1,這個時候 fullmm會被賦值1 #ifndef CONFIG_MMU_GATHER_NO_GATHER tlb->need_flush_all = 0; //初始化“本地”積聚相關(guān)成員 tlb->local.next = NULL; tlb->local.nr = 0; tlb->local.max = ARRAY_SIZE(tlb->__pages); tlb->active = &tlb->local; //active指向本地的積聚結(jié)構(gòu) tlb->batch_count = 0; #endif tlb_table_init(tlb); // tlb->batch = NULL ,來表示先不使用多批次積聚 #ifdef CONFIG_MMU_GATHER_PAGE_SIZE tlb->page_size = 0; #endif __tlb_reset_range(tlb); //tlb->start/end 和 lb->freed_tables、tlb->cleared_xxx初始化 inc_tlb_flush_pending(tlb->mm); }

    下面給出tlb_gather_mmu時的圖解:

    2.4 unmap_vmas

    這個函數(shù)用于解除相關(guān)進程虛擬內(nèi)存區(qū)域的頁表映射,還會將相關(guān)的物理頁面放入積聚結(jié)構(gòu)中,后面統(tǒng)一釋放。

    下面我們來看下這個函數(shù):

    void unmap_vmas(struct mmu_gather *tlb, struct vm_area_struct *vma, unsigned long start_addr, unsigned long end_addr){ … for ( ; vma && vma->vm_start vm_next) unmap_single_vma(tlb, vma, start_addr, end_addr, NULL); …}

    函數(shù)傳遞進已經(jīng)初始化好的mmu積聚結(jié)構(gòu)、操作的起始vma、以及虛擬內(nèi)存范圍[start_addr, end_addr], 然后調(diào)用unmap_single_vma來操作這個范圍內(nèi)的每一個vma。

    unmap_single_vma地實現(xiàn)相關(guān)代碼比較多,在此不再贅述,我們會分析關(guān)鍵代碼,它主要做的工作為:通過遍歷進程的多級頁表,來找到vma中每一個虛擬頁對應(yīng)的物理頁(存在的話),然后解除虛擬頁到物理頁的映射關(guān)系,最后將物理頁放入積聚結(jié)構(gòu)中。

    總體調(diào)用如下:

    // mm/memory.cunmap_vmas-> unmap_single_vma //處理單個vma -> unmap_page_range ->zap_p4d_range //遍歷pge頁目錄中每一個p4d表項 ->zap_pud_range //遍歷p4d頁目錄中每一個pud表項 ->zap_pmd_range //遍歷pud頁目錄中每一個pmd表項 ->zap_pte_range //遍歷pmd頁目錄中每一個pmd表項

    下面我們省略中間各級頁表的遍歷過程,重點看下最后一級頁表的處理(這段代碼相當(dāng)關(guān)鍵):

    zap_pte_range->static unsigned long zap_pte_range(struct mmu_gather *tlb, struct vm_area_struct *vma, pmd_t *pmd, unsigned long addr, unsigned long end, struct zap_details *details){ …again: init_rss_vec(rss); start_pte = pte_offset_map_lock(mm, pmd, addr, &ptl); //根據(jù)addr從pmd指向的頁表中獲得頁表項指針,并申請頁表的自旋鎖 pte = start_pte; flush_tlb_batched_pending(mm); arch_enter_lazy_mmu_mode(); do { pte_t ptent = *pte; //獲得頁表項 if (pte_none(ptent)) //頁表項的內(nèi)容 為空表示沒有映射過,繼續(xù)下一個虛擬頁 continue; … if (pte_present(ptent)) { //虛擬頁相關(guān)的物理頁在內(nèi)存中(如沒有被換出到swap) struct page *page; page = vm_normal_page(vma, addr, ptent); //獲得虛擬頁相關(guān)的物理頁 … ptent = ptep_get_and_clear_full(mm, addr, pte, tlb->fullmm); //將頁表項清空(即是解除了映射關(guān)系),并返回原來的頁表項的內(nèi)容 tlb_remove_tlb_entry(tlb, pte, addr); if (unlikely(!page)) continue; if (!PageAnon(page)) { //如果是文件頁 if (pte_dirty(ptent)) { //是臟頁 force_flush = 1; //強制刷tlb set_page_dirty(page); //臟標(biāo)志傳遞到page結(jié)構(gòu) } if (pte_young(ptent) && likely(!(vma->vm_flags & VM_SEQ_READ))) //如果頁表項訪問標(biāo)志置位,且是隨機訪問的vma,則標(biāo)記頁面被訪問 mark_page_accessed(page); } rss[mm_counter(page)]–; //進程的相關(guān)rss 做減1記賬 page_remove_rmap(page, false); // page->_mapcount– if (unlikely(page_mapcount(page) check_mapping)) { /* * unmap_shared_mapping_pages() wants to * invalidate cache without truncating: * unmap shared but keep private pages. */ if (details->check_mapping != page_rmapping(page)) continue; } pte_clear_not_present_full(mm, addr, pte, tlb->fullmm); rss[mm_counter(page)]–; page_remove_rmap(page, false); put_page(page); continue; } …. if (!non_swap_entry(entry)) //非遷移類型的swap_entry rss[MM_SWAPENTS]–; //進程相關(guān)的交換條目的rss 減1 else if (is_migration_entry(entry)) { //遷移類型的表項 struct page *page; page = migration_entry_to_page(entry); //得到對應(yīng)的物理頁 rss[mm_counter(page)]–; //進程相關(guān)的物理頁類型的rss 減1 } if (unlikely(!free_swap_and_cache(entry))) //釋放swap條目 print_bad_pte(vma, addr, ptent, NULL); pte_clear_not_present_full(mm, addr, pte, tlb->fullmm); //清除虛擬頁相關(guān)的物理頁的頁表映射 } while (pte++, addr += PAGE_SIZE, addr != end); //遍歷pmd表項管轄范圍內(nèi)的每一個虛擬頁 add_mm_rss_vec(mm, rss); //記錄到進程的相關(guān)rss結(jié)構(gòu)中 arch_leave_lazy_mmu_mode(); /* Do the actual TLB flush before dropping ptl */ if (force_flush) tlb_flush_mmu_tlbonly(tlb); //如果是強制刷tlb,則刷tlb pte_unmap_unlock(start_pte, ptl); //釋放進程的頁表自旋鎖 /* * If we forced a TLB flush (either due to running out of * batch buffers or because we needed to flush dirty TLB * entries before releasing the ptl), free the batched * memory too. Restart if we didn’t do everything. */ if (force_flush) { //如果是強制刷tlb,則釋放掉本次聚集的物理頁 force_flush = 0; tlb_flush_mmu(tlb); //釋放本次聚集的物理頁 } … return addr; }

    以上函數(shù),遍歷進程相關(guān)頁表(一個pmd表項指向一個頁表)所描述的范圍的每一個虛擬頁,如果之前已經(jīng)建立過映射,就將相關(guān)的頁表項清除,對于在內(nèi)存中物理頁來說,需要調(diào)用__tlb_remove_page將其加入到mmu的積聚結(jié)構(gòu)中,下面重點看下這個函數(shù):

    __tlb_remove_page->__tlb_remove_page_size //mm/mmu_gather.c -> bool __tlb_remove_page_size(struct mmu_gather *tlb, struct page *page, int page_size) { struct mmu_gather_batch *batch; … batch = tlb->active; //獲得當(dāng)前批次的積聚結(jié)構(gòu) /* * Add the page and check if we are full. If so * force a flush. */ batch->pages[batch->nr++] = page; //將頁面加入到 批次的積聚結(jié)構(gòu)的pages數(shù)組中,并增加 batch->nr計數(shù) if (batch->nr == batch->max) { //如果當(dāng)前批次的 積聚結(jié)構(gòu)的pages數(shù)組中積聚的頁面?zhèn)€數(shù)到達最大個數(shù) if (!tlb_next_batch(tlb)) //獲得下一個批次積聚結(jié)構(gòu) return true; //獲得不成功返回true batch = tlb->active; } VM_BUG_ON_PAGE(batch->nr > batch->max, page); return false; //獲得 下一個批次 批次積聚結(jié)構(gòu)成功,返回 false; }

    我們再來看下tlb_next_batch的實現(xiàn):

    static bool tlb_next_batch(struct mmu_gather *tlb) { struct mmu_gather_batch *batch; batch = tlb->active; if (batch->next) { //下一個批次積聚結(jié)構(gòu)存在 tlb->active = batch->next; //當(dāng)前的批次積聚結(jié)構(gòu)指向這個批次結(jié)構(gòu) return true; } if (tlb->batch_count == MAX_GATHER_BATCH_COUNT) //如果批次數(shù)量達到最大值 則返回false return false; //批次還沒有到達最大值,則分配并初始化批次的積聚結(jié)構(gòu) batch = (void *)__get_free_pages(GFP_NOWAIT | __GFP_NOWARN, 0); //申請一個物理頁面由于存放mmu_gather_batch和page數(shù)組 if (!batch) return false; tlb->batch_count++; //批次計數(shù)加1 batch->next = NULL; batch->nr = 0; batch->max = MAX_GATHER_BATCH; //批次積聚結(jié)構(gòu)的 page數(shù)組最大個數(shù)賦值為MAX_GATHER_BATCH //插入到mmu 積聚結(jié)構(gòu)的批次鏈表中 tlb->active->next = batch; tlb->active = batch; return true; }

    這里有幾個地方需要注意:MAX_GATHER_BATCH_COUNT 表示的是mmu積聚操作最多可以有多少個批次積聚結(jié)構(gòu),他的值為10000UL/MAX_GATHER_BATCH (考慮到非搶占式內(nèi)核的soft lockups的影響)。MAX_GATHER_BATCH 表示一個批次的積聚結(jié)構(gòu)的 page數(shù)組的最多元素個數(shù),他的值為((PAGE_SIZE – sizeof(struct mmu_gather_batch)) / sizeof(void *)),也就是物理頁面大小去除掉struct mmu_gather_batch結(jié)構(gòu)大小。

    下面給出相關(guān)圖解:

    解除頁表過程:

    添加的到積聚結(jié)構(gòu)page數(shù)組頁面小于等于8個的情況:

    添加的到積聚結(jié)構(gòu)page數(shù)組頁面大于8個的情況:

    1個批次積聚結(jié)構(gòu)->

    2個批次積聚結(jié)構(gòu)->

    更多批次積聚結(jié)構(gòu)加入->

    2.5 free_pgtables

    unmap_vmas函數(shù)主要是積聚了一些相關(guān)的虛擬頁面對應(yīng)的物理頁面,但是我們還需要釋放各級頁表對應(yīng)的物理頁等。下面看下free_pgtables的實現(xiàn):

    首先看下它的主要脈絡(luò):

    // mm/memory.cvoid free_pgtables(struct mmu_gather *tlb, struct vm_area_struct *vma, unsigned long floor, unsigned long ceiling) { while (vma) { //從起始的vma開始遍歷每個vma struct vm_area_struct *next = vma->vm_next; //獲得下一個vma unsigned long addr = vma->vm_start; //獲得vma的起始地址 /* * Hide vma from rmap and truncate_pagecache before freeing * pgtables */ unlink_anon_vmas(vma); //解除匿名vma的反向映射關(guān)系 unlink_file_vma(vma); //解除文件vma反向映射關(guān)系 if (is_vm_hugetlb_page(vma)) { hugetlb_free_pgd_range(tlb, addr, vma->vm_end, floor, next ? next->vm_start : ceiling); } else { /* * Optimization: gather nearby vmas into one call down */ while (next && next->vm_start vm_end + PMD_SIZE && !is_vm_hugetlb_page(next)) { vma = next; next = vma->vm_next; unlink_anon_vmas(vma); unlink_file_vma(vma); } free_pgd_range(tlb, addr, vma->vm_end, floor, next ? next->vm_start : ceiling); //遍歷各級頁表 } vma = next; } }

    我們主要看free_pgd_range的實現(xiàn):

    free_pgd_range->free_p4d_range ->free_pud_range ->free_pmd_range ->free_pte_range -> …. -> pud_clear(pud); ////清除pud頁目錄中的對應(yīng)的pud表項 pmd_free_tlb(tlb, pmd, start); //pmd頁目錄的物理頁放入 頁表的積聚結(jié)構(gòu)中 mm_dec_nr_pmds(tlb->mm) //進程使用的頁表的物理頁統(tǒng)計減1 ->p4d_clear(p4d); //清除p4d頁目錄中的對應(yīng)的p4d表項 pud_free_tlb(tlb, pud, start) //pud頁目錄的物理頁放入 頁表的積聚結(jié)構(gòu)中 -> pgd_clear(pgd); //清除pgd頁目錄中的對應(yīng)的pgd表項 p4d_free_tlb(tlb, p4d, start); //p4d頁目錄的物理頁放入 頁表的積聚結(jié)構(gòu)中(存在p4d頁目錄的話)

    我們以最后一級頁表(pmd表項指向)為例說明:

    static void free_pte_range(struct mmu_gather *tlb, pmd_t *pmd, unsigned long addr){ pgtable_t token = pmd_pgtable(*pmd); //從相關(guān)的pmd表項指針中獲得頁表 pmd_clear(pmd); //清除pmd頁目錄中的對應(yīng)的pmd表項,即是頁表指針 pte_free_tlb(tlb, token, addr); //存放頁表的物理頁放入 頁表的積聚結(jié)構(gòu)中 mm_dec_nr_ptes(tlb->mm); //進程使用的頁表的物理頁統(tǒng)計減1}

    看下pte_free_tlb函數(shù):

    // include/asm-generic/tlb.h#ifndef pte_free_tlb#define pte_free_tlb(tlb, ptep, address) do { tlb_flush_pmd_range(tlb, address, PAGE_SIZE); //更新tlb->start和tlb->end, tlb->cleared_pmds = 1 tlb->freed_tables = 1; __pte_free_tlb(tlb, ptep, address); //存放頁表的物理頁放入頁表的積聚結(jié)構(gòu)中 } while (0)#endif

    再看看__pte_free_tlb:

    // arch/arm64/include/asm/tlb.h__pte_free_tlb->pgtable_pte_page_dtor(pte); //執(zhí)行釋放頁表的時候的構(gòu)造函數(shù),如釋放ptlock內(nèi)存,zone的頁表頁面統(tǒng)計減1等 tlb_remove_table(tlb, pte); //mm/mmu_gather.c -> struct mmu_table_batch **batch = &tlb->batch; //獲得 頁表的積聚結(jié)構(gòu) if (*batch == NULL) { //如何為空,則分配一個物理頁,存放積聚結(jié)構(gòu)和積聚數(shù)組 *batch = (struct mmu_table_batch *)__get_free_page(GFP_NOWAIT | __GFP_NOWARN); if (*batch == NULL) { tlb_table_invalidate(tlb); tlb_remove_table_one(table); return; } (*batch)->nr = 0; } (*batch)->tables[(*batch)->nr++] = table; //相關(guān)的頁目錄對應(yīng)的物理頁放入 積聚數(shù)組中 if ((*batch)->nr == MAX_TABLE_BATCH) //加入的物理頁達到最大值 tlb_table_flush(tlb); //做一次刷tlb和釋放當(dāng)前已經(jīng)積聚的頁目錄的物理頁

    需要說明的是:對于存放各級頁目錄的物理頁的釋放,每當(dāng)一個頁表積聚結(jié)構(gòu)填滿了就會釋放,不會構(gòu)建批次鏈表。

    2.6 tlb_finish_mmu

    通過上面的unmap_vmas和free_pgtables之后,我們積聚了大量的物理頁以及存放各級頁目錄的物理頁,現(xiàn)在需要將這些頁面進行釋放。

    下面我們來看下tlb_finish_mmu做的mmu-gather的收尾動作:

    void tlb_finish_mmu(struct mmu_gather *tlb, unsigned long start, unsigned long end){ … tlb_flush_mmu(tlb); //刷tlb和釋放所有積聚的物理頁#ifndef CONFIG_MMU_GATHER_NO_GATHER tlb_batch_list_free(tlb); //釋放各批次結(jié)構(gòu)對應(yīng)的物理頁#endif …}

    首先看下tlb_flush_mmu:

    mm/mmu_gather.c void tlb_flush_mmu(struct mmu_gather *tlb) { tlb_flush_mmu_tlbonly(tlb); //刷tlb tlb_flush_mmu_free(tlb); //釋放各個批次積聚結(jié)構(gòu)的物理頁 }

    tlb_flush_mmu_tlbonly的實現(xiàn):

    static inline void tlb_flush_mmu_tlbonly(struct mmu_gather *tlb) { /* * Anything calling __tlb_adjust_range() also sets at least one of * these bits. */ if (!(tlb->freed_tables || tlb->cleared_ptes || tlb->cleared_pmds || tlb->cleared_puds || tlb->cleared_p4ds)) //有一個為0 即返回 return; tlb_flush(tlb); //刷tlb,和處理器架構(gòu)相關(guān) … __tlb_reset_range(tlb); //將tlb->start 和tlb->end以及tlb->freed_tables,tlb->cleared_xxx復(fù)位 }

    我們來看下tlb_flush:

    arch/arm64/include/asm/tlb.hstatic inline void tlb_flush(struct mmu_gather *tlb){ struct vm_area_struct vma = TLB_FLUSH_VMA(tlb->mm, 0); bool last_level = !tlb->freed_tables; unsigned long stride = tlb_get_unmap_size(tlb); int tlb_level = tlb_get_level(tlb); //得到刷tlb的級別,如只刷pte級別 /* * If we’re tearing down the address space then we only care about * invalidating the walk-cache, since the ASID allocator won’t * reallocate our ASID without invalidating the entire TLB. */ if (tlb->fullmm) { //刷整個mm的tlb if (!last_level) flush_tlb_mm(tlb->mm); return; } //刷一個虛擬內(nèi)存范圍的tlb __flush_tlb_range(&vma, tlb->start, tlb->end, stride, last_level, tlb_level);}

    最后我們看tlb_flush_mmu_free:

    static void tlb_flush_mmu_free(struct mmu_gather *tlb){ tlb_table_flush(tlb); //釋放之前積聚的存放各級頁目錄的物理頁#ifndef CONFIG_MMU_GATHER_NO_GATHER tlb_batch_pages_flush(tlb); //釋放各個批次積聚結(jié)構(gòu)積聚的物理頁#endif}

    tlb_table_flush的實現(xiàn):

    static void tlb_table_flush(struct mmu_gather *tlb) { struct mmu_table_batch **batch = &tlb->batch; //獲得當(dāng)前的 頁表批次 積聚結(jié)構(gòu) if (*batch) { tlb_table_invalidate(tlb); //刷tlb tlb_remove_table_free(*batch); //釋放頁目錄物理頁 *batch = NULL; } }static void tlb_remove_table_free(struct mmu_table_batch *batch){ call_rcu(&batch->rcu, tlb_remove_table_rcu); //rsu延遲調(diào)用 -> __tlb_remove_table_free(container_of(head, struct mmu_table_batch, rcu)); -> static void __tlb_remove_table_free(struct mmu_table_batch *batch) { int i; for (i = 0; i nr; i++) //釋放頁表批次 積聚結(jié)構(gòu)中的page數(shù)組中每一個物理頁 __tlb_remove_table(batch->tables[i]); free_page((unsigned long)batch); //釋放這個 表批次 積聚結(jié)構(gòu)對應(yīng)的物理頁 } }

    tlb_batch_pages_flush的實現(xiàn):

    static void tlb_batch_pages_flush(struct mmu_gather *tlb){ struct mmu_gather_batch *batch; for (batch = &tlb->local; batch && batch->nr; batch = batch->next) { //遍歷積聚批次鏈表的每一個 批次積聚結(jié)構(gòu) free_pages_and_swap_cache(batch->pages, batch->nr); //釋放積聚結(jié)構(gòu)的page數(shù)組的每一個物理頁 batch->nr = 0; } tlb->active = &tlb->local;}

    最終是:調(diào)用free_pages_and_swap_cache將物理頁的引用計數(shù)減1 ,引用計數(shù)為0時就將這個物理頁釋放,還給伙伴系統(tǒng)。

    雖然上面已經(jīng)釋放了相關(guān)的各級頁表的物理頁和映射到進程地址空間的物理頁,但是存放積聚結(jié)構(gòu)和page數(shù)組的物理頁還沒有釋放,所以調(diào)用tlb_batch_list_free來做這個事情:

    tlb_batch_list_free-> static void tlb_batch_list_free(struct mmu_gather *tlb) { struct mmu_gather_batch *batch, *next; for (batch = tlb->local.next; batch; batch = next) { //釋放積聚結(jié)構(gòu)的物理頁從tlb->local.next開始的,遍歷所有批次的積聚結(jié)構(gòu) next = batch->next; free_pages((unsigned long)batch, 0); //釋放這個批次積聚結(jié)構(gòu)的物理頁 } tlb->local.next = NULL; }

    于是相關(guān)的所有物理頁面都被釋放了(包括相關(guān)地址范圍內(nèi)進程各級頁目錄對應(yīng)的物理頁,映射到進程地址空間的物理頁,和各個積聚結(jié)構(gòu)所在的物理頁)。

    最后給出整體的圖解:

    tlb_flush_mmu函數(shù)的tlb_table_flush會將B鏈表中的相關(guān)物理頁面釋放(包括之前保存的各級頁表的頁面和mmu_table_batch結(jié)構(gòu)所在頁面),tlb_batch_pages_flush會將A鏈表的所有除了積聚結(jié)構(gòu)以外的所有物理頁面釋放,而tlb_batch_list_free會將A鏈表的所有批次積聚結(jié)構(gòu)(mmu_gather_batch)的物理頁面釋放。

    3.應(yīng)用場景

    使用mmu-gather的應(yīng)用場景主要是進程退出,執(zhí)行execv和調(diào)用munmap等。

    下面我們主要來看下他們的調(diào)用鏈:

    3.1 進程退出時

    進程退出時會釋放它的所有的相關(guān)聯(lián)的系統(tǒng)資源,其中就包括內(nèi)存資源:

    kernel/exit.cdo_exit->exit_mm ->mmput //kernel/fork.c ->if (atomic_dec_and_test(&mm->mm_users)) //如果mm->mm_users減1為0時,也就是當(dāng)前進程是最后一個mm的使用者 __mmput(mm); //釋放mm ->exit_mmap //mm/mmap.c ->tlb_gather_mmu(&tlb, mm, 0, -1); //初始化mmu_gather結(jié)構(gòu),start=0,end=-1 表示釋放整個mm ->unmap_vmas(&tlb, vma, 0, -1); //解除頁表映射,相關(guān)的物理頁放入積聚結(jié)構(gòu)中 >free_pgtables(&tlb, vma, FIRST_USER_ADDRESS, USER_PGTABLES_CEILING); //釋放各級頁表,頁表相關(guān)物理頁放入頁表積聚結(jié)構(gòu),滿則釋放 >tlb_finish_mmu(&tlb, 0, -1); //刷mm的tlb,釋放所有積聚物理頁,釋放所有積聚結(jié)構(gòu)相關(guān)物理頁

    3.2 執(zhí)行execv時

    執(zhí)行execv時進程會將所有的mm釋放掉:

    fs/exec.c…do_execveat_common->bprm_execve ->exec_binprm ->search_binary_handler … ->load_elf_binary // fs/binfmt_elf.c ->begin_new_exec ->exec_mmap ->mmput(old_mm) ->if (atomic_dec_and_test(&mm->mm_users)) //如果mm->mm_users減1為0時,也就是當(dāng)前進程是最后一個mm的使用者 __mmput(mm); //釋放mm ->exit_mmap //mm/mmap.c ->tlb_gather_mmu(&tlb, mm, 0, -1); //初始化mmu_gather結(jié)構(gòu),start=0,end=-1標(biāo)識釋放整個mm ->unmap_vmas(&tlb, vma, 0, -1); //解除頁表映射,相關(guān)的物理頁放入積聚結(jié)構(gòu)中 ->free_pgtables(&tlb, vma, FIRST_USER_ADDRESS, USER_PGTABLES_CEILING); //釋放各級頁表,頁表相關(guān)物理頁放入頁表積聚結(jié)構(gòu),滿則釋放 ->tlb_finish_mmu(&tlb, 0, -1); //刷mm的tlb,釋放所有積聚物理頁,釋放所有積聚結(jié)構(gòu)相關(guān)物理頁

    3.3 調(diào)用munmap時

    執(zhí)行munmap時,會將一個地址范圍的頁表解除并釋放相關(guān)的物理頁面:

    mm/mmap.c…__do_munmap->unmap_region(mm, vma, prev, start, end); -> tlb_gather_mmu(&tlb, mm, start, end); //初始化mmu_gather結(jié)構(gòu) unmap_vmas(&tlb, vma, start, end); //解除頁表映射,相關(guān)的物理頁放入積聚結(jié)構(gòu)中 free_pgtables(&tlb, vma, prev ? prev->vm_end : FIRST_USER_ADDRESS, next ? next->vm_start : USER_PGTABLES_CEILING); //釋放各級頁表,頁表相關(guān)物理頁放入頁表積聚結(jié)構(gòu),滿則釋放 tlb_finish_mmu(&tlb, start, end); //刷mm的tlb,釋放所有積聚物理頁,釋放所有積聚結(jié)構(gòu)相關(guān)物理頁

    4.總結(jié)

    Linux內(nèi)核mmu-gather用于積聚解除映射的相關(guān)物理頁面,并保證了刷tlb和釋放物理頁面的順序。首先解除掉相關(guān)虛擬頁面對應(yīng)物理頁面(如果有的話)的頁表映射關(guān)系,然后將相關(guān)的物理頁面保存在積聚結(jié)構(gòu)的數(shù)組中,接著將相關(guān)的各級頁目錄表項清除,并放入頁表相關(guān)的積聚結(jié)構(gòu)的數(shù)組中,最后刷對應(yīng)內(nèi)存范圍的tlb,釋放掉所有放在積聚結(jié)構(gòu)數(shù)組中的物理頁面。

    原文地址:https://mp.weixin.qq.com/s/VQo8u04snaDNzEWz-txM0w 侵權(quán)請私信刪除

    鄭重聲明:本文內(nèi)容及圖片均整理自互聯(lián)網(wǎng),不代表本站立場,版權(quán)歸原作者所有,如有侵權(quán)請聯(lián)系管理員(admin#wlmqw.com)刪除。
    用戶投稿
    上一篇 2022年6月13日 21:06
    下一篇 2022年6月13日 21:06

    相關(guān)推薦

    • 我國首臺130噸級重復(fù)使用液氧煤油補燃循環(huán)發(fā)動機試車成功

      新華社西安11月26日電記者26日從中國航天科技集團六院獲悉,由該院自主研制的首臺130噸級重復(fù)使用液氧煤油補燃循環(huán)發(fā)動機兩次起動試車取得圓滿成功。 該型發(fā)動機是瞄準(zhǔn)我國新一代運載…

      2022年11月27日
    • 世界領(lǐng)先!我國已應(yīng)用于新一代戰(zhàn)機→

      本文轉(zhuǎn)自【央視軍事】; “3D打印技術(shù)在飛機上的應(yīng)用 我們已達到規(guī)?;⒐こ袒?處于世界領(lǐng)先位置” 如何運用3D打印設(shè)備 生產(chǎn)新一代戰(zhàn)機的零部件? 規(guī)?;?工程化 3D打印件批量裝…

      2022年11月27日
    • 存儲過程語法(sql server存儲過程語法)

      今天小編給各位分享存儲過程語法的知識,其中也會對sql server存儲過程語法進行解釋,如果能碰巧解決你現(xiàn)在面臨的問題,別忘了關(guān)注本站,現(xiàn)在開始吧! oracle存儲過程基本語法…

      2022年11月26日
    • 西芹腰果的做法(西芹炒腰果)

      01 去除西芹的葉子以及表皮,切成菱形塊,將大蒜和生姜都準(zhǔn)備好,蝦米放入到清水中清洗,去除表面的雜質(zhì)。 02 在鍋里面加入適量的水,然后加入食用油以及鹽,將切好的西芹直接放入到其中…

      2022年11月25日
    • 白蘿卜的做法大全(白蘿卜的做法大全腌制糖醋)

      簡要回答 白蘿卜營養(yǎng)價值高,就算是生吃也很入口清脆,而且還有豐富的營養(yǎng)元素,不管是燉蒸炒還是做湯都很鮮香。 詳細內(nèi)容 01 腌制爽脆的白蘿卜,可以買一小袋泡腳,放入切好的白蘿卜條,…

      2022年11月25日
    • 拼多多百億補貼預(yù)售一般多久發(fā)貨(拼多多百億補貼預(yù)售)

      拼多多里面有很多優(yōu)惠活動,其中百億補貼活動非?;鸨?,一些里面的東西價格比別的平臺便宜,質(zhì)量也有保障,還有預(yù)售的活動,那么拼多多百億補貼預(yù)售一般多久發(fā)貨?下面小編為大家?guī)砥炊喽喟賰|…

      2022年11月25日
    • 百度關(guān)鍵詞快速排名的4大原理解析(百度怎么刷關(guān)鍵詞)

      近期百度公告驚雷算法2.0,升級之快還是第一次吧,看來百度對于刷點擊行為是零容忍了。之前尹華峰SEO技術(shù)博客介紹過一篇如何使用刷點擊工具,其實市面上有很多這類SEO快速排名的軟件,…

      2022年11月25日
    • 淘寶直播開通后帶貨鏈接怎么做(淘寶直播需要開通淘寶店鋪嗎)

      直播帶貨無論是對于商家來說還是主播收益都是非常可觀的,所以不少平臺都有直播帶貨功能,一些小伙伴也想加入淘寶直播,那么淘寶直播開通后帶貨鏈接怎么做?下面小編為大家?guī)硖詫氈辈ラ_通后帶…

      2022年11月24日
    • Steam秋季特賣開啟 為Steam大獎提名游戲

      Steam秋季特賣開啟 為Steam大獎提名游戲 Steam秋季特賣活動現(xiàn)已正式開啟,時間從11月23日持續(xù)到11月30日(北京時間),新老游戲均有不錯的折扣,感興趣的玩家可以前往…

      2022年11月24日
    • 園屬于什么結(jié)構(gòu)(園的結(jié)構(gòu)和部首)

      園 yuán:全包圍結(jié)構(gòu),平穩(wěn)端正中稍帶左收右展。 外部“口” 體態(tài)端莊,稍抗肩,稍帶左輕右重。左豎起筆稍抖,豎身勿重,稍左斜,垂露收筆;第二筆橫折壓著左豎起筆,橫畫稍抗肩,不要重…

      2022年11月24日

    聯(lián)系我們

    聯(lián)系郵箱:admin#wlmqw.com
    工作時間:周一至周五,10:30-18:30,節(jié)假日休息