<span id="mktg5"></span>

<i id="mktg5"><meter id="mktg5"></meter></i>

        <label id="mktg5"><meter id="mktg5"></meter></label>
        最新文章專題視頻專題問答1問答10問答100問答1000問答2000關鍵字專題1關鍵字專題50關鍵字專題500關鍵字專題1500TAG最新視頻文章推薦1 推薦3 推薦5 推薦7 推薦9 推薦11 推薦13 推薦15 推薦17 推薦19 推薦21 推薦23 推薦25 推薦27 推薦29 推薦31 推薦33 推薦35 推薦37視頻文章20視頻文章30視頻文章40視頻文章50視頻文章60 視頻文章70視頻文章80視頻文章90視頻文章100視頻文章120視頻文章140 視頻2關鍵字專題關鍵字專題tag2tag3文章專題文章專題2文章索引1文章索引2文章索引3文章索引4文章索引5123456789101112131415文章專題3
        問答文章1 問答文章501 問答文章1001 問答文章1501 問答文章2001 問答文章2501 問答文章3001 問答文章3501 問答文章4001 問答文章4501 問答文章5001 問答文章5501 問答文章6001 問答文章6501 問答文章7001 問答文章7501 問答文章8001 問答文章8501 問答文章9001 問答文章9501
        當前位置: 首頁 - 科技 - 知識百科 - 正文

        關于高端內存的權威解釋

        來源:懂視網 責編:小采 時間:2020-11-09 08:13:41
        文檔

        關于高端內存的權威解釋

        關于高端內存的權威解釋:注:本文是我見到的所有 關于 高端內存 解釋 的最詳細、最清晰的 解釋 ,其他帖子寥寥數語寫的都是垃圾,保存下來只為方便后來人和我自己,感謝原文作者! 原文地址:http://bbs.chinaunix.net/thread-1938084-1-1.html 注:本文提及的物理地址空間
        推薦度:
        導讀關于高端內存的權威解釋:注:本文是我見到的所有 關于 高端內存 解釋 的最詳細、最清晰的 解釋 ,其他帖子寥寥數語寫的都是垃圾,保存下來只為方便后來人和我自己,感謝原文作者! 原文地址:http://bbs.chinaunix.net/thread-1938084-1-1.html 注:本文提及的物理地址空間

        注:本文是我見到的所有 關于 高端內存 解釋 的最詳細、最清晰的 解釋 ,其他帖子寥寥數語寫的都是垃圾,保存下來只為方便后來人和我自己,感謝原文作者! 原文地址:http://bbs.chinaunix.net/thread-1938084-1-1.html 注:本文提及的物理地址空間可以理解

        注:本文是我見到的所有關于高端內存解釋的最詳細、最清晰的解釋,其他帖子寥寥數語寫的都是垃圾,保存下來只為方便后來人和我自己,感謝原文作者!

        原文地址:http://bbs.chinaunix.net/thread-1938084-1-1.html



        注:本文提及的物理地址空間可以理解為就是物理內存,但是在某些情況下,把他們理解為物理內存是不對的。

        本文討論的環境是NON-PAE的i386平臺,內核版本2.6.31-14

        一.什么是高端內存

        linux中內核使用3G-4G的線性地址空間,也就是說總共只有1G的地址空間可以用來映射物理地址空間。但是,如果內存大于1G的情況下呢?是不是超過1G的內存就無法使用了呢?為此內核引入了一個高端內存的概念,把1G的線性地址空間劃分為兩部分:小于896M物理地址空間的稱之為低端內存,這部分內存的物理地址和3G開始的線性地址是一一對應映射的,也就是說內核使用的線性地址空間3G--(3G+896M)和物理地址空間0-896M一一對應;剩下的128M的線性空間用來映射剩下的大于896M的物理地址空間,這也就是我們通常說的高端內存區。

        所謂的建立高端內存的映射就是能用一個線性地址來訪問高端內存的頁。如何理解這句話呢?在開啟分頁后,我們要訪問一個物理內存地址,需要經過MMU的轉換,也就是一個32位地址vaddr的高10位用來查找該vaddr所在頁目錄項,用12-21位來查找頁表項,再用0-11位偏移和頁的起始物理地址相加得到paddr,再把該paddr放到前端總線上,那么我們就可以訪問該vaddr對應的物理內存了。在低端內存中,每一個物理內存頁在系統初始化的時候都已經存在這樣一個映射了。而高端內存還不存在這樣一個映射(頁目錄項,頁表都是空的),所以我們必須要在系統初始化完后,提供一系列的函數來實現這個功能,這就是所謂的高端內存的映射。那么我們為什么不再系統初始化的時候把所有的內存映射都建立好呢?主要原因是,內核線性地址空間不足以容納所有的物理地址空間(1G的內核線性地址空間和最多可達4G的物理地址空間),所以才需要預留一部分(128M)的線性地址空間來動態的映射所有的物理地址空間,于是就產生了所謂的高端內存映射。

        二.內核如何管理高端內存


        上面的圖展示了內核如何使用3G-4G的線性地址空間,首先解釋下什么是high_memory

        在arch/x86/mm/init_32.c里面由如下代碼:

        #ifdef CONFIG_HIGHMEM

        highstart_pfn = highend_pfn = max_pfn;

        if (max_pfn > max_low_pfn)

        highstart_pfn = max_low_pfn;

        e820_register_active_regions(0, 0, highend_pfn);

        sparse_memory_present_with_active_regions(0);

        printk(KERN_NOTICE "%ldMB HIGHMEM available.\n",

        pages_to_mb(highend_pfn - highstart_pfn));

        num_physpages = highend_pfn;

        high_memory = (void *) __va(highstart_pfn * PAGE_SIZE-1)+1;

        #else

        e820_register_active_regions(0, 0, max_low_pfn);

        sparse_memory_present_with_active_regions(0);

        num_physpages = max_low_pfn;

        high_memory = (void *) __va(max_low_pfn * PAGE_SIZE - 1)+1;

        #endif

        high_memory是“具體物理內存的上限對應的虛擬地址”,可以這么理解:當內存內存小于896M時,那么high_memory = (void *)__va(max_low_pfn * PAGE_SIZE),max_low_pfn就是在內存中最后的一個頁幀號,所以high_memory=0xc0000000+物理內存大小;當內存大于896M時,那么highstart_pfn= max_low_pfn,此時max_low_pfn就不是物理內存的最后一個頁幀號了,而是內存為896M時的最后一個頁幀號,那么high_memory=0xc0000000+896M.總之high_memory是不能超過0xc0000000+896M.

        由于我們討論的是物理內存大于896M的情況,所以high_memory實際上就是0xc0000000+896M,從high_memory開始的128M(4G-high_memory)就是用作用來映射剩下的大于896M的內存的,當然這128M還可以用來映射設備的內存(MMIO)。

        從上圖我們看到有VMALLOC_START,VMALLOC_END,PKMAP_BASE,FIX_ADDRESS_START等宏術語,其實這些術語劃分了這128M的線性空間,一共分為三個區域:VMALLOC區域(本文不涉及這部分內容,關注本博客的其他文章),永久映射區(permanetkernelmappings), 臨時映射區(temporary kernelmappings).這三個區域都可以用來映射高端內存,本文重點闡述下后兩個區域是如何映射高端內存的。

        三.永久映射區(permanet kernel mappings)

        1.介紹幾個定義:

        PKMAP_BASE:永久映射區的起始線性地址。

        pkmap_page_table:永久映射區對應的頁表。

        LAST_PKMAP:pkmap_page_table里面包含的entry的數量=1024

        pkmap_count[LAST_PKMAP]數組:每一個元素的值對應一個entry的引用計數。關于引用計數的值,有以下幾種情況:

        0:說明這個entry可用。

        1:entry不可用,雖然這個entry沒有被用來映射任何內存,但是他仍然存在TLBentry沒有被flush,

        所以還是不可用。

        N:有N-1個對象正在使用這個頁面

        首先,要知道這個區域的大小是4M,也就是說128M的線性地址空間里面,只有4M的線性地址空間是用來作永久映射區的。至于到底是哪4M,是由PKMAP_BASE決定的,這個變量表示用來作永久內存映射的4M區間的起始線性地址。

        在NON-PAE的i386上,頁目錄里面的每一項都指向一個4M的空間,所以永久映射區只需要一個頁目錄項就可以了。而一個頁目錄項指向一張頁表,那么永久映射區正好就可以用一張頁表來表示了,于是我們就用pkmap_page_table來指向這張頁表。

        pgd = swapper_pg_dir + pgd_index(vaddr);

        pud = pud_offset(pgd, vaddr);//pud==pgd

        pmd = pmd_offset(pud, vaddr);//pmd==pud==pgd

        pte = pte_offset_kernel(pmd, vaddr);

        pkmap_page_table = pte;

        2.具體代碼分析(2.6.31)

        void *kmap(struct page *page)

        {

        might_sleep();

        if (!PageHighMem(page))

        return page_address(page);

        return kmap_high(page);

        }

        kmap()函數就是用來建立永久映射的函數:由于調用kmap函數有可能會導致進程阻塞,所以它不能在中斷處理函數等不可被阻塞的上下文下被調用,might_sleep()的作用就是當該函數在不可阻塞的上下文下被調用是,打印棧信息。接下來判斷該需要建立永久映射的頁是否確實屬于高端內存,因為我們知道低端內存的每個頁都已經存在和線性地址的映射了,所以,就不需要再建立了,page_address()函數返回該page對應的線性地址。(關于page_address()函數,參考本博客的專門文章有解釋)。最后調用kmap_high(page),可見kmap_high()才真正執行建立永久映射的操作。

        /**

        * kmap_high - map a highmem page into memory

        * @page: &struct page to map

        *

        * Returns the page's virtual memory address.

        *

        * We cannot call this from interrupts, as it may block.

        */

        void *kmap_high(struct page *page)

        {

        unsigned long vaddr;

        /*

        * For highmem pages, we can't trust "virtual" until

        * after we have the lock.

        */

        lock_kmap();

        vaddr = (unsigned long)page_address(page);

        if (!vaddr)

        vaddr = map_new_virtual(page);

        pkmap_count[PKMAP_NR(vaddr)]++;

        BUG_ON(pkmap_count[PKMAP_NR(vaddr)] < 2);

        unlock_kmap();

        return (void*) vaddr;

        }

        kmap_high函數分析:首先獲得對pkmap_page_table操作的鎖,然后再調用page_address()來返回該page是否已經被映射,我們看到前面在kmap()里面已經判斷過了,為什么這里還要再次判斷呢?因為再獲的鎖的時候,有可能鎖被其他CPU拿走了,而恰巧其他CPU拿了這個鎖之后,也是執行這段code,而且映射的也是同一個page,那么當它把鎖釋放掉的時候,其實就表示該page的映射已經被建立了,我們這里就沒有必要再去執行這段code了,所以就有必要在獲得鎖后再判斷下。

        如果發現vaddr不為空,那么就是剛才說的,已經被其他cpu上執行的任務給建立了,這里只需要把表示該頁引用計數的pkmap_count[]再加一就可以了。同時調用BUG_ON來確保該引用計數確實是不小于2的,否則就是有問題的了。然后返回vaddr,整個建立就完成了。

        如果發現vaddr為空呢?調用map_new_virtual()函數,到此我們看到,其實真正進行建立映射的代碼在這個函數里面

        static inline unsigned long map_new_virtual(struct page *page)

        {

        unsigned long vaddr;

        int count;

        start:

        count = LAST_PKMAP;//LAST_PKMAP=1024

        /* Find an empty entry */

        for (;;) {

        last_pkmap_nr = (last_pkmap_nr + 1) & LAST_PKMAP_MASK;

        if (!last_pkmap_nr) {

        flush_all_zero_pkmaps();

        count = LAST_PKMAP;

        }

        if (!pkmap_count[last_pkmap_nr])

        break; /* Found a usable entry */

        if (--count)

        continue;

        /*

        * Sleep for somebody else to unmap their entries

        */

        {

        DECLARE_WAITQUEUE(wait, current);

        __set_current_state(TASK_UNINTERRUPTIBLE);

        add_wait_queue(&pkmap_map_wait, &wait);

        unlock_kmap();

        schedule();

        remove_wait_queue(&pkmap_map_wait, &wait);

        lock_kmap();

        /* Somebody else might have mapped it while we slept */

        if (page_address(page))

        return (unsigned long)page_address(page);

        /* Re-start */

        goto start;

        }

        }

        vaddr = PKMAP_ADDR(last_pkmap_nr);

        set_pte_at(&init_mm, vaddr,

        &(pkmap_page_table[last_pkmap_nr]), mk_pte(page, kmap_prot));

        pkmap_count[last_pkmap_nr] = 1;

        set_page_address(page, (void *)vaddr);

        return vaddr;

        }

        last_pkmap_nr:記錄上次被分配的頁表項在pkmap_page_table里的位置,初始值為0,所以第一次分配的時候last_pkmap_nr等于1。

        接下來判斷什么時候last_pkmap_nr等于0,等于0就表示1023(LAST_PKMAP(1024)-1)個頁表項已經被分配了,這時候就需要調用flush_all_zero_pkmaps()函數,把所有pkmap_count[]計數為1的頁表項在TLB里面的entry給flush掉,并重置為0,這就表示該頁表項又可以用了,可能會有疑惑為什么不在把pkmap_count置為1的時候也就是解除映射的同時把TLB也flush呢?個人感覺有可能是為了效率的問題吧,畢竟等到不夠的時候再刷新,效率要好點吧。

        再判斷pkmap_count[last_pkmap_nr]是否為0,0的話就表示這個頁表項是可用的,那么就跳出循環了到下面了。

        PKMAP_ADDR(last_pkmap_nr)返回這個頁表項對應的線性地址vaddr.

        #definePKMAP_ADDR(nr) (PKMAP_BASE + ((nr) << PAGE_SHIFT))

        set_pte_at(mm,addr, ptep, pte)函數在NON-PAE i386上的實現其實很簡單,其實就等同于下面的代碼:

        staticinline void native_set_pte(pte_t *ptep , pte_t pte)

        {

        *ptep = pte;

        }

        我們已經知道頁表的線性起始地址存放在pkmap_page_table里面,那么相應的可用的頁表項的地址就是&pkmap_page_table[last_pkmap_nr],得到了頁表項的地址,只要把相應的pte填寫進去,那么整個映射不就完成了嗎?

        pte由兩部分組成:高20位表示物理地址,低12位表示頁的描述信息。

        怎么通過page查找對應的物理地址呢(參考page_address()一文)?其實很簡單,用(page- mem_map) 再移PAGE_SHIFT位就可以了。

        低12位的頁描述信息是固定的:kmap_prot=(_PAGE_PRESENT| _PAGE_RW | _PAGE_DIRTY | _PAGE_ACCESSED | _PAGE_GLOBAL).

        下面的代碼就是做了這些事情:

        mk_pte(page,kmap_prot));

        #definemk_pte(page, pgprot) pfn_pte(page_to_pfn(page), (pgprot))

        #definepage_to_pfn __page_to_pfn

        #define__page_to_pfn(page) ((unsigned long)((page) - mem_map) + \

        ARCH_PFN_OFFSET)

        staticinline pte_t pfn_pte(unsigned long page_nr, pgprot_t pgprot)

        {

        return __pte(((phys_addr_t)page_nr <

        massage_pgprot(pgprot));

        }

        接下來把pkmap_count[last_pkmap_nr]置為1,1不是表示不可用嗎,既然映射已經建立好了,應該賦值為2呀,其實這個操作是在他的上層函數kmap_high里面完成的(pkmap_count[PKMAP_NR(vaddr)]++).

        到此為止,整個映射就完成了,再把page和對應的線性地址加入到page_address_htable哈希鏈表里面就可以了(參考page_address一文)。

        我們繼續看所有的頁表項都已經用了的情況下,也就是1024個頁表項全已經映射了內存了,如何處理。此時count==0,于是就進入了下面的代碼:

        /*

        * Sleepfor somebody else to unmap their entries

        */

        {

        DECLARE_WAITQUEUE(wait, current);

        __set_current_state(TASK_UNINTERRUPTIBLE);

        add_wait_queue(&pkmap_map_wait, &wait);

        unlock_kmap();

        schedule();

        remove_wait_queue(&pkmap_map_wait, &wait);

        lock_kmap();

        /* Somebody else might have mapped it while we slept */

        if (page_address(page))

        return (unsignedlong)page_address(page);

        /* Re-start */

        goto start;

        }

        這段代碼其實很簡單,就是把當前任務加入到等待隊列pkmap_map_wait,當有其他任務喚醒這個隊列時,再繼續gotostart,重新整個過程。這里就是上面說的調用kmap函數有可能阻塞的原因。

        那么什么時候會喚醒pkmap_map_wait隊列呢?當調用kunmap_high函數,來釋放掉一個映射的時候。

        kunmap_high函數其實頁很簡單,就是把要釋放的頁表項的計數減1,如果等于1的時候,表示有可用的頁表項了,再喚醒pkmap_map_wait隊列

        /**

        *kunmap_high - map a highmem page into memory

        * @page:&struct page to unmap

        *

        * IfARCH_NEEDS_KMAP_HIGH_GET is not defined then this may be called

        * onlyfrom user context.

        */

        voidkunmap_high(struct page *page)

        {

        unsigned long vaddr;

        unsigned long nr;

        unsigned long flags;

        int need_wakeup;

        lock_kmap_any(flags);

        vaddr = (unsigned long)page_address(page);

        BUG_ON(!vaddr);

        nr = PKMAP_NR(vaddr);

        /*

        * A count must never go down to zero

        * without a TLB flush!

        */

        need_wakeup = 0;

        switch (--pkmap_count[nr]) {//減一

        case 0:

        BUG();

        case 1:

        /*

        * Avoidan unnecessary wake_up() function call.

        * Thecommon case is pkmap_count[] == 1, but

        * nowaiters.

        * Thetasks queued in the wait-queue are guarded

        * by boththe lock in the wait-queue-head and by

        * thekmap_lock. As the kmap_lock is held here,

        * no needfor the wait-queue-head's lock. Simply

        * test ifthe queue is empty.

        */

        need_wakeup =waitqueue_active(&pkmap_map_wait);

        }

        unlock_kmap_any(flags);

        /* do wake-up, if needed, race-free outside ofthe spin lock */

        if (need_wakeup)

        wake_up(&pkmap_map_wait);

        }

        聲明:本網頁內容旨在傳播知識,若有侵權等問題請及時與本網聯系,我們將在第一時間刪除處理。TEL:177 7030 7066 E-MAIL:11247931@qq.com

        文檔

        關于高端內存的權威解釋

        關于高端內存的權威解釋:注:本文是我見到的所有 關于 高端內存 解釋 的最詳細、最清晰的 解釋 ,其他帖子寥寥數語寫的都是垃圾,保存下來只為方便后來人和我自己,感謝原文作者! 原文地址:http://bbs.chinaunix.net/thread-1938084-1-1.html 注:本文提及的物理地址空間
        推薦度:
        標簽: 內存 所有 是我
        • 熱門焦點

        最新推薦

        猜你喜歡

        熱門推薦

        專題
        Top
        主站蜘蛛池模板: 在线观看亚洲视频| 亚洲精品无码mⅴ在线观看| 亚洲免费日韩无码系列| 四虎成人精品在永久免费| 亚洲乱码日产精品一二三| 毛片视频免费观看| 国产精品久久亚洲不卡动漫| 成年女人色毛片免费看| 亚洲精品无码久久久久A片苍井空| 成年18网站免费视频网站| 亚洲乱亚洲乱妇无码| 国产男女猛烈无遮挡免费视频| 亚洲av午夜电影在线观看| 免费A级毛片无码A∨男男| 国产精品玖玖美女张开腿让男人桶爽免费看| 亚洲国产日韩在线观频| 国产一级一毛免费黄片| 91亚洲国产成人精品下载| 日本在线高清免费爱做网站| 亚洲色中文字幕在线播放| 亚洲国产精品尤物yw在线| 国产一区二区三区免费观看在线| 久久精品亚洲一区二区| 免费精品国产自产拍在线观看图片 | 亚洲国产精品无码久久九九大片 | a级日本高清免费看| 亚洲国产精品婷婷久久| 成人毛片18女人毛片免费96| 另类小说亚洲色图| 亚洲日产韩国一二三四区| 最近最好最新2019中文字幕免费| 亚洲 日韩经典 中文字幕| 亚洲精品偷拍视频免费观看| 在线看片免费人成视频福利| 国产精品亚洲精品观看不卡| 亚洲乱亚洲乱少妇无码| 亚洲免费福利视频| 男女啪啪免费体验区| 亚洲视频免费一区| 亚洲国产精品第一区二区三区| 亚洲a一级免费视频|