天天看點

Linux高端記憶體映射(上)【轉】

版權聲明:本文為部落客原創文章,未經部落客允許不得轉載。

<a href="http://blog.csdn.net/vanbreaker/article/details/7579941#t0">高端記憶體概述</a>

<a href="http://blog.csdn.net/vanbreaker/article/details/7579941#t1">永久核心映射</a>

        在32位的系統上,核心占有從第3GB~第4GB的線性位址空間,共1GB大小,核心将其中的前896MB與實體記憶體的0~896MB進行直接映射,即線性映射,将剩餘的128M線性位址空間作為通路高于896M的記憶體的一個視窗。引入高端記憶體映射這樣一個概念的主要原因就是我們所安裝的記憶體大于1G時,核心的1G線性位址空間無法建立一個完全的直接映射來觸及整個實體記憶體空間,而對于80x86開啟PAE的情況下,允許的最大實體記憶體可達到64G,是以核心将自己的最後128M的線性位址空間騰出來,用以完成對高端記憶體的暫時性映射。而在64位的系統上就不存在這樣的問題了,因為可用的線性位址空間遠大于可安裝的記憶體。下圖描述了核心1GB線性位址空間是如何劃分的

Linux高端記憶體映射(上)【轉】

其中可以用來完成上述映射目的的區域為vmalloc area,Persistent kernel mappings區域和固定映射線性位址空間中的FIX_KMAP區域,這三個區域對應的映射機制分别為非連續記憶體配置設定,永久核心映射和臨時核心映射。

          在核心初始化頁表管理機制時,專門用pkmap_page_table這個變量儲存了PKMAP_BASE對應的頁表項的位址,由pkmap_page_table來維護永久核心映射區的頁表項的映射,頁表項總數為LAST_PKMAP個,具體可以看前面關于頁表機制初始化的博文。這裡的永久并不是指調用kmap()建立的映射關系會一直持續下去無法解除,而是指在調用kunmap()解除映射之間這種映射會一直存在,這是相對于臨時核心映射機制而言的。

       核心用一個pkmap_count數組來記錄pkmap_page_table中每一個頁表項的使用狀态,其實就是為每個頁表項配置設定一個計數器來記錄相應的頁表是否已經被用來映射。計數值分為以下三種情況:

計數值為0:對應的頁表項沒有映射高端記憶體,即為空閑可用的

計數值為1:   對應的頁表項沒有映射高端記憶體,但是不可用,因為上次映射後對應的TLB項還未被淸刷

計數值為n(n&gt;1):對應的頁表項已經映射了一個高端記憶體頁框,并且有n-1個核心成分正在利用這種映射關系

下面結合代碼進行具體的分析,先通過alloc_page(__GFP_HIGHMEM)配置設定到了一個屬于高端記憶體區域的page結構,然後調用kmap(struct page*page)來建立與永久核心映射區的映射,需要注意一點的是,當永久核心映射區沒有空閑的頁表項可供映射時,請求映射的程序會被阻塞,是以永久核心映射請求不能發生在中斷和可延遲函數中。

void *kmap(struct page *page)  

{  

    might_sleep();  

    if (!PageHighMem(page))/*頁框屬于低端記憶體*/  

        return page_address(page);/*傳回頁框的虛拟位址*/  

    return kmap_high(page);  

}  

如果頁框是屬于高端記憶體的話,則調用kmap_high()來建立映射

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);  

    /*虛拟位址不存在則調用map_new_virtual()為該頁框配置設定一個虛拟位址,完成映射*/  

    if (!vaddr)  

        vaddr = map_new_virtual(page);  

    pkmap_count[PKMAP_NR(vaddr)]++;/*相應的頁表項的計數值加1*/  

    BUG_ON(pkmap_count[PKMAP_NR(vaddr)] &lt; 2);  

    unlock_kmap();  

    return (void*) vaddr;  

如果該頁框之前沒被映射到永久核心映射區,則要通過map_new_virtual()函數在永久核心映射區對應的頁表中找到一個空閑的表項來映射這個頁框,簡單的說就是為這個頁框配置設定一個線性位址。

