1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

        vmalloc原理與實(shí)現(xiàn)

        共 4805字,需瀏覽 10分鐘

         ·

        2020-10-23 14:25

        在 Linux 系統(tǒng)中的每個(gè)進(jìn)程都有獨(dú)立 4GB 內(nèi)存空間,而 Linux 把這 4GB 內(nèi)存空間劃分為用戶內(nèi)存空間(0 ~ 3GB)和內(nèi)核內(nèi)存空間(3GB ~ 4GB),而內(nèi)核內(nèi)存空間由劃分為直接內(nèi)存映射區(qū)和動(dòng)態(tài)內(nèi)存映射區(qū)(vmalloc區(qū))。

        直接內(nèi)存映射區(qū)從?3GB?開始到?3GB+896MB?處結(jié)束,直接內(nèi)存映射區(qū)的特點(diǎn)就是物理地址與虛擬地址的關(guān)系為:虛擬地址 = 物理地址 + 3GB。而動(dòng)態(tài)內(nèi)存映射區(qū)不能通過這種簡(jiǎn)單的關(guān)系關(guān)聯(lián),而是需要訪問動(dòng)態(tài)內(nèi)存映射區(qū)時(shí),由內(nèi)核動(dòng)態(tài)申請(qǐng)物理內(nèi)存并且映射到動(dòng)態(tài)內(nèi)存映射區(qū)中。下圖是動(dòng)態(tài)內(nèi)存映射區(qū)在內(nèi)存空間的位置:

        為什么需要vmalloc區(qū)

        由于直接內(nèi)存映射區(qū)(3GB ~ 3GB+896MB)是直接映射到物理地址(0 ~ 896MB)的,所以內(nèi)核不能通過直接內(nèi)存映射區(qū)使用到超過 896MB 之外的物理內(nèi)存。這時(shí)候就需要提供一個(gè)機(jī)制能夠讓內(nèi)核使用 896MB 之外的物理內(nèi)存,所以 Linux 就實(shí)現(xiàn)了一個(gè) vmalloc 機(jī)制。vmalloc 機(jī)制的目的是在內(nèi)核內(nèi)存空間提供一個(gè)內(nèi)存區(qū),能夠讓這個(gè)內(nèi)存區(qū)映射到 896MB 之外的物理內(nèi)存。如下圖:

        那么什么時(shí)候使用 vmalloc 呢?一般來說,如果要申請(qǐng)大塊的內(nèi)存就可以用vmalloc。

        vmalloc實(shí)現(xiàn)

        可以通過?vmalloc()?函數(shù)向內(nèi)核申請(qǐng)一塊內(nèi)存,其原型如下:

        void * vmalloc(unsigned long size);

        參數(shù)?size?表示要申請(qǐng)的內(nèi)存塊大小。

        我們看看看?vmalloc()?函數(shù)的實(shí)現(xiàn),代碼如下:

        static inline void * vmalloc(unsigned long size)
        {
        return __vmalloc(size, GFP_KERNEL|__GFP_HIGHMEM, PAGE_KERNEL);
        }

        從上面代碼可以看出,vmalloc()?函數(shù)直接調(diào)用了?__vmalloc()?函數(shù),而?__vmalloc()?函數(shù)的實(shí)現(xiàn)如下:

        void * __vmalloc(unsigned long size, int gfp_mask, pgprot_t prot)
        {
        void * addr;
        struct vm_struct *area;

        size = PAGE_ALIGN(size); // 內(nèi)存對(duì)齊
        if (!size || (size >> PAGE_SHIFT) > num_physpages) {
        BUG();
        return NULL;
        }

        area = get_vm_area(size, VM_ALLOC); // 申請(qǐng)一個(gè)合法的虛擬地址
        if (!area)
        return NULL;

        addr = area->addr;
        // 映射物理內(nèi)存地址
        if (vmalloc_area_pages(VMALLOC_VMADDR(addr), size, gfp_mask, prot)) {
        vfree(addr);
        return NULL;
        }

        return addr;
        }

        __vmalloc()?函數(shù)主要工作有兩點(diǎn):

        • 調(diào)用?get_vm_area()?函數(shù)申請(qǐng)一個(gè)合法的虛擬內(nèi)存地址。

        • 調(diào)用?vmalloc_area_pages()?函數(shù)把虛擬內(nèi)存地址映射到物理內(nèi)存地址。

        接下來,我們看看?get_vm_area()?函數(shù)的實(shí)現(xiàn),代碼如下:

        struct vm_struct * get_vm_area(unsigned long size, unsigned long flags)
        {
        unsigned long addr;
        struct vm_struct **p, *tmp, *area;

        area = (struct vm_struct *) kmalloc(sizeof(*area), GFP_KERNEL);
        if (!area)
        return NULL;
        size += PAGE_SIZE;
        addr = VMALLOC_START;
        write_lock(&vmlist_lock);
        for (p = &vmlist; (tmp = *p) ; p = &tmp->next) {
        if ((size + addr) < addr)
        goto out;
        if (size + addr <= (unsigned long) tmp->addr)
        break;
        addr = tmp->size + (unsigned long) tmp->addr;
        if (addr > VMALLOC_END-size)
        goto out;
        }
        area->flags = flags;
        area->addr = (void *)addr;
        area->size = size;
        area->next = *p;
        *p = area;
        write_unlock(&vmlist_lock);
        return area;

        out:
        write_unlock(&vmlist_lock);
        kfree(area);
        return NULL;
        }

        get_vm_area()?函數(shù)比較簡(jiǎn)單,首先申請(qǐng)一個(gè)類型為?vm_struct?的結(jié)構(gòu)?area?用于保存申請(qǐng)到的虛擬內(nèi)存地址。然后查找可用的虛擬內(nèi)存地址,如果找到,就把虛擬內(nèi)存到虛擬內(nèi)存地址保存到?area?變量中。最后把?area?連接到?vmalloc?虛擬內(nèi)存地址管理鏈表?vmlist?中。vmlist?鏈表最終結(jié)果如下圖:

        申請(qǐng)到虛擬內(nèi)存地址后,__vmalloc()?函數(shù)會(huì)調(diào)用?vmalloc_area_pages()?函數(shù)來對(duì)虛擬內(nèi)存地址與物理內(nèi)存地址進(jìn)行映射。

        我們知道,映射過程就是對(duì)進(jìn)程的?頁表?進(jìn)行映射。但每個(gè)進(jìn)程都有一個(gè)獨(dú)立?頁表(內(nèi)核線程除外),并且我們知道內(nèi)核空間是所有進(jìn)程共享的,那么就有個(gè)問題:如果只映射當(dāng)前進(jìn)程?頁表?的內(nèi)核空間,那么怎么同步到其他進(jìn)程的內(nèi)核空間呢?

        為了解決內(nèi)核空間同步問題,Linux 并不是直接對(duì)當(dāng)前進(jìn)程的內(nèi)核空間映射的,而是對(duì)?init?進(jìn)程的內(nèi)核空間(init_mm)進(jìn)行映射,我們來看看?vmalloc_area_pages()?函數(shù)的實(shí)現(xiàn):

        inline int vmalloc_area_pages (unsigned long address, unsigned long size,
        int gfp_mask, pgprot_t prot)
        {
        pgd_t * dir;
        unsigned long end = address + size;
        int ret;

        dir = pgd_offset_k(address); // 獲取 address 地址在 init 進(jìn)程對(duì)應(yīng)的頁目錄項(xiàng)
        spin_lock(&init_mm.page_table_lock); // 對(duì) init_mm 上鎖
        do {
        pmd_t *pmd;

        pmd = pmd_alloc(&init_mm, dir, address);
        ret = -ENOMEM;
        if (!pmd)
        break;

        ret = -ENOMEM;
        if (alloc_area_pmd(pmd, address, end - address, gfp_mask, prot)) // 對(duì)頁目錄項(xiàng)進(jìn)行映射
        break;

        address = (address + PGDIR_SIZE) & PGDIR_MASK;
        dir++;

        ret = 0;
        } while (address && (address < end));
        spin_unlock(&init_mm.page_table_lock);
        return ret;
        }

        從上面代碼可以看出,vmalloc_area_pages()?函數(shù)映射的主體是?init?進(jìn)程的內(nèi)存空間。因?yàn)橛成涞?init?進(jìn)程的內(nèi)存空間,所以當(dāng)前進(jìn)程訪問?vmalloc()?函數(shù)申請(qǐng)的內(nèi)存時(shí),由于沒有對(duì)虛擬內(nèi)存進(jìn)行映射,所以會(huì)發(fā)生?缺頁異常?而觸發(fā)內(nèi)核調(diào)用?do_page_fault()?函數(shù)來修復(fù)。我們看看?do_page_fault()?函數(shù)對(duì)?vmalloc()?申請(qǐng)的內(nèi)存異常處理:

        void do_page_fault(struct pt_regs *regs, unsigned long error_code)
        {
        ...
        __asm__("movl %%cr2,%0":"=r" (address)); // 獲取出錯(cuò)的虛擬地址
        ...

        if (address >= TASK_SIZE && !(error_code & 5))
        goto vmalloc_fault;

        ...

        vmalloc_fault:
        {
        int offset = __pgd_offset(address);
        pgd_t *pgd, *pgd_k;
        pmd_t *pmd, *pmd_k;
        pte_t *pte_k;

        asm("movl %%cr3,%0":"=r" (pgd));
        pgd = offset + (pgd_t *)__va(pgd);
        pgd_k = init_mm.pgd + offset;

        if (!pgd_present(*pgd_k))
        goto no_context;
        set_pgd(pgd, *pgd_k);

        pmd = pmd_offset(pgd, address);
        pmd_k = pmd_offset(pgd_k, address);
        if (!pmd_present(*pmd_k))
        goto no_context;
        set_pmd(pmd, *pmd_k);

        pte_k = pte_offset(pmd_k, address);
        if (!pte_present(*pte_k))
        goto no_context;
        return;
        }
        }

        上面的代碼就是當(dāng)進(jìn)程訪問?vmalloc()?函數(shù)申請(qǐng)到的內(nèi)存時(shí),發(fā)生?缺頁異常?而進(jìn)行的異常修復(fù),主要的修復(fù)過程就是把?init?進(jìn)程的?頁表項(xiàng)?復(fù)制到當(dāng)前進(jìn)程的?頁表項(xiàng)?中,這樣就可以實(shí)現(xiàn)所有進(jìn)程的內(nèi)核內(nèi)存地址空間同步。


        瀏覽 99
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        評(píng)論
        圖片
        表情
        推薦
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            天天操天天综合 | 一级AA视频 | jizzzz成熟丰满韩国女视频 | 宝贝乖~张开腿我轻点 | 国产精品人成A片一区二区 | 无码日韩人妻精品久久蜜桃 | 娇妻被交换粗又大又硬动漫 | 黄色动漫视频免费看 | 六月婷婷久久 | 欧美老妇50|