真实的国产乱ⅩXXX66竹夫人,五月香六月婷婷激情综合,亚洲日本VA一区二区三区,亚洲精品一区二区三区麻豆

成都創(chuàng)新互聯(lián)網(wǎng)站制作重慶分公司

Linux內(nèi)存分配的詳細(xì)過(guò)程

這篇文章主要講解了“Linux內(nèi)存分配的詳細(xì)過(guò)程”,文中的講解內(nèi)容簡(jiǎn)單清晰,易于學(xué)習(xí)與理解,下面請(qǐng)大家跟著小編的思路慢慢深入,一起來(lái)研究和學(xué)習(xí)“Linux內(nèi)存分配的詳細(xì)過(guò)程”吧!

專注于為中小企業(yè)提供成都做網(wǎng)站、成都網(wǎng)站制作服務(wù),電腦端+手機(jī)端+微信端的三站合一,更高效的管理,為中小企業(yè)曲周免費(fèi)做網(wǎng)站提供優(yōu)質(zhì)的服務(wù)。我們立足成都,凝聚了一批互聯(lián)網(wǎng)行業(yè)人才,有力地推動(dòng)了上1000+企業(yè)的穩(wěn)健成長(zhǎng),幫助中小企業(yè)通過(guò)網(wǎng)站建設(shè)實(shí)現(xiàn)規(guī)模擴(kuò)充和轉(zhuǎn)變。

本文使用 Linux 2.6.32 版本代碼

內(nèi)存分區(qū)對(duì)象

在《你真的理解內(nèi)存分配》一文中介紹過(guò),Linux 會(huì)把進(jìn)程虛擬內(nèi)存空間劃分為多個(gè)分區(qū),在 Linux 內(nèi)核中使用 vm_area_struct  對(duì)象來(lái)表示,其定義如下:

 struct vm_area_struct {     struct mm_struct *vm_mm;        // 分區(qū)所屬的內(nèi)存管理對(duì)象       unsigned long vm_start;         // 分區(qū)的開(kāi)始地址     unsigned long vm_end;           // 分區(qū)的結(jié)束地址       struct vm_area_struct *vm_next; // 通過(guò)這個(gè)指針把進(jìn)程所有的內(nèi)存分區(qū)連接成一個(gè)鏈表    ...     struct rb_node vm_rb;           // 紅黑樹(shù)的節(jié)點(diǎn), 用于保存到內(nèi)存分區(qū)紅黑樹(shù)中   ... };

我們對(duì) vm_area_struct 對(duì)象進(jìn)行了簡(jiǎn)化,只保留了本文需要的字段。

內(nèi)核就是使用 vm_area_struct 對(duì)象來(lái)記錄一個(gè)內(nèi)存分區(qū)(如 代碼段、數(shù)據(jù)段 和 堆空間 等),下面介紹一下 vm_area_struct  對(duì)象各個(gè)字段的作用:

  • vm_mm:指定了當(dāng)前內(nèi)存分區(qū)所屬的內(nèi)存管理對(duì)象。

  • vm_start:內(nèi)存分區(qū)的開(kāi)始地址。

  • vm_end:內(nèi)存分區(qū)的結(jié)束地址。

  • vm_next:通過(guò)這個(gè)指針把進(jìn)程中所有的內(nèi)存分區(qū)連接成一個(gè)鏈表。

  • vm_rb:另外,為了快速查找內(nèi)存分區(qū),內(nèi)核還把進(jìn)程的所有內(nèi)存分區(qū)保存到一棵紅黑樹(shù)中。vm_rb 就是紅黑樹(shù)的節(jié)點(diǎn),用于把內(nèi)存分區(qū)保存到紅黑樹(shù)中。

假如進(jìn)程 A 現(xiàn)在有 4 個(gè)內(nèi)存分區(qū),它們的范圍如下:

  • 代碼段:00400000 ~ 00401000

  • 數(shù)據(jù)段:00600000 ~ 00601000

  • 堆空間:00983000 ~ 009a4000

  • ??臻g:7f37ce866000 ~ 7f3fce867000

那么這 4 個(gè)內(nèi)存分區(qū)在內(nèi)核中的結(jié)構(gòu)如 圖1 所示:

Linux內(nèi)存分配的詳細(xì)過(guò)程

在 圖1 中,我們可以看到有個(gè) mm_struct  的對(duì)象,此對(duì)象每個(gè)進(jìn)程都持有一個(gè),是進(jìn)程虛擬內(nèi)存空間和物理內(nèi)存空間的管理對(duì)象。我們簡(jiǎn)單介紹一下這個(gè)對(duì)象,其定義如下:

struct mm_struct {    struct vm_area_struct *mmap;  // 指向由進(jìn)程內(nèi)存分區(qū)連接成的鏈表    struct rb_root mm_rb;         // 內(nèi)核使用紅黑樹(shù)保存進(jìn)程的所有內(nèi)存分區(qū), 這個(gè)是紅黑樹(shù)的根節(jié)點(diǎn)    unsigned long start_brk, brk; // 堆空間的開(kāi)始地址和結(jié)束地址   ... };

我們來(lái)介紹下 mm_struct 對(duì)象各個(gè)字段的作用:

  • mmap:指向由進(jìn)程所有內(nèi)存分區(qū)連接成的鏈表。

  • mm_rb:內(nèi)核為了加快查找內(nèi)存分區(qū)的速度,使用了紅黑樹(shù)保存所有內(nèi)存分區(qū),這個(gè)就是紅黑樹(shù)的根節(jié)點(diǎn)。

  • start_brk:堆空間的開(kāi)始內(nèi)存地址。

  • brk:堆空間的頂部?jī)?nèi)存地址。

