国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

深入理解Linux虛擬內(nèi)存管理(六)

這篇具有很好參考價值的文章主要介紹了深入理解Linux虛擬內(nèi)存管理(六)。希望對大家有所幫助。如果存在錯誤或未考慮完全的地方,請大家不吝賜教,您也可以點擊"舉報違法"按鈕提交疑問。

深入理解Linux虛擬內(nèi)存管理(六)

系列文章目錄


Linux 內(nèi)核設(shè)計與實現(xiàn)
深入理解 Linux 內(nèi)核
Linux 設(shè)備驅(qū)動程序
Linux設(shè)備驅(qū)動開發(fā)詳解
深入理解Linux虛擬內(nèi)存管理(一)
深入理解Linux虛擬內(nèi)存管理(二)
深入理解Linux虛擬內(nèi)存管理(三)
深入理解Linux虛擬內(nèi)存管理(四)
深入理解Linux虛擬內(nèi)存管理(五)
深入理解Linux虛擬內(nèi)存管理(六)
深入理解Linux虛擬內(nèi)存管理(七)
深入理解Linux虛擬內(nèi)存管理(八)
深入理解Linux虛擬內(nèi)存管理(九)



一、高端內(nèi)存管理

1、映射高端內(nèi)存頁面

9.1 管理 PKMap 地址空間

(1)kmap

? ? 這個 API 用于進行阻塞的調(diào)用者。

// include/asm-i386/highmem.h
// 核心函數(shù) __kmap 的第2個參數(shù)表示調(diào)用者將進行阻塞。
#define kmap(page) __kmap(page, 0)

(2)kmap_nonblock

// include/asm-i386/highmem.h
// 核心函數(shù) __kmap 的第2個參數(shù)表示調(diào)用者將不進行阻塞。
#define kmap_nonblock(page) __kmap(page, 1)

(3)__kmap

// include/asm-i386/highmem.h
static inline void *__kmap(struct page *page, int nonblocking)
{
// 這個函數(shù)不會在中斷時調(diào)用,因為它已睡眠。out_of_line_bug()調(diào)用do_exit并
// 返回錯誤碼來取代BUG()。之所以不調(diào)用BUG()是因為BUG()用極端方式殺掉進程,這將
// 引起如寓言 “Aiee,殺掉中斷句柄! ” 般的內(nèi)核癱瘓。
	if (in_interrupt())
		out_of_line_bug();
	// 如果頁面已在低端內(nèi)存中,則返回一個直接映射。
	if (page < highmem_start_page)
		return page_address(page);
	// 調(diào)用kmap_high()(見L1. 4小節(jié))完成與體系結(jié)構(gòu)無關(guān)的工作。
	return kmap_high(page, nonblocking);
}

(4)kmap_high

// mm/highmem.c
void *kmap_high(struct page *page, int nonblocking)
{
	unsigned long vaddr;

	/*
	 * For highmem pages, we can't trust "virtual" until
	 * after we have the lock.
	 *
	 * We cannot call this from interrupts, as it may block
	 */
	// kmap_lock 保護頁面的 virtual 字段和 pkmap_count 數(shù)組。
	spin_lock(&kmap_lock);
	// 獲取頁面的虛擬地址。
	vaddr = (unsigned long) page->virtual;
	// 如果尚未映射,則調(diào)用map_new_virtual(),進行頁面映射并返回其虛擬地址。
	// 如果失敗,則跳轉(zhuǎn)到 out 處,釋放自旋鎖并返回 NULL。
	if (!vaddr) {
		vaddr = map_new_virtual(page, nonblocking);
		if (!vaddr)
			goto out;
	}
	// 增加頁面映射的引用計數(shù)。
	pkmap_count[PKMAP_NR(vaddr)]++;
	// 如果計數(shù)現(xiàn)在小于2,則這是個嚴重的bug。在實際運行中,嚴重的系統(tǒng)問題可能會引起這個bug。
	if (pkmap_count[PKMAP_NR(vaddr)] < 2)
		BUG();
 out:
 	// 釋放 kmap_lock。
	spin_unlock(&kmap_lock);
	return (void*) vaddr;
}

(5)map_new_virtual

? ? 這個函數(shù)分為 3 個主要部分:查找一個空閑槽;如果沒有可用的槽則在隊列上等待;然后映射該頁面。

