Kernel那些事兒之記憶體管理(11) --- 核心映射(上)

來源:互聯網
上載者:User

標籤:記憶體管理   linux kernel   

前面簡單地介紹了三種不同的地址空間,接下來重點講述線性地址空間到物理地址空間的映射。


我們先從32位系統開始。

在32位系統中,線性地址空間的大小為 2^32,即4GB。Kernel一般會按照 3:1 的比例,把線性地址空間分為兩部分:

  • 0~3GB 使用者地址空間

  • 3GB~4GB 核心地址空間。

650) this.width=650;" src="http://s3.51cto.com/wyfs02/M02/70/D9/wKiom1W_NS6isOSgAABJiSmORuo084.jpg" title="32-mapping.png" alt="wKiom1W_NS6isOSgAABJiSmORuo084.jpg" width="230" height="253" border="0" hspace="0" vspace="0" style="width:230px;height:253px;" />


使用者地址空間的管理和映射是個大的topic。我們後面再詳細講述。


核心地址空間只有1GB大小,最多隻能映射1GB的實體記憶體。那問題來了:1GB之外實體記憶體怎麼辦? 

Kernel 給出的解決辦法是,把1GB的核心地址空間分成兩部分:

  • 前面的896MB:即 3GB ~ (3GB+896MB),與實體記憶體的 0~896MB 作直接映射。

  • 後面的128MB:即 (3GB+896MB) ~ 4GB,這部分可以動態映射不同位置的實體記憶體。


這樣,分時複用最後這128MB的線性地址空間,Kernel 就可以訪問到全部的實體記憶體了。正所謂,退一步海闊天空啊。


也正因為如此,大於896MB實體記憶體,無法直接映射到核心地址空間,通常被稱為高端記憶體。


那核心地址空間中後面的這128MB是如何動態映射不同位置的實體記憶體的呢?Kernel為此提供了三種機制,如所示。


650) this.width=650;" src="http://s3.51cto.com/wyfs02/M00/70/DA/wKiom1W_PXKQDfdtAAHt0J3gQhU569.jpg" title="kernel-mapping.png" width="720" height="194" border="0" hspace="0" vspace="0" style="width:720px;height:194px;" alt="wKiom1W_PXKQDfdtAAHt0J3gQhU569.jpg" />


雖然機制不同,但是它們都需要通過修改頁表來完成映射。我們先介紹一類特殊的頁表:kernel page tables。


我們知道,每個進程都有自己的一套頁表,用於完成線性地址到物理地址的轉換。而核心也維護了一套頁表,稱為 kernel page tables. 

不過,這套頁表有點與眾不同。系統初始化完成後,kernel page tables不會被任何使用者態或核心態進程直接使用。咦?不被任何進程使用,那它到底有什麼用呢?

這套頁表的頁目錄表,即所謂的 master kernel Page Global Directory,其最高的一些頁目錄項 (128/4 = 32),為進程的頁表提供了一個參考模型。如果修改了master kernel Page Global Directory中的這些頁目錄項,這些改動最終會傳播到進程所使用的頁表中。那這個“傳播”是怎麼實現的呢?我們在後面會更詳細地講述。


持久核心映射 (Persistent Kernel Mappings)

持久核心映射可以用來比較持久地把高端記憶體頁面映射到核心地址空間中。既然是映射,那就要涉及到三個元素:集合L,集合P,以及這兩個集合之間的映射M。


集合L:核心在那128MB的線性地址空間中預留了一塊地區。如所示,該地區的範圍是 PKMAP_BASE ~ FIXADDR_START。

集合P:高端記憶體頁面。

映射M:這個就是下面要講的內容。


資料結構

持久核心映射使用了 master kernel page tables 中一個供自己專用的頁表: pkmap_page_table. 

宏 LAST_PKMAP 定義了該頁表中有多少頁表項。

 41 #ifdef CONFIG_X86_PAE 42 #define LAST_PKMAP 512 43 #else 44 #define LAST_PKMAP 1024 45 #endif