我們來(lái)回顧一下進(jìn)程虛擬內(nèi)存空間的布局圖,如 圖2 所示:

Linux內(nèi)存分配的詳細(xì)過(guò)程

start_brk 和 brk 字段用來(lái)記錄堆空間的范圍, 如 圖2 所示。一般來(lái)說(shuō),start_brk 是不會(huì)變的,而 brk  會(huì)隨著分配內(nèi)存和釋放內(nèi)存而變化。

虛擬內(nèi)存分配

在《你真的理解內(nèi)存分配》一文中說(shuō)過(guò),調(diào)用 malloc 申請(qǐng)內(nèi)存時(shí),最終會(huì)調(diào)用 brk 系統(tǒng)調(diào)用來(lái)從堆空間中分配內(nèi)存。我們來(lái)分析一下 brk  系統(tǒng)調(diào)用的實(shí)現(xiàn):

unsigned long sys_brk(unsigned long brk) {    unsigned long rlim, retval;    unsigned long newbrk, oldbrk;    struct mm_struct *mm = current->mm;   ...    down_write(&mm->mmap_sem);  // 對(duì)內(nèi)存管理對(duì)象進(jìn)行上鎖   ...    // 判斷堆空間的大小是否超出限制, 如果超出限制, 就不進(jìn)行處理    rlim = current->signal->rlim[RLIMIT_DATA].rlim_cur;    if (rlim < RLIM_INFINITY        && (brk - mm->start_brk) + (mm->end_data - mm->start_data) > rlim)        goto out;     newbrk = PAGE_ALIGN(brk);      // 新的brk值    oldbrk = PAGE_ALIGN(mm->brk);  // 舊的brk值    if (oldbrk == newbrk)          // 如果新舊的位置都一樣, 就不需要進(jìn)行處理        goto set_brk;   ...    // 調(diào)用 do_brk 函數(shù)進(jìn)行下一步處理    if (do_brk(oldbrk, newbrk-oldbrk) != oldbrk)        goto out;  set_brk:    mm->brk = brk; // 設(shè)置堆空間的頂部位置(brk指針) out:    retval = mm->brk;    up_write(&mm->mmap_sem);    return retval; }

總結(jié)上面的代碼,主要有以下幾個(gè)步驟:

1、判斷堆空間的大小是否超出限制,如果超出限制,就不作任何處理,直接返回舊的 brk 值。

2、如果新的 brk 值跟舊的 brk 值一致,那么也不用作任何處理。

3、如果新的 brk 值發(fā)生變化,那么就調(diào)用 do_brk 函數(shù)進(jìn)行下一步處理。

4、設(shè)置進(jìn)程的 brk 指針(堆空間頂部)為新的 brk 的值。

我們看到第 3 步調(diào)用了 do_brk 函數(shù)來(lái)處理,do_brk 函數(shù)的實(shí)現(xiàn)有點(diǎn)小復(fù)雜,所以這里介紹一下大概處理流程:

  1. 鴻蒙官方戰(zhàn)略合作共建——HarmonyOS技術(shù)社區(qū)

  2. 通過(guò)堆空間的起始地址 start_brk 從進(jìn)程內(nèi)存分區(qū)紅黑樹(shù)中找到其對(duì)應(yīng)的內(nèi)存分區(qū)對(duì)象(也就是 vm_area_struct)。

  3. 把堆空間的內(nèi)存分區(qū)對(duì)象的 vm_end 字段設(shè)置為新的 brk 值。

至此,brk 系統(tǒng)調(diào)用的工作就完成了(上面沒(méi)有分析釋放內(nèi)存的情況),總結(jié)來(lái)說(shuō),brk 系統(tǒng)調(diào)用的工作主要有兩部分:

把進(jìn)程的 brk 指針設(shè)置為新的 brk 值。

把堆空間的內(nèi)存分區(qū)對(duì)象的 vm_end 字段設(shè)置為新的 brk 值。

物理內(nèi)存分配

從上面的分析知道,brk 系統(tǒng)調(diào)用申請(qǐng)的是 虛擬內(nèi)存,但存儲(chǔ)數(shù)據(jù)只能使用 物理內(nèi)存。所以,虛擬內(nèi)存必須映射到物理內(nèi)存才能被使用。

那么什么時(shí)候才進(jìn)行內(nèi)存映射呢?

在《你真的理解內(nèi)存分配》一文中介紹過(guò),當(dāng)對(duì)沒(méi)有映射的虛擬內(nèi)存地址進(jìn)行讀寫(xiě)操作時(shí),CPU 將會(huì)觸發(fā) 缺頁(yè)異常。內(nèi)核接收到 缺頁(yè)異常 后, 會(huì)調(diào)用  do_page_fault 函數(shù)進(jìn)行修復(fù)。

我們來(lái)分析一下 do_page_fault 函數(shù)的實(shí)現(xiàn)(精簡(jiǎn)后):

void do_page_fault(struct pt_regs *regs, unsigned long error_code) {    struct vm_area_struct *vma;    struct task_struct *tsk;    unsigned long address;    struct mm_struct *mm;    int write;    int fault;     tsk = current;    mm = tsk->mm;     address = read_cr2(); // 獲取導(dǎo)致頁(yè)缺失異常的虛擬內(nèi)存地址   ...    vma = find_vma(mm, address); // 通過(guò)虛擬內(nèi)存地址從進(jìn)程內(nèi)存分區(qū)中查找對(duì)應(yīng)的內(nèi)存分區(qū)對(duì)象   ...    if (likely(vma->vm_start <= address)) // 如果找到內(nèi)存分區(qū)對(duì)象        goto good_area;   ...  good_area:    write = error_code & PF_WRITE;   ...    // 調(diào)用 handle_mm_fault 函數(shù)對(duì)虛擬內(nèi)存地址進(jìn)行映射操作    fault = handle_mm_fault(mm, vma, address, write ? FAULT_FLAG_WRITE : 0);   ... }

do_page_fault 函數(shù)主要完成以下操作:

獲取導(dǎo)致頁(yè)缺失異常的虛擬內(nèi)存地址,保存到 address 變量中。

調(diào)用 find_vma 函數(shù)從進(jìn)程內(nèi)存分區(qū)中查找異常的虛擬內(nèi)存地址對(duì)應(yīng)的內(nèi)存分區(qū)對(duì)象。

如果找到內(nèi)存分區(qū)對(duì)象,那么調(diào)用 handle_mm_fault 函數(shù)對(duì)虛擬內(nèi)存地址進(jìn)行映射操作。

從上面的分析可知,對(duì)虛擬內(nèi)存進(jìn)行映射操作是通過(guò) handle_mm_fault 函數(shù)完成的,而 handle_mm_fault  函數(shù)的主要工作就是完成對(duì)進(jìn)程 頁(yè)表 的填充。

我們通過(guò) 圖3 來(lái)理解內(nèi)存映射的原理,可以參考文章《一文讀懂 HugePages的原理》:

Linux內(nèi)存分配的詳細(xì)過(guò)程

下面我們來(lái)分析一下 handle_mm_fault 的實(shí)現(xiàn),代碼如下:

int handle_mm_fault(struct mm_struct *mm, struct vm_area_struct *vma,                    unsigned long address, unsigned int flags) {    pgd_t *pgd;  // 頁(yè)全局目錄項(xiàng)    pud_t *pud;  // 頁(yè)上級(jí)目錄項(xiàng)    pmd_t *pmd;  // 頁(yè)中間目錄項(xiàng)    pte_t *pte;  // 頁(yè)表項(xiàng)   ...    pgd = pgd_offset(mm, address);         // 獲取虛擬內(nèi)存地址對(duì)應(yīng)的頁(yè)全局目錄項(xiàng)    pud = pud_alloc(mm, pgd, address);     // 獲取虛擬內(nèi)存地址對(duì)應(yīng)的頁(yè)上級(jí)目錄項(xiàng)   ...    pmd = pmd_alloc(mm, pud, address);     // 獲取虛擬內(nèi)存地址對(duì)應(yīng)的頁(yè)中間目錄項(xiàng)   ...    pte = pte_alloc_map(mm, pmd, address); // 獲取虛擬內(nèi)存地址對(duì)應(yīng)的頁(yè)表項(xiàng)   ...    // 對(duì)頁(yè)表項(xiàng)進(jìn)行映射    return handle_pte_fault(mm, vma, address, pte, pmd, flags); 18}

handle_mm_fault 函數(shù)主要對(duì)每一級(jí)的頁(yè)表進(jìn)行映射(對(duì)照 圖3 就容易理解),最終調(diào)用 handle_pte_fault 函數(shù)對(duì) 頁(yè)表項(xiàng)  進(jìn)行映射。

我們繼續(xù)來(lái)分析 handle_pte_fault 函數(shù)的實(shí)現(xiàn),代碼如下:

static inline int handle_pte_fault(struct mm_struct *mm, struct vm_area_struct *vma,                 unsigned long address, pte_t *pte, pmd_t *pmd,                 unsigned int flags) {    pte_t entry;     entry = *pte;     if (!pte_present(entry)) { // 還沒(méi)有映射到物理內(nèi)存        if (pte_none(entry)) {           ...            // 調(diào)用 do_anonymous_page 函數(shù)進(jìn)行匿名頁(yè)映射(堆空間就是使用匿名頁(yè))            return do_anonymous_page(mm, vma, address, pte, pmd, flags);       }       ...   }   ... }

上面代碼簡(jiǎn)化了很多與本文無(wú)關(guān)的邏輯。從上面代碼可以看出,handle_pte_fault 函數(shù)最終會(huì)調(diào)用 do_anonymous_page  來(lái)完成內(nèi)存映射操作,我們接著來(lái)分析下 do_anonymous_page 函數(shù)的實(shí)現(xiàn):

static int do_anonymous_page(struct mm_struct *mm, struct vm_area_struct *vma,                  unsigned long address, pte_t *page_table, pmd_t *pmd,                  unsigned int flags) {    struct page *page;    spinlock_t *ptl;    pte_t entry;     if (!(flags & FAULT_FLAG_WRITE)) { // 如果是讀操作導(dǎo)致的異常        // 使用 `零頁(yè)` 進(jìn)行映射        entry = pte_mkspecial(pfn_pte(my_zero_pfn(address), vma->vm_page_prot));       ...        goto setpte;   }   ...    // 如果是寫(xiě)操作導(dǎo)致的異常    // 申請(qǐng)一塊新的物理內(nèi)存頁(yè)    page = alloc_zeroed_user_highpage_movable(vma, address);   ...    // 根據(jù)物理內(nèi)存頁(yè)的地址生成映射關(guān)系    entry = mk_pte(page, vma->vm_page_prot);    if (vma->vm_flags & VM_WRITE)        entry = pte_mkwrite(pte_mkdirty(entry));   ... setpte:    set_pte_at(mm, address, page_table, entry); // 設(shè)置頁(yè)表項(xiàng)為新的映射關(guān)系   ...    return 0; }

do_anonymous_page 函數(shù)的實(shí)現(xiàn)比較有趣,它會(huì)根據(jù) 缺頁(yè)異常 是由讀操作還是寫(xiě)操作導(dǎo)致的,分為兩個(gè)不同的處理邏輯,如下:

如果是讀操作導(dǎo)致的,那么將會(huì)使用 零頁(yè) 進(jìn)行映射(零頁(yè) 是 Linux 內(nèi)核中一個(gè)比較特殊的內(nèi)存頁(yè),所有讀操作引起的 缺頁(yè)異常  都會(huì)指向此頁(yè),從而可以減少物理內(nèi)存的消耗),并且設(shè)置其為只讀(因?yàn)?零頁(yè) 是不能進(jìn)行寫(xiě)操作)。如果下次對(duì)此頁(yè)進(jìn)行寫(xiě)操作,將會(huì)觸發(fā)寫(xiě)操作的  缺頁(yè)異常,從而進(jìn)入下面步驟。

如果是寫(xiě)操作導(dǎo)致的,就申請(qǐng)一塊新的物理內(nèi)存頁(yè),然后根據(jù)物理內(nèi)存頁(yè)的地址生成映射關(guān)系,再對(duì)頁(yè)表項(xiàng)進(jìn)行填充(映射)。

感謝各位的閱讀,以上就是“Linux內(nèi)存分配的詳細(xì)過(guò)程”的內(nèi)容了,經(jīng)過(guò)本文的學(xué)習(xí)后,相信大家對(duì)Linux內(nèi)存分配的詳細(xì)過(guò)程這一問(wèn)題有了更深刻的體會(huì),具體使用情況還需要大家實(shí)踐驗(yàn)證。這里是創(chuàng)新互聯(lián),小編將為大家推送更多相關(guān)知識(shí)點(diǎn)的文章,歡迎關(guān)注!


本文題目:Linux內(nèi)存分配的詳細(xì)過(guò)程
分享網(wǎng)址:http://weahome.cn/article/pdssei.html

其他資訊

在線咨詢

微信咨詢

電話咨詢

028-86922220(工作日)

18980820575(7×24)

提交需求

返回頂部