// mm/highmem.c
static inline unsigned long map_new_virtual(struct page *page, int nonblocking)
{
	unsigned long vaddr;
	int count;

start:
	// 從最后一個可能的槽開始掃描。
	count = LAST_PKMAP;
	/* Find an empty entry */
	// 持續(xù)掃描并等待直至有一個槽空閑。這里可能導致某些進程進入死循環(huán)。
	for (;;) {
	// last_pkmap_nr 是掃描中最后一個pkmap。為阻止搜索同一個頁面,記錄該值就可以
	// 進行循環(huán)搜索。如果達到LAST_PKMAP,則折返為0。
		last_pkmap_nr = (last_pkmap_nr + 1) & LAST_PKMAP_MASK;
	// 在 last_pkmap_nr 折返為 0 時,調(diào)用 flush_all_zero_pkmaps() (I. 1, 6 小節(jié)),在
	// 刷新TLB之前設(shè)置pkmap_count數(shù)組中所有的項從1改為0。接著重新設(shè)置LAST_PKMAP
	// 表示再次開始掃描。
		if (!last_pkmap_nr) {
			flush_all_zero_pkmaps();
			count = LAST_PKMAP;
		}
	// 如果元素為0,則表示為頁面找到了一個可用槽。
		if (!pkmap_count[last_pkmap_nr])
			break;	/* Found a usable entry */
	// 轉(zhuǎn)到下一個下標,開始掃描。
		if (--count)
			continue;
	// 下一塊代碼將睡眠,等待一個槽變空閑。如果調(diào)用者要求不阻塞該函數(shù),則直接返回
		if (nonblocking)
			return 0;

		/*
		 * Sleep for somebody else to unmap their entries
		 */
// 如果在掃描完所有頁面一遍后仍沒有可用槽,則在pkmap_map_wait隊列上睡眠直至在
// 解除映射后被喚醒。
		{
	// 聲明該等待隊列。
			DECLARE_WAITQUEUE(wait, current);
	// 設(shè)置隊列可中斷,因為是在內(nèi)核空間中睡眠。
			current->state = TASK_UNINTERRUPTIBLE;
	// 添加自己到pkmap_map_wait隊列。
			add_wait_queue(&pkmap_map_wait, &wait);
	// 釋放kmap_lock自旋鎖。
			spin_unlock(&kmap_lock);
	// 調(diào)用schedule(),使自己開始睡眠。在解除映射后如果有槽空閑,則自己被喚醒。
			schedule();
	// 從等待隊列移除自己。
			remove_wait_queue(&pkmap_map_wait, &wait);
	// 重新獲取kmap鎖。
			spin_lock(&kmap_lock);

			/* Somebody else might have mapped it while we slept */
	// 如果在睡眠時有其他的事務映射了該頁面,則僅僅返回地址,并由kmap_high()增加引用計數(shù)。
			if (page->virtual)
				return (unsigned long) page->virtual;

			/* Re-start */
	// 重新開始掃描。
			goto start;
		}
	}
// 這塊代碼在找到一個槽時進行處理,用于映射頁面。
	// 獲取被找到槽的虛擬地址。
	vaddr = PKMAP_ADDR(last_pkmap_nr);
	// 確保PTE與頁面對應且已獲得必需的保護,將其放置在頁表中被找到槽的地方。
	set_pte(&(pkmap_page_table[last_pkmap_nr]), mk_pte(page, kmap_prot));
	// 初始化pkmap_count數(shù)組的值為1。該計數(shù)在父函數(shù)中增加,如果這是第一次在該
	// 函數(shù)處出現(xiàn),可以保證這是第一次映射。
	pkmap_count[last_pkmap_nr] = 1;
	// 設(shè)置頁面的虛擬字段。
	page->virtual = (void *) vaddr;
	// 返回虛擬地址。
	return vaddr;
}

? ? 其核心就是,如果 page 是一個高端頁面,則把其映射到從 PKMAP_BASEFIXADDR_START 之間的虛擬地址。

(6)flush_all_zero_pkmaps

? ? 這個函數(shù)循環(huán)遍歷 pkmap_count 數(shù)組并在刷新 TLB 之前設(shè)置所有的項從 1 變?yōu)?0

// mm/highmem.c
static void flush_all_zero_pkmaps(void)
{
	int i;
	// 由于全局頁表將變化,因此必須刷新所有處理器的CPU高速緩存。
	flush_cache_all();
	// 循環(huán)遍歷整個pkmap_count數(shù)組。
	for (i = 0; i < LAST_PKMAP; i++) {
		struct page *page;

		/*
		 * zero means we don't have anything to do,
		 * >1 means that it is still in use. Only
		 * a count of 1 means that it is free but
		 * needs to be unmapped
		 */
	// 如果元素不為1,則移到下一個元素。
		if (pkmap_count[i] != 1)
			continue;
	// 從1設(shè)置為0。
		pkmap_count[i] = 0;

		/* sanity check */
	// 確保PTE沒有被映射過。
		if (pte_none(pkmap_page_table[i]))
			BUG();

		/*
		 * 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.
		 */
	// 從PTE解除對頁面的映射,并清除PTE。
		page = pte_page(pkmap_page_table[i]);
		pte_clear(&pkmap_page_table[i]);
	// 更新虛擬字段為頁面未被映射。
		page->virtual = NULL;
	}
	// 刷新TLB。
	flush_tlb_all();
}

2、自動映射高端內(nèi)存頁面

? ? 下面是 x86km_type 枚舉類型的例子。這里列舉了自動調(diào)用 kmap 的不同中斷。由于 KM_TYPE_NR 是最后一個元素,所以它用作所有元素的計數(shù)器。

// include/asm-i386/kmap_types.h
enum km_type {
	KM_BOUNCE_READ,
	KM_SKB_SUNRPC_DATA,
	KM_SKB_DATA_SOFTIRQ,
	KM_USER0,
	KM_USER1,
	KM_BH_IRQ,
	KM_SOFTIRQ0,
	KM_SOFTIRQ1,
	KM_TYPE_NR
};

