天天看點

Linux高端記憶體管理之永久核心映射inux高端記憶體管理之永久核心映射  與直接映射的實體記憶體末端、高端記憶體的始端所對應的線性位址存放在high_memory變量中,在x86體系結構上,高于896MB的所有實體記憶體的範圍大都是高端記憶體,它并不會永久地或自動地映射到核心位址空間,盡管x86處理器能夠尋址實體RAM的範圍達到4GB(啟用PAE可以尋址到64GB)。一旦這些頁被配置設定,就必須in射到核心的邏輯位址空間上。在x86上,高端記憶體中的頁被映射到3GB-4GB。

  核心可以采用三種不同的機制将頁框映射到高端記憶體;分别叫做永久核心映射、臨時核心映射以及非連續記憶體配置設定。在這裡,隻總結前兩種技術,第三種技術将在後面總結。

  建立永久核心映射可能阻塞目前程序;這發生在空閑頁表項不存在時,也就是在高端記憶體上沒有頁表項可以用作頁框的“視窗”時。是以,永久核心映射不能用于中斷處理程式和可延遲函數。相反,建立臨時核心映射絕不會要求阻塞目前程序;不過,他的缺點是隻有很少的臨時核心映射可以同時建立起來。

  使用臨時核心映射的核心控制路徑必須保證目前沒有其他的核心控制路徑在使用同樣地映射。這意味着核心控制路徑永遠不能被阻塞,後者其他核心控制路徑有可能使用同一個視窗來映射其他的高端記憶體頁。

  永久記憶體映射

  永久核心映射允許核心建立高端頁框到核心位址空間的長期映射。他們使用住核心頁表中一個專門的頁表,其位址存放在變量pkmap_page_table中,這在前面的頁表機制管理區初始化中已經介紹過了。頁表中的表項數由last_pkmap宏産生。是以,核心一次最多通路2mb或4mb的高端記憶體。

  /*這裡由定義可以看出永久記憶體映射為固定映射下面的4m空間*/

  #define pkmap_base ((fixaddr_boot_start - page_size * (last_pkmap + 1)) \

  & pmd_mask)

  該頁表映射的線性位址從pkmap_base開始。pkmap_count數組包含last_pkmap個計數器,pkmap_page_table頁表中的每一項都有一個。

  高端映射區邏輯頁面的配置設定結構用配置設定表(pkmap_count)來描述,它有1024項,對應于映射區内不同的邏輯頁面。當配置設定項的值等于0時為自由項,等于1時為緩沖項,大于1時為映射項。映射頁面的配置設定基于配置設定表的掃描,當所有的自由項都用完時,系統将清除所有的緩沖項,如果連緩沖項都用完時,系統将進入等待狀态。

  /*

  高端映射區邏輯頁面的配置設定結構用配置設定表(pkmap_count)來描述,它有1024項,

  對應于映射區内不同的邏輯頁面。當配置設定項的值等于零時為自由項,等于1時為

  緩沖項,大于1時為映射項。映射頁面的配置設定基于配置設定表的掃描,當所有的自由

  項都用完時,系統将清除所有的緩沖項,如果連緩沖項都用完時,系

  統将進入等待狀态。

  */

  static int pkmap_count[last_pkmap];

  /*last_pkmap_nr:記錄上次被配置設定的頁表項在pkmap_page_table裡的位置,初始值為0,是以第一次配置設定的時候last_pkmap_nr等于1*/

  static unsigned int last_pkmap_nr;

  為了記錄高端記憶體頁框與永久核心映射包含的線性位址之間的聯系,核心使用了page_address_htable散清單。該表包含一個page_address_map資料結構,用于為高端記憶體中的每一個頁框進行目前映射。而該資料結構還包含一個指向頁描述符的指針和配置設定給該頁框的線性位址。

* hash table bucket

*/

static struct page_address_slot {

struct list_head lh;            /* list of page_address_maps */

spinlock_t lock;            /* protect this bucket's list */

} ____cacheline_aligned_in_smp page_address_htable[1<<pa_hash_order];

