1. 程式人生 > >linux高階記憶體管理之永久核心對映

linux高階記憶體管理之永久核心對映

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

核心可以採用三種不同的機制將頁框對映到高階記憶體;分別叫做永久核心對映、臨時核心對映以及非連續記憶體分配。在這裡,只總結前兩種技術,第三種技術將在後面總結。

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

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

永久記憶體對映

永久核心對映允許核心建立高階頁框到核心地址空間的長期對映。他們使用住核心頁表中一個專門的頁表,其地址存放在變數pkmap_page_table中,這在前面的頁表機制管理區初始化中已經介紹過了。頁表中的表項數由LAST_PKMAP巨集產生。因此,核心一次最多訪問2MB4MB的高階記憶體。

/*這裡由定義可以看出永久記憶體對映為固定對映下面的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)
{
	unsigned long vaddr;
	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();
			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.*/
	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 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]) {/*減小這個值,因為在對映的時候對其進行了加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);
}