(1)kmap_atomic

9.4 原子性的映射高端內(nèi)存頁面

? ? 這是 kmap() 的原子版本。請注意,不論在什么時候都不持有自旋鎖或不睡眠。之所以不需要自旋鎖是因為每個處理器都有它自己的保留空間。

// include/asm-i386/highmem.h
/*
 * The use of kmap_atomic/kunmap_atomic is discouraged - kmap/kunmap
 * gives a more generic (and caching) interface. But kmap_atomic can
 * be used in IRQ contexts, so in some (very limited) cases we need
 * it.
 */
// 這里的參數(shù)為映射的頁面和處理所需的類型。一個槽的使用對應一個處理器。
static inline void *kmap_atomic(struct page *page, enum km_type type)
{
	enum fixed_addresses idx;
	unsigned long vaddr;
	// 如果頁面在低端內(nèi)存,則返回直接映射。
	if (page < highmem_start_page)
		return page_address(page);
	// type給出了使用哪個槽。而 KM_TYPE_NR * smp_processor_id() 則給出了為處理
	// 器保留的槽的集合。
	idx = type + KM_TYPE_NR*smp_processor_id();
	// 獲取虛擬地址。
	vaddr = __fix_to_virt(FIX_KMAP_BEGIN + idx);
#if HIGHMEM_DEBUG
	// 用于調(diào)試的代碼。在實際運行中,PTE將退出。
	if (!pte_none(*(kmap_pte-idx)))
		out_of_line_bug();
#endif
	// 為保留的槽設(shè)置PTE。
	set_pte(kmap_pte-idx, mk_pte(page, kmap_prot));
	// 為槽刷新TLB。
	__flush_tlb_one(vaddr);
	// 返回虛擬地址。
	return (void*) vaddr;
}

3、解除頁面映射

9.3 解除頁面映射

(1)kunmap

// include/asm-i386/highmem.h
static inline void kunmap(struct page *page)
{
	// kunmap()不能在中斷中進行調(diào)用,因此自然退出。
	if (in_interrupt())
		out_of_line_bug();
	// 如果頁面已在低端內(nèi)存中,則不需要進行解除映射的操作。
	if (page < highmem_start_page)
		return;
	// 調(diào)用與體系結(jié)構(gòu)無關(guān)的函數(shù)kunmap_high()。
	kunmap_high(page);
}

(2)kunmap_high

? ? 這是與體系結(jié)構(gòu)無關(guān)的 kunmap() 操作部分。

// mm/highmem.c
void kunmap_high(struct page *page)
{
	unsigned long vaddr;
	unsigned long nr;
	int need_wakeup;
	// 獲取kmap鎖, 用來保護虛擬字段和pkmap_count數(shù)組。
	spin_lock(&kmap_lock);
	// 獲取虛擬頁面
	vaddr = (unsigned long) page->virtual;
	// 如果沒有設(shè)置虛擬字段,那這是兩次解除映射的操作或是對沒有映射頁面的解
	// 除映射操作,則調(diào)用BUG。
	if (!vaddr)
		BUG();
	// 獲取在pkmap_count數(shù)組中的下標。
	nr = PKMAP_NR(vaddr);

	/*
	 * A count must never go down to zero
	 * without a TLB flush!
	 */
	// 在缺省情況下,并不需要喚醒進程來調(diào)用kmap()。
	need_wakeup = 0;
	// 在減小下標后檢查其值。
	switch (--pkmap_count[nr]) {
	// 如果減為0,那這是個bug,因為需要刷新TLB以使0成為合法項。
	case 0:
		BUG();
	// 如果減小到1(項雖然已釋放,但還需要刷新TLB),則檢查是否有誰在
	// pkmap_map_wait_queue隊列上睡眠。如果有必要,則在釋放自旋鎖后喚醒隊列。
	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);
	}
	// 釋放 kmap_lock。
	spin_unlock(&kmap_lock);

	/* do wake-up, if needed, race-free outside of the spin lock */
	// 如果還有等待者在隊列上而槽已被釋放,則喚醒它們。
	if (need_wakeup)
		wake_up(&pkmap_map_wait);
}

4、自動解除高端內(nèi)存頁面映射

(1)kunmap_atomic

? ? 整個函數(shù)是調(diào)試代碼。其原因是頁面只在此處自動映射,那么在解除映射以前只會在很小的范圍且較短的時間內(nèi)被使用。保留頁面在那里是安全的,因為在解除映射之后不會再引用它們,而對同一個槽的另一個映射會簡單地替換它。

// include/asm-i386/highmem.h
static inline void kunmap_atomic(void *kvaddr, enum km_type type)
{
#if HIGHMEM_DEBUG
	// 獲取虛擬地址并確保它和單個頁邊界對齊。
	unsigned long vaddr = (unsigned long) kvaddr & PAGE_MASK;
	enum fixed_addresses idx = type + KM_TYPE_NR*smp_processor_id();
	// 如果提供的地址不在定長區(qū)域內(nèi),則返回。
	if (vaddr < FIXADDR_START) // FIXME
		return;

	// 如果地址與使用類型及處理器不對應,則聲明它。
	if (vaddr != __fix_to_virt(FIX_KMAP_BEGIN+idx))
		out_of_line_bug();

	/*
	 * force other mappings to Oops if they'll try to access
	 * this pte without first remap it
	 */
	// 現(xiàn)在解除頁面映射,如果再次引用它,則會產(chǎn)生oops。
	pte_clear(kmap_pte-idx);
	__flush_tlb_one(vaddr);
#endif
}