/*

* describes one page->virtual association

struct page_address_map {

struct page *page;

void *virtual;

struct list_head list;

};

 page_address()函數傳回頁框對應的線性位址

* returns the page's virtual address.

/*傳回頁框對應的線性位址*/

void *page_address(struct page *page)

{

unsigned long flags;

void *ret;

struct page_address_slot *pas;

/*如果頁框不在高端記憶體中*/

if (!pagehighmem(page))

/*線性位址總是存在,通過計算頁框下标

然後将其轉換成實體位址,最後根據相應的

實體位址得到線性位址*/

return lowmem_page_address(page);

/*從page_address_htable散清單中得到pas*/

pas = page_slot(page);

ret = null;

spin_lock_irqsave(&pas->lock, flags);

if (!list_empty(&pas->lh)) {/*如果對應的連結清單不空,

該連結清單中存放的是page_address_map結構*/

struct page_address_map *pam;

/*對每個連結清單中的元素*/

list_for_each_entry(pam, &pas->lh, list) {

if (pam->page == page) {

ret = pam->virtual;/*傳回線性位址*/

goto done;

}

done:

spin_unlock_irqrestore(&pas->lock, flags);

return ret;

  kmap()函數建立永久核心映射。

/*高端記憶體映射,運用數組進行操作配置設定情況

配置設定好後需要加入哈希表中;*/

void *kmap(struct page *page)

might_sleep();

if (!pagehighmem(page))/*如果頁框不屬于高端記憶體*/

return page_address(page);

return kmap_high(page);/*頁框确實屬于高端記憶體*/

/**

* 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)/*如果沒有*/

/*把頁框的實體位址插入到pkmap_page_table的

一個項中并在page_address_htable散清單中加入一個

元素*/

vaddr = map_new_virtual(page);

pkmap_count[pkmap_nr(vaddr)]++;/*配置設定計數加一,此時流程都正确應該是2了*/

bug_on(pkmap_count[pkmap_nr(vaddr)] < 2);

unlock_kmap();

return (void*) vaddr;/*傳回位址*/

static inline unsigned long map_new_virtual(struct page *page)

int count;

start:

count = last_pkmap;

/* find an empty entry */

for (;;) {

/*加1,防止越界*/

last_pkmap_nr = (last_pkmap_nr + 1) & last_pkmap_mask;

接下來判斷什麼時候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呢?

個人感覺有可能是為了效率的問題吧,畢竟等到不夠的時候再重新整理,效率要好點吧。

if (!last_pkmap_nr) {

flush_all_zero_pkmaps();

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

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.*/

vaddr = pkmap_addr(last_pkmap_nr);

v

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

static inline void native_set_pte(pte_t *ptep , pte_t pte)

*ptep = pte;

*/  set_pte_at(&init_mm, vaddr,/*設定頁表項*/

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

/*接下來把pkmap_count[last_pkmap_nr]置為1,1不是表示不可用嗎,

既然映射已經建立好了,應該指派為2呀,其實這個操作

是在他的上層函數kmap_high裡面完成的(pkmap_count[pkmap_nr(vaddr)]++).*/

pkmap_count[last_pkmap_nr] = 1;

/*到此為止,整個映射就完成了,再把page和對應的線性位址

加入到page_address_htable哈希連結清單裡面就可以了*/

set_page_address(page, (void *)vaddr);

return vaddr;

 kunmap()函數撤銷先前由kmap()建立的永久核心映射

  如果頁确實在高端記憶體中,則調用kunmap_high()函數

* kunmap_high - map a highmem page into memory

* @page: &struct page to unmap

* if arch_needs_kmap_high_get is not defined then this may be called

* only from user context.

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]) {/*減小這個值,因為在映射的時候對其進行了加2*/

case 0:

bug();

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.

need_wakeup = waitqueue_active(&pkmap_map_wait);

unlock_kmap_any(flags);

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

if (need_wakeup)

wake_up(&pkmap_map_wait);

最新内容請見作者的github頁:http://qaseven.github.io/

繼續閱讀