static inline unsigned long map_new_virtual(struct page *page)  

    int count;  

start:  

    /*LAST_PKMAP為永久映射區可以映射的頁框數,在禁用PAE的情況下為512,開啟PAE的情況下為1024, 

    也就是說核心通過kmap,一次最多能映射2M/4M的高端記憶體*/  

    count = LAST_PKMAP;  

    /* Find an empty entry */  

    for (;;) {  

        /*last_pkmap_nr記錄了上次周遊pkmap_count數組找到一個空閑頁表項後的位置,首先從 

        last_pkmap_nr出開始周遊,如果未能在pkmap_count中找到計數值為0的頁表項,則last_pkmap_nr 

        和LAST_PKMAP_MASK相與後又回到0,進行第二輪周遊*/  

        last_pkmap_nr = (last_pkmap_nr + 1) &amp; LAST_PKMAP_MASK;  

        /*last_pkmap_nr變為了0,也就是說第一次周遊中未能找到計數值為0的頁表項*/  

        if (!last_pkmap_nr) {  

            flush_all_zero_pkmaps();  

            count = LAST_PKMAP;  

        }  

        if (!pkmap_count[last_pkmap_nr])/*找到一個計數值為0的頁表項,即空閑可用的頁表項*/  

            break;  /* Found a usable entry */  

        if (--count)  

            continue;  

        /* 

         * Sleep for somebody else to unmap their entries 

         */  

         /*在pkmap_count數組中,找不到計數值為0或1的頁表項,即所有頁表項都被核心映射了, 

            則聲明一個等待隊列,并将目前要求映射高端記憶體的程序添加到等待隊列中然後 

            阻塞該程序,等待其他的程序釋放了KMAP區的某個頁框的映射*/  

        {  

            DECLARE_WAITQUEUE(wait, current);  

            __set_current_state(TASK_UNINTERRUPTIBLE);  

            add_wait_queue(&amp;pkmap_map_wait, &amp;wait);  

            unlock_kmap();  

            schedule();  

            remove_wait_queue(&amp;pkmap_map_wait, &amp;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*/  

    vaddr = PKMAP_ADDR(last_pkmap_nr);  

    /*将pkmap_page_table中對應的pte設為申請映射的頁框的pte,完成永久核心映射區中的頁表項到實體頁框的映射*/  

    set_pte_at(&amp;init_mm, vaddr,  

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

    pkmap_count[last_pkmap_nr] = 1;  

    /*設定頁面的虛拟位址,将該頁面添加到page_address_htable散清單中*/  

    set_page_address(page, (void *)vaddr);  

    return vaddr;  

當第一趟周遊pkmap_count數組找不到計數值為0的頁表項時,就要調用flush_all_zero_pkmaps(),将計數值為1的頁表項置為0,撤銷已經不用了的映射,并且重新整理TLB

&lt;span style="font-size:12px;"&gt;static void flush_all_zero_pkmaps(void)  

    int i;  

    int need_flush = 0;  

    flush_cache_kmaps();  

    for (i = 0; i &lt; LAST_PKMAP; i++) {  

        struct page *page;  

         * zero means we don't have anything to do, 

         * &gt;1 means that it is still in use. Only 

         * a count of 1 means that it is free but 

         * needs to be unmapped 

         /*将計數值為1的頁面的計數值設為0,*/  

        if (pkmap_count[i] != 1)  

        pkmap_count[i] = 0;  

        /* sanity check */  

        BUG_ON(pte_none(pkmap_page_table[i]));  

         * Don't need an atomic fetch-and-clear op here; 

         * no-one has the page mapped, and cannot get at 

         * its virtual address (and hence PTE) without first 

         * getting the kmap_lock (which is held here). 

         * So no dangers, even with speculative execution. 

         /*撤銷之前的映射關系,并将page從page_address_htable散清單中删除*/  

        page = pte_page(pkmap_page_table[i]);  

        pte_clear(&amp;init_mm, (unsigned long)page_address(page),  

              &amp;pkmap_page_table[i]);  

        set_page_address(page, NULL);  

        need_flush = 1;  

    if (need_flush)/*重新整理TLB*/  

        flush_tlb_kernel_range(PKMAP_ADDR(0), PKMAP_ADDR(LAST_PKMAP));  

}&lt;/span&gt;  

在map_new_virtual()中,當找到一個空閑頁表後,還要調用set_page_address()将page與該頁表項對應的線性位址進行關聯,這裡并不是簡單的page結構中的virtual給賦上相應的值,而是将該映射的page添加到page_address_htable散清單中,該散清單維護着所有被映射到永久核心映射區的頁框,散清單中的每一項記錄了頁框的page結構位址和映射頁框的線性位址。

&lt;span style="font-size:12px;"&gt;void set_page_address(struct page *page, void *virtual)  

    unsigned long flags;  

    struct page_address_slot *pas;  

    struct page_address_map *pam;  

    BUG_ON(!PageHighMem(page));  

    pas = page_slot(page);  

    if (virtual) {      /* Add */  

        BUG_ON(list_empty(&amp;page_address_pool));  

        spin_lock_irqsave(&amp;pool_lock, flags);  

        /*從page_address_pool中得到一個空閑的page_address_map*/  

        pam = list_entry(page_address_pool.next,  

                struct page_address_map, list);  

        list_del(&amp;pam-&gt;list);/*将該節點從page_address_pool中删除*/  

        spin_unlock_irqrestore(&amp;pool_lock, flags);  

        /*将頁框的page結構位址和虛拟位址儲存到page_address_map中,可以看到并沒有直接設定page的虛拟位址*/  

        pam-&gt;page = page;  

        pam-&gt;virtual = virtual;  

        spin_lock_irqsave(&amp;pas-&gt;lock, flags);  

        list_add_tail(&amp;pam-&gt;list, &amp;pas-&gt;lh);/*将pam添入散清單*/  

        spin_unlock_irqrestore(&amp;pas-&gt;lock, flags);  

    } else {        /* Remove */ /*從散清單中删除一個節點,執行與上面相反的操作*/  

        list_for_each_entry(pam, &amp;pas-&gt;lh, list) {  

            if (pam-&gt;page == page) {  

                list_del(&amp;pam-&gt;list);  

                spin_unlock_irqrestore(&amp;pas-&gt;lock, flags);  

                spin_lock_irqsave(&amp;pool_lock, flags);  

                list_add_tail(&amp;pam-&gt;list, &amp;page_address_pool);  

                spin_unlock_irqrestore(&amp;pool_lock, flags);  

                goto done;  

            }  

done:  

    return;  

&lt;/span&gt;  

弄清楚了kmap()為頁框建立永久核心映射,那麼釋放映射的話就容易了解了,當我們需要釋放頁框的映射時,調用kunmap()函數

&lt;span style="font-size:12px;"&gt;void kunmap(struct page *page)  

    if (in_interrupt())  

        BUG();  

    if (!PageHighMem(page))/*頁框處于低端記憶體則直接傳回*/  

        return;  

    kunmap_high(page);  

&lt;span style="font-size:12px;"&gt;void kunmap_high(struct page *page)  

    unsigned long nr;  

    int need_wakeup;  

    lock_kmap_any(flags);  

    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]) {/*該頁面對應的頁表項計數值減1*/  

    case 0:  

    case 1:  

         * Avoid an unnecessary wake_up() function call. 

         * The common case is pkmap_count[] == 1, but 

         * no waiters. 

         * The tasks queued in the wait-queue are guarded 

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

         * the kmap_lock.  As the kmap_lock is held here, 

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

         * test if the queue is empty. 

        /*确定pkmap_map_wait等待隊列是否為空*/  

        need_wakeup = waitqueue_active(&amp;pkmap_map_wait);  

    unlock_kmap_any(flags);  

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

    if (need_wakeup)/*等待隊列不為空則喚醒其中的程序*/  

        wake_up(&amp;pkmap_map_wait);  

【新浪微網誌】 張昺華--sky

【twitter】 @sky2030_

【facebook】 張昺華 zhangbinghua

本文版權歸作者和部落格園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接配接,否則保留追究法律責任的權利.

繼續閱讀