5、彈性緩沖區(qū)

(1)創(chuàng)建彈性緩沖區(qū)

① create_bounce

9.5.2 創(chuàng)建彈性緩沖區(qū)

? ? 函數(shù)調(diào)用圖如圖 9.3 所示。它是創(chuàng)建彈性緩沖區(qū)的高層函數(shù)。它由兩部分組成,即分配必要的資源和從模板復制數(shù)據(jù)。

// mm/highmem.c
// 函數(shù)的參數(shù)如下所示:
//	rw 如果是寫緩沖區(qū),則設(shè)置為1。
//	bh_orig 是模板緩沖區(qū)頭,是復制數(shù)據(jù)源。
struct buffer_head * create_bounce(int rw, struct buffer_head * bh_orig)
{
	struct page *page;
	struct buffer_head *bh;
	// 如果模板緩沖區(qū)頭已在低端內(nèi)存中,則簡單地返回它。
	if (!PageHighMem(bh_orig->b_page))
		return bh_orig;
	// 從slab分配器分別緩沖區(qū)頭,如果失敗則從緊急池分配。
	bh = alloc_bounce_bh();
	/*
	 * This is wasteful for 1k buffers, but this is a stopgap measure
	 * and we are being ineffective anyway. This approach simplifies
	 * things immensly. On boxes with more than 4GB RAM this should
	 * not be an issue anyway.
	 */
	// 從伙伴分配器分配頁面,如果失敗則從緊急池分配。
	page = alloc_bounce_page();
	// 將分配的頁面與分配的緩沖區(qū)頭關(guān)聯(lián)起來。
	set_bh_page(bh, page, 0);

// 這塊產(chǎn)生新的緩沖區(qū)頭。
	// 逐個復制必要的信息,除了 b_list 字段,因為該緩沖區(qū)并不與該鏈表上的其他部分直
	// 接連接。
	bh->b_next = NULL;
	bh->b_blocknr = bh_orig->b_blocknr;
	bh->b_size = bh_orig->b_size;
	bh->b_list = -1;
	bh->b_dev = bh_orig->b_dev;
	bh->b_count = bh_orig->b_count;
	bh->b_rdev = bh_orig->b_rdev;
	bh->b_state = bh_orig->b_state;
#ifdef HIGHMEM_DEBUG
// 僅用于調(diào)試的信息。
	bh->b_flushtime = jiffies;
	bh->b_next_free = NULL;
	bh->b_prev_free = NULL;
	/* bh->b_this_page */
	bh->b_reqnext = NULL;
	bh->b_pprev = NULL;
#endif
	/* bh->b_page */
	if (rw == WRITE) {
// 如果這個緩沖區(qū)將被寫入,則用于結(jié)束I/O的回調(diào)函數(shù)是bounce_end_io_write()
// (見L 5. 2.1節(jié)),它在設(shè)備接收所有信息后調(diào)用。由于數(shù)據(jù)在高端內(nèi)存中,則從
// copy_from_high_bh() (見 I. 5. 2. 3)復制 “下來” 。
		bh->b_end_io = bounce_end_io_write;
		copy_from_high_bh(bh, bh_orig);
	} else
// 如果等待設(shè)備寫數(shù)據(jù)到緩沖區(qū)中,則調(diào)用bounce_end_io_read() (見I. 5.2.2)
// 回調(diào)函數(shù)。
		bh->b_end_io = bounce_end_io_read;
// 從模板緩沖區(qū)頭復制剩下的信息。
	bh->b_private = (void *)bh_orig;
	bh->b_rsector = bh_orig->b_rsector;
#ifdef HIGHMEM_DEBUG
	memset(&bh->b_wait, -1, sizeof(bh->b_wait));
#endif
	// 返回新彈性緩沖區(qū)
	return bh;
}
⑴ ? alloc_bounce_bh

? ? alloc_bounce_bh 函數(shù)

⑵ ? alloc_bounce_page

? ? alloc_bounce_page 函數(shù)

⑶ ? set_bh_page
// fs/buffer.c
void set_bh_page (struct buffer_head *bh, struct page *page, unsigned long offset)
{
	if (offset >= PAGE_SIZE)
		BUG();

	if (PageHighMem(page)) {
		bh->b_data = (char *)offset;
	} else {
		bh->b_data = page_address(page) + offset;
	}
	bh->b_page = page;
}
EXPORT_SYMBOL(set_bh_page);
⑷ ? copy_from_high_bh

? ? copy_from_high_bh 函數(shù)

⑸ ? bounce_end_io_write

? ? bounce_end_io_write 函數(shù)

⑹ ? bounce_end_io_read

? ? bounce_end_io_read 函數(shù)

② alloc_bounce_bh