沒開啟PAE的情況下,該頁表中有 1024 頁表項,所以持久核心映射一次能夠映射 4MB 的高端記憶體。


整型數組 pkmap_count 用來描述頁表 pkmap_page_table 中每個頁表項的使用方式。

 61 static int pkmap_count[LAST_PKMAP];
  • counter 等於 0:對應的頁表項沒有映射高端記憶體頁面,可以使用。

  • counter 等於 1:對應的頁表項沒有映射高端記憶體頁面,但是卻不能被使用,因為它對應的TLB項還沒有被重新整理。

  • counter 大於 1:對應的頁表項映射了高端記憶體頁面,且被使用了 (n - 1)次。


為了能夠方便的找到一個高端記憶體頁面通過持久核心映射機制所映射到的線性地址,核心使用了一個雜湊表:page_address_htable.

該雜湊表中每一項為 struct page_address_map:

234 /*235  * Describes one page->virtual association236  */237 struct page_address_map {238     struct page *page;239     void *virtual;240     struct list_head list;241 };


在介紹核心如何利用這些資料結構建立持久核心映射之前,我們先看看核心如何尋找一個物理頁面所映射到的線性地址。


尋找

262 void *page_address(struct page *page)263 {264     unsigned long flags;265     void *ret;266     struct page_address_slot *pas;267268     if (!PageHighMem(page))269         return lowmem_page_address(page);270271     pas = page_slot(page);272     ret = NULL;273     spin_lock_irqsave(&pas->lock, flags);274     if (!list_empty(&pas->lh)) {275         struct page_address_map *pam;276277         list_for_each_entry(pam, &pas->lh, list) {278             if (pam->page == page) {279                 ret = pam->virtual;280                 goto done;281             }282         }283     }284 done:285     spin_unlock_irqrestore(&pas->lock, flags);286     return ret;287 }


函數 page_address() 區分兩種情況:

1.該頁面不屬於高端記憶體。那麼該頁面位於實體記憶體中的 0 ~ 896MB,該範圍的實體記憶體頁面都是直接映射到核心地址空間中的,所以頁面的線性地址總是存在,且可通過頁面索引計算出來。

 536 static __always_inline void *lowmem_page_address(struct page *page) 537 { 538     return __va(page_to_pfn(page) << PAGE_SHIFT); 539 }


2.該頁面屬於高端記憶體。這時就用到前面講的那個雜湊表了。如果在雜湊表中找到了該頁面,說明該頁面已經建立了持久核心映射,則返回其線性地址;否則返回NULL。


映射

建立一個持久核心映射是由函數 kmap() 完成的。

  4 void *kmap(struct page *page)  5 {  6     might_sleep();  7     if (!PageHighMem(page))  8         return page_address(page);  9     return kmap_high(page); 10 }

如果頁面不屬於高端記憶體,則直接返回其對應的線性地址。否則利用函數 kmap_high() 來完成映射操作。


166 void fastcall *kmap_high(struct page *page)167 {168     unsigned long vaddr;169170     /*171      * For highmem pages, we can‘t trust "virtual" until172      * after we have the lock.173      *174      * We cannot call this from interrupts, as it may block175      */176     spin_lock(&kmap_lock);177     vaddr = (unsigned long)page_address(page);178     if (!vaddr)179         vaddr = map_new_virtual(page);180     pkmap_count[PKMAP_NR(vaddr)]++;181     BUG_ON(pkmap_count[PKMAP_NR(vaddr)] < 2);182     spin_unlock(&kmap_lock);183     return (void*) vaddr;184 }

該函數首先判斷頁面是否已經映射。如果沒有,則通過函數 map_new_virtual()來建立一個新的映射。

最後遞增數組 pkmap_count中的計數值。