? ? 這個函數(shù)首先嘗試從 slab 分配器分配一個 buffer_head ,如果失敗,則使用緊急池。

// mm/highmem.c
struct buffer_head *alloc_bounce_bh (void)
{
	struct list_head *tmp;
	struct buffer_head *bh;
	// 嘗試從slab分配器分配一個新buffer_head。 請注意,如何產(chǎn)生不使用I/O操作的請
	// 求以避免遞歸,包括高級I/O。
	bh = kmem_cache_alloc(bh_cachep, SLAB_NOHIGHIO);
	// 如果分配成功,則返回。
	if (bh)
		return bh;
	/*
	 * No luck. First, kick the VM so it doesn't idle around while
	 * we are using up our emergency rations.
	 */
	// 如果失敗,則喚醒bdflush清洗頁面。
	wakeup_bdflush();

repeat_alloc:
	/*
	 * Try to allocate from the emergency pool.
	 */
// 由于從slab分配失敗,因此從緊急池中分配。
	// 取得緊急池頭鏈表的尾部。
	tmp = &emergency_bhs;
	// 獲取保護池的鎖。
	spin_lock_irq(&emergency_lock);
	// 如果池不空,則從鏈表取得一個緩沖區(qū)并減小nr_emergency_bhs計數(shù)器。
	if (!list_empty(tmp)) {
		bh = list_entry(tmp->next, struct buffer_head, b_inode_buffers);
		list_del(tmp->next);
		nr_emergency_bhs--;
	}
	// 釋放鎖。
	spin_unlock_irq(&emergency_lock);
	// 如果分配成功,則返回它。
	if (bh)
		return bh;

	/* we need to wait I/O completion */
	// 如果失敗,則表示內(nèi)存不足,那么補充池的惟一方法便是完成高端內(nèi)存I/O操作。就
	// 這樣,請求從tq_disk開始,然后寫數(shù)據(jù)到磁盤,接著釋放進程中適當?shù)捻撁妗?/span>
	run_task_queue(&tq_disk);
	// 出讓處理器。
	yield();
	// 再次嘗試從緊急池分配。
	goto repeat_alloc;
}
③ alloc_bounce_page

? ? 這個函數(shù)本質(zhì)上與 alloc_bounce_bh() 一樣。它首先嘗試從伙伴分配器分配頁面,如果失敗則從緊急池中分配。

// mm/highmem.c
struct page *alloc_bounce_page (void)
{
	struct list_head *tmp;
	struct page *page;
	// 從伙伴分配器進行分配,如果成功則返回頁面。
	page = alloc_page(GFP_NOHIGHIO);
	if (page)
		return page;
	/*
	 * No luck. First, kick the VM so it doesn't idle around while
	 * we are using up our emergency rations.
	 */
	// 喚醒bdflush清洗頁面。
	wakeup_bdflush();

repeat_alloc:
	/*
	 * Try to allocate from the emergency pool.
	 */
	// 取得緊急池緩沖區(qū)頭鏈表尾部。 	
	tmp = &emergency_pages;
	// 獲取保護池的鎖。
	spin_lock_irq(&emergency_lock);
	// 如果池不空,則從鏈表取得頁面并減小可用nr_emergency_pages的計數(shù)。
	if (!list_empty(tmp)) {
		page = list_entry(tmp->next, struct page, list);
		list_del(tmp->next);
		nr_emergency_pages--;
	}
	// 釋放鎖。
	spin_unlock_irq(&emergency_lock);
	// 如果分配成功,則返回它。
	if (page)
		return page;

	/* we need to wait I/O completion */
	// 運行I/O任務隊列,嘗試并補充緊急池。
	run_task_queue(&tq_disk);
	// 讓出處理器。
	yield();
	// 再次嘗試從緊急池中分配。
	goto repeat_alloc;
}

(2)用彈性緩沖區(qū)復制

① bounce_end_io_write

? ? 在彈性緩沖區(qū)用于向設(shè)備寫數(shù)據(jù)而完成 I/O 操作時,調(diào)用該函數(shù)。由于緩沖區(qū)用于從高端內(nèi)存復制數(shù)據(jù)到設(shè)備,除了回收資源沒有其他更多的任務待處理。

// mm/highmem.c
static void bounce_end_io_write (struct buffer_head *bh, int uptodate)
{
	bounce_end_io(bh, uptodate);
}
⑴ ? bounce_end_io

? ? bounce_end_io 函數(shù)

② bounce_end_io_read

? ? 在數(shù)據(jù)從設(shè)備讀出且待復制到高端內(nèi)存時,調(diào)用該函數(shù)。由于在中斷中調(diào)用,所以要更加謹慎。

// mm/highmem.c
static void bounce_end_io_read (struct buffer_head *bh, int uptodate)
{
	struct buffer_head *bh_orig = (struct buffer_head *)(bh->b_private);

	// 調(diào)用copy_to_high_bh_irq() 從彈性緩沖區(qū)復制數(shù)據(jù)并移至高端內(nèi)存。
	if (uptodate)
		copy_to_high_bh_irq(bh_orig, bh);
	// 回收資源。
	bounce_end_io(bh, uptodate);
}
⑴ ? copy_to_high_bh_irq

? ? copy_to_high_bh_irq 函數(shù)

⑵ ? bounce_end_io

? ? bounce_end_io 函數(shù)

③ copy_from_high_bh

? ? 這個函數(shù)用于從高端內(nèi)存 buffer_head 復制數(shù)據(jù)到彈性緩沖區(qū)。

// mm/highmem.c
/*
 * Simple bounce buffer support for highmem pages.
 * This will be moved to the block layer in 2.5.
 */

static inline void copy_from_high_bh (struct buffer_head *to,
			 struct buffer_head *from)
{
	struct page *p_from;
	char *vfrom;

	p_from = from->b_page;
	// 映射高端內(nèi)存頁面到低端內(nèi)存。該執(zhí)行路徑由IRQ安全鎖io_request_lock保護,這
	// 樣可以安全地調(diào)用kmap_atomic()(見I. 2.1小節(jié))。
	vfrom = kmap_atomic(p_from, KM_USER0);
	// 復制數(shù)據(jù)。
	memcpy(to->b_data, vfrom + bh_offset(from), to->b_size);
	// 解除頁面映射。
	kunmap_atomic(vfrom, KM_USER0);
}
⑴ ? kmap_atomic

? ? kmap_atomic 函數(shù)

⑵ ? kunmap_atomic

? ? kunmap_atomic 函數(shù)

④ copy_to_high_bh_irq

? ? 在設(shè)備完成寫數(shù)據(jù)到彈性緩沖區(qū)后,從中斷中調(diào)用該函數(shù)。這個函數(shù)復制數(shù)據(jù)到高端內(nèi)存。

// mm/highmem.c
static inline void copy_to_high_bh_irq (struct buffer_head *to,
			 struct buffer_head *from)
{
	struct page *p_to;
	char *vto;
	unsigned long flags;

	p_to = to->b_page;
	// 保存標志位并禁止中斷。
	__save_flags(flags);
	__cli();
	// 映射高端內(nèi)存頁面到低端內(nèi)存。
	vto = kmap_atomic(p_to, KM_BOUNCE_READ);
	// 復制數(shù)據(jù)。
	memcpy(vto + bh_offset(to), from->b_data, to->b_size);
	// 解除頁面映射。
	kunmap_atomic(vto, KM_BOUNCE_READ);
	// 恢復中斷標志位。
	__restore_flags(flags);
}
⑴ ? kmap_atomic

? ? kmap_atomic 函數(shù)

⑵ ? kunmap_atomic

? ? kunmap_atomic 函數(shù)

⑤ bounce_end_io

? ? 這個函數(shù)回收由彈性緩沖區(qū)使用的資源。如果緊急池耗盡,則添加資源給它。

// mm/highmem.c
static inline void bounce_end_io (struct buffer_head *bh, int uptodate)
{
	struct page *page;
	struct buffer_head *bh_orig = (struct buffer_head *)(bh->b_private);
	unsigned long flags;
	// 為原始 buffer_head 調(diào)用I/O完成的回調(diào)函數(shù)。
	bh_orig->b_end_io(bh_orig, uptodate);
	// 獲取指向待釋放的緩沖區(qū)頁面的指針。
	page = bh->b_page;
	// 獲取緊急池的鎖。
	spin_lock_irqsave(&emergency_lock, flags);
// 如果頁面池已滿,則僅返回頁面至伙伴分配器進行頁面釋放	
	if (nr_emergency_pages >= POOL_SIZE)
		__free_page(page);
	else {
// 否則,添加頁面到緊急池。	
		/*
		 * We are abusing page->list to manage
		 * the highmem emergency pool:
		 */
		list_add(&page->list, &emergency_pages);
		nr_emergency_pages++;
	}

	if (nr_emergency_bhs >= POOL_SIZE) {
// 如果 buffer_head 池已滿,則僅返回它至slab分配器進行釋放
#ifdef HIGHMEM_DEBUG
		/* Don't clobber the constructed slab cache */
		init_waitqueue_head(&bh->b_wait);
#endif
		kmem_cache_free(bh_cachep, bh);
	} else {
//  否則,添加該 buffer_head  到緊急池。	
		/*
		 * Ditto in the bh case, here we abuse b_inode_buffers:
		 */
		list_add(&bh->b_inode_buffers, &emergency_bhs);
		nr_emergency_bhs++;
	}
	// 釋放鎖。
	spin_unlock_irqrestore(&emergency_lock, flags);
}

6、 緊急池

? ? 只有一個函數(shù)與緊急池相關(guān),那就是其初始化函數(shù)。在系統(tǒng)啟動時調(diào)用它,接著就刪除它的代碼,因為不再需要它。

(1)init_emergency_pool

? ? 這個函數(shù)為緊急頁面和緊急緩沖區(qū)頭創(chuàng)建一個池。