116 static inline unsigned long map_new_virtual(struct page *page)117 {118     unsigned long vaddr;119     int count;120121 start:122     count = LAST_PKMAP;123     /* Find an empty entry */124     for (;;) {125         last_pkmap_nr = (last_pkmap_nr + 1) & LAST_PKMAP_MASK;126         if (!last_pkmap_nr) {127             flush_all_zero_pkmaps();128             count = LAST_PKMAP;129         }130         if (!pkmap_count[last_pkmap_nr])131             break;  /* Found a usable entry */132         if (--count)133             continue;134135         /*136          * Sleep for somebody else to unmap their entries137          */138         {139             DECLARE_WAITQUEUE(wait, current);140141             __set_current_state(TASK_UNINTERRUPTIBLE);142             add_wait_queue(&pkmap_map_wait, &wait);143             spin_unlock(&kmap_lock);144             schedule();145             remove_wait_queue(&pkmap_map_wait, &wait);146             spin_lock(&kmap_lock);147148             /* Somebody else might have mapped it while we slept */149             if (page_address(page))150                 return (unsigned long)page_address(page);151152             /* Re-start */153             goto start;154         }155     }156     vaddr = PKMAP_ADDR(last_pkmap_nr);157     set_pte_at(&init_mm, vaddr,158            &(pkmap_page_table[last_pkmap_nr]), mk_pte(page, kmap_prot));159160     pkmap_count[last_pkmap_nr] = 1;161     set_page_address(page, (void *)vaddr);162163     return vaddr;164 }

該函數做了兩件事情:

  • 通過尋找數組 pkmap_count,找到一個可用的頁表項。通過該頁表項,建立線性地址到物理頁面的映射。

  • 建立好了映射之後,把該映射關係插入雜湊表中,以方便以後的尋找操作。


在找可用的頁表項時,是從上次的位置 (last_pkmap_nr) 開始的。如果已遍曆到了數組pkmap_count的尾部,則從數組頭部開始接著遍曆。不過接著遍曆之前,會先調用函數 flush_all_zero_pkmaps()。該函數專門尋找counter為1的頁表項,然後對它們做了四件事情:

  • 把 counter 重設為0。

  • 清除頁表 pkmap_page_table 中相應的頁表項。

  • 刪除雜湊表中對應的元素。

  • 對屬於持久核心映射的線性地址空間中的所有TLB項進行重新整理。


如果沒能找到可用的頁表項,當前進程就會進入睡眠,直到其他進程釋放了 pkmap_page_table中的一個頁表項。也正因為如此,函數kmap()會阻塞當前進程,不能用在中斷上下文中。

如果找到了可用的頁表項,則計算出其對應的線性地址,在頁表pkmap_page_table對應的頁表項中建立映射,把pkmap_count中的counter置為1,最後把該映射關係插入到雜湊表中。


解除一個持久核心映射是由函數 kunmap() 完成的。

 12 void kunmap(struct page *page) 13 { 14     if (in_interrupt()) 15         BUG(); 16     if (!PageHighMem(page)) 17         return; 18     kunmap_high(page); 19 }

如果頁面不屬於高端記憶體,就沒啥可做的了。否則,利用函數 kunmap_high() 來解除映射。


188 void fastcall kunmap_high(struct page *page)189 {190     unsigned long vaddr;191     unsigned long nr;192     int need_wakeup;193194     spin_lock(&kmap_lock);195     vaddr = (unsigned long)page_address(page);196     BUG_ON(!vaddr);197     nr = PKMAP_NR(vaddr);203     need_wakeup = 0;204     switch (--pkmap_count[nr]) {205     case 0:206         BUG();207     case 1:218         need_wakeup = waitqueue_active(&pkmap_map_wait);219     }220     spin_unlock(&kmap_lock);221222     /* do wake-up, if needed, race-free outside of the spin lock */223     if (need_wakeup)224         wake_up(&pkmap_map_wait);225 }

該函數的實現非常簡單。遞減數組 pkmap_count 中對應的counter。如果發現counter為1,則說明其對應的頁表項已沒人使用,於是喚醒在函數 map_new_virtual() 中由於等待可用頁表項而進入睡眠的進程。


本文出自 “核心部落格” 部落格,請務必保留此出處http://richardguo.blog.51cto.com/9343720/1682364

Kernel那些事兒之記憶體管理(11) --- 核心映射(上)

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.