// mm/highmem.c
static __init int init_emergency_pool(void)
{
	struct sysinfo i;
        si_meminfo(&i);
        si_swapinfo(&i);
 	// 如果沒有可用的高端內(nèi)存,則返回。       
        if (!i.totalhigh)
        	return 0;
	// 獲取保護緊急池的鎖。
	spin_lock_irq(&emergency_lock);
// 從伙伴分配器分配POOL_SIZE個頁面并添加它們到鏈接表中。然后用
// nr_emergency_pages 記錄緊急池中頁面的數(shù)量。
	while (nr_emergency_pages < POOL_SIZE) {
		struct page * page = alloc_page(GFP_ATOMIC);
		if (!page) {
			printk("couldn't refill highmem emergency pages");
			break;
		}
		list_add(&page->list, &emergency_pages);
		nr_emergency_pages++;
	}
// 從slab分配器分配POOL_SIZE個buffer_head對象,并添加它們到由b_inode_buffers鏈接
// 的鏈表中。而且以nr_emergency_bhs記錄池中緩沖區(qū)頭的數(shù)量。
	while (nr_emergency_bhs < POOL_SIZE) {
		struct buffer_head * bh = kmem_cache_alloc(bh_cachep, SLAB_ATOMIC);
		if (!bh) {
			printk("couldn't refill highmem emergency bhs");
			break;
		}
		list_add(&bh->b_inode_buffers, &emergency_bhs);
		nr_emergency_bhs++;
	}
	// 釋放用于保護池的鎖。
	spin_unlock_irq(&emergency_lock);
	printk("allocated %d pages and %d bhs reserved for the highmem bounces\n",
	       nr_emergency_pages, nr_emergency_bhs);
	// 返回成功。
	return 0;
}

二、頁面幀回收

1、頁面高速緩存操作

2、LRU 鏈表操作

3、重填充 inactive_list

4、 從 LRU 鏈表回收頁面

5、收縮所有高速緩存

(1)shrink_caches

(2)try_to_free_pages

(3)try_to_free_pages_zone

// mm/vmscan.c
int try_to_free_pages_zone(zone_t *classzone, unsigned int gfp_mask)
{
	int priority = DEF_PRIORITY;
	int nr_pages = SWAP_CLUSTER_MAX;

	gfp_mask = pf_gfp_mask(gfp_mask);
	do {
		nr_pages = shrink_caches(classzone, priority, gfp_mask, nr_pages);
		if (nr_pages <= 0)
			return 1;
	} while (--priority);

	/*
	 * Hmm.. Cache shrink failed - time to kill something?
	 * Mhwahahhaha! This is the part I really like. Giggle.
	 */
	out_of_memory();
	return 0;
}

6、換出進程頁面

7、頁面交換守護程序

符號

? ?
? ? ? ? ? ?
①②③④⑤⑥⑦⑧⑨⑩????????????????????????????????????????
⑴⑵⑶⑷⑸⑹⑺⑻⑼⑽⑿⒀⒁⒂⒃⒄⒅⒆⒇
????????????????????
??????????????????????????
??????????????????????????
??????????????????????????
????????????????????????????????????????????????????

123 文章來源地址http://www.zghlxwxcb.cn/news/detail-475883.html

到了這里,關(guān)于深入理解Linux虛擬內(nèi)存管理(六)的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!

本文來自互聯(lián)網(wǎng)用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權(quán),不承擔相關(guān)法律責任。如若轉(zhuǎn)載,請注明出處: 如若內(nèi)容造成侵權(quán)/違法違規(guī)/事實不符,請點擊違法舉報進行投訴反饋,一經(jīng)查實,立即刪除!

領(lǐng)支付寶紅包贊助服務器費用

相關(guān)文章

  • 深入理解Linux內(nèi)核——內(nèi)存管理(4)——伙伴系統(tǒng)(1)

    深入理解Linux內(nèi)核——內(nèi)存管理(4)——伙伴系統(tǒng)(1)

    提要:本系列文章主要參考 MIT 6.828課程 以及兩本書籍 《深入理解Linux內(nèi)核》 《深入Linux內(nèi)核架構(gòu)》 對Linux內(nèi)核內(nèi)容進行總結(jié)。 內(nèi)存管理的實現(xiàn)覆蓋了多個領(lǐng)域: 內(nèi)存中的物理內(nèi)存頁的管理 分配大塊內(nèi)存的伙伴系統(tǒng) 分配較小內(nèi)存的slab、slub、slob分配器 分配非連續(xù)內(nèi)存塊的

    2024年02月10日
    瀏覽(22)
  • 深入理解Java虛擬機——內(nèi)存分配與回收策略

    深入理解Java虛擬機——內(nèi)存分配與回收策略

    在讀這篇博客之前,你需要了解分代收集理論中,收集器應該將Java堆劃分出不同的區(qū)域**,**然后將回收對象依據(jù)其年齡(年齡即對象熬過垃圾收集過程的次數(shù))分配到不同的區(qū)域之中存儲。 例如 appel式回收 ,HotSpot虛擬機中的新生代收集器都采用了appel式回收來設(shè)計新生代內(nèi)

    2024年02月04日
    瀏覽(19)
  • 《深入理解Java虛擬機》讀書筆記:內(nèi)存分配策略

    《深入理解Java虛擬機》讀書筆記:內(nèi)存分配策略

    Java技術(shù)體系中所提倡的自動內(nèi)存管理最終可以歸結(jié)為自動化地解決了兩個問題:給對象分配內(nèi)存以及回收分配給對象的內(nèi)存。關(guān)于回收內(nèi)存這一點,我們已經(jīng)使用了大量篇幅去介紹虛擬機中的垃圾收集器體系以及運作原理,現(xiàn)在我們再一起來探討一下給對象分配內(nèi)存的那點事

    2024年02月13日
    瀏覽(22)
  • 深入理解Java虛擬機(二)Java內(nèi)存區(qū)域與內(nèi)存溢出異常

    深入理解Java虛擬機(二)Java內(nèi)存區(qū)域與內(nèi)存溢出異常

    ????????對于Java程序員來說,在虛擬機自動內(nèi)存管理機制的幫助下,不再需要為每一個new操作去寫配對的delete/free代碼,不容易出現(xiàn)內(nèi)存泄漏和內(nèi)存溢出問題,看起來由虛擬機管理內(nèi)存一切都很美好。不過,也正是因為Java程序員把控制內(nèi)存的權(quán)力交給了Java虛擬機,一旦出

    2024年02月16日
    瀏覽(21)
  • 深入理解Java虛擬機jvm-對象的內(nèi)存布局

    深入理解Java虛擬機jvm-對象的內(nèi)存布局

    在HotSpot虛擬機里,對象在堆內(nèi)存中的存儲布局可以劃分為三個部分:對象頭(Header)、實例 數(shù)據(jù)(Instance Data)和對齊填充(Padding)。 HotSpot虛擬機對象的對象頭部分包括兩類信息。第一類是用于存儲對象自身的運行時數(shù)據(jù),如哈 希碼(HashCode)、GC分代年齡、鎖狀態(tài)標志、

    2024年02月09日
    瀏覽(17)
  • 《深入理解Java虛擬機》讀書筆記:Java內(nèi)存區(qū)域

    《深入理解Java虛擬機》讀書筆記:Java內(nèi)存區(qū)域

    Java內(nèi)存區(qū)域包含程序計數(shù)器、虛擬機棧、本地方法棧、Java堆、方法區(qū)五個區(qū)域。 運行時數(shù)據(jù)區(qū)分類 ? Java內(nèi)存區(qū)域 ? 程序計數(shù)器(Program Counter Register)是一塊較小的內(nèi)存空間,它可以看作是當前線程所執(zhí)行的字節(jié)碼的信號指示器。 字節(jié)碼解釋器工作時就是通過改變這個計數(shù)

    2024年02月14日
    瀏覽(30)
  • 《深入理解Java虛擬機》讀書筆記:內(nèi)存分配與回收策略

    《深入理解Java虛擬機》讀書筆記:內(nèi)存分配與回收策略

    Java技術(shù)體系中所提倡的自動內(nèi)存管理最終可以歸結(jié)為自動化地解決了兩個問題:給對象分配內(nèi)存以及回收分配給對象的內(nèi)存。關(guān)于回收內(nèi)存這一點,我們已經(jīng)使用了大量篇幅去介紹虛擬機中的垃圾收集器體系以及運作原理,現(xiàn)在我們再一起來探討一下給對象分配內(nèi)存的那點事

    2024年02月13日
    瀏覽(27)
  • 深入理解C++內(nèi)存管理

    深入理解C++內(nèi)存管理

    C++的高抽象層次,又兼具高性能,是其他語言所無法替代的,C++標準保持穩(wěn)定發(fā)展,更加現(xiàn)代化,更加強大。但在各種活躍編程語言中,C++門檻依然很高,尤其C++的內(nèi)存問題(內(nèi)存泄露,內(nèi)存溢出,內(nèi)存宕機,堆棧破壞等問題),需要理解C++標準對象模型,C++標準庫,標準

    2023年04月08日
    瀏覽(28)
  • 【深入理解C】動態(tài)內(nèi)存管理

    【深入理解C】動態(tài)內(nèi)存管理

    大家在編寫C程序的時候,是否會遇到數(shù)組空間不夠大,或者一次性就把空間開大了, 又怕造成空間開銷浪費等內(nèi)存大小相關(guān)問題不得其解的時候呢? 那么這一章內(nèi)容將會給你帶來解決這類問題的好辦法—— 動態(tài)內(nèi)存開辟 為什么要動態(tài)內(nèi)存分配 動態(tài)內(nèi)存函數(shù)的介紹 malloc與

    2023年04月08日
    瀏覽(21)
  • 深入理解C&C++內(nèi)存管理

    深入理解C&C++內(nèi)存管理

    我們先來看一下下面的代碼,分析一下對于每個變量的存儲位置: 【說明】 棧 又叫堆棧–非靜態(tài)局部變量/函數(shù)參數(shù)/返回值等等,棧是向下增長的。 內(nèi)存映射段 是高效的I/O映射方式,用于裝載一個共享的動態(tài)內(nèi)存庫。用戶可使用系統(tǒng)接口創(chuàng)建共享共享內(nèi)存,做進程間通信。

    2023年04月26日
    瀏覽(26)

覺得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請作者喝杯咖啡吧~博客贊助

支付寶掃一掃領(lǐng)取紅包,優(yōu)惠每天領(lǐng)

二維碼1

領(lǐng)取紅包

二維碼2

領(lǐng)紅包