深入理解Linux虚拟内存管理(六)

这篇具有很好参考价值的文章主要介绍了深入理解Linux虚拟内存管理(六)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

深入理解Linux虚拟内存管理(六)

系列文章目录


Linux 内核设计与实现
深入理解 Linux 内核
Linux 设备驱动程序
Linux设备驱动开发详解
深入理解Linux虚拟内存管理(一)
深入理解Linux虚拟内存管理(二)
深入理解Linux虚拟内存管理(三)
深入理解Linux虚拟内存管理(四)
深入理解Linux虚拟内存管理(五)
深入理解Linux虚拟内存管理(六)
深入理解Linux虚拟内存管理(七)
深入理解Linux虚拟内存管理(八)
深入理解Linux虚拟内存管理(九)



一、高端内存管理

1、映射高端内存页面

9.1 管理 PKMap 地址空间

(1)kmap

    这个 API 用于进行阻塞的调用者。

// include/asm-i386/highmem.h
// 核心函数 __kmap 的第2个参数表示调用者将进行阻塞。
#define kmap(page) __kmap(page, 0)

(2)kmap_nonblock

// include/asm-i386/highmem.h
// 核心函数 __kmap 的第2个参数表示调用者将不进行阻塞。
#define kmap_nonblock(page) __kmap(page, 1)

(3)__kmap

// include/asm-i386/highmem.h
static inline void *__kmap(struct page *page, int nonblocking)
{
// 这个函数不会在中断时调用,因为它已睡眠。out_of_line_bug()调用do_exit并
// 返回错误码来取代BUG()。之所以不调用BUG()是因为BUG()用极端方式杀掉进程,这将
// 引起如寓言 “Aiee,杀掉中断句柄! ” 般的内核瘫痪。
	if (in_interrupt())
		out_of_line_bug();
	// 如果页面已在低端内存中,则返回一个直接映射。
	if (page < highmem_start_page)
		return page_address(page);
	// 调用kmap_high()(见L1. 4小节)完成与体系结构无关的工作。
	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 数组。
	spin_lock(&kmap_lock);
	// 获取页面的虚拟地址。
	vaddr = (unsigned long) page->virtual;
	// 如果尚未映射,则调用map_new_virtual(),进行页面映射并返回其虚拟地址。
	// 如果失败,则跳转到 out 处,释放自旋锁并返回 NULL。
	if (!vaddr) {
		vaddr = map_new_virtual(page, nonblocking);
		if (!vaddr)
			goto out;
	}
	// 增加页面映射的引用计数。
	pkmap_count[PKMAP_NR(vaddr)]++;
	// 如果计数现在小于2,则这是个严重的bug。在实际运行中,严重的系统问题可能会引起这个bug。
	if (pkmap_count[PKMAP_NR(vaddr)] < 2)
		BUG();
 out:
 	// 释放 kmap_lock。
	spin_unlock(&kmap_lock);
	return (void*) vaddr;
}

(5)map_new_virtual

    这个函数分为 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 */
	// 持续扫描并等待直至有一个槽空闲。这里可能导致某些进程进入死循环。
	for (;;) {
	// last_pkmap_nr 是扫描中最后一个pkmap。为阻止搜索同一个页面,记录该值就可以
	// 进行循环搜索。如果达到LAST_PKMAP,则折返为0。
		last_pkmap_nr = (last_pkmap_nr + 1) & LAST_PKMAP_MASK;
	// 在 last_pkmap_nr 折返为 0 时,调用 flush_all_zero_pkmaps() (I. 1, 6 小节),在
	// 刷新TLB之前设置pkmap_count数组中所有的项从1改为0。接着重新设置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 */
	// 转到下一个下标,开始扫描。
		if (--count)
			continue;
	// 下一块代码将睡眠,等待一个槽变空闲。如果调用者要求不阻塞该函数,则直接返回
		if (nonblocking)
			return 0;

		/*
		 * Sleep for somebody else to unmap their entries
		 */
// 如果在扫描完所有页面一遍后仍没有可用槽,则在pkmap_map_wait队列上睡眠直至在
// 解除映射后被唤醒。
		{
	// 声明该等待队列。
			DECLARE_WAITQUEUE(wait, current);
	// 设置队列可中断,因为是在内核空间中睡眠。
			current->state = TASK_UNINTERRUPTIBLE;
	// 添加自己到pkmap_map_wait队列。
			add_wait_queue(&pkmap_map_wait, &wait);
	// 释放kmap_lock自旋锁。
			spin_unlock(&kmap_lock);
	// 调用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()增加引用计数。
			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数组的值为1。该计数在父函数中增加,如果这是第一次在该
	// 函数处出现,可以保证这是第一次映射。
	pkmap_count[last_pkmap_nr] = 1;
	// 设置页面的虚拟字段。
	page->virtual = (void *) vaddr;
	// 返回虚拟地址。
	return vaddr;
}

    其核心就是,如果 page 是一个高端页面,则把其映射到从 PKMAP_BASEFIXADDR_START 之间的虚拟地址。

(6)flush_all_zero_pkmaps

    这个函数循环遍历 pkmap_count 数组并在刷新 TLB 之前设置所有的项从 1 变为 0

// mm/highmem.c
static void flush_all_zero_pkmaps(void)
{
	int i;
	// 由于全局页表将变化,因此必须刷新所有处理器的CPU高速缓存。
	flush_cache_all();
	// 循环遍历整个pkmap_count数组。
	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设置为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、自动映射高端内存页面

    下面是 x86km_type 枚举类型的例子。这里列举了自动调用 kmap 的不同中断。由于 KM_TYPE_NR 是最后一个元素,所以它用作所有元素的计数器。

// 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 原子性的映射高端内存页面

    这是 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.
 */
// 这里的参数为映射的页面和处理所需的类型。一个槽的使用对应一个处理器。
static inline void *kmap_atomic(struct page *page, enum km_type type)
{
	enum fixed_addresses idx;
	unsigned long vaddr;
	// 如果页面在低端内存,则返回直接映射。
	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
	// 用于调试的代码。在实际运行中,PTE将退出。
	if (!pte_none(*(kmap_pte-idx)))
		out_of_line_bug();
#endif
	// 为保留的槽设置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()不能在中断中进行调用,因此自然退出。
	if (in_interrupt())
		out_of_line_bug();
	// 如果页面已在低端内存中,则不需要进行解除映射的操作。
	if (page < highmem_start_page)
		return;
	// 调用与体系结构无关的函数kunmap_high()。
	kunmap_high(page);
}

(2)kunmap_high

    这是与体系结构无关的 kunmap() 操作部分。

// mm/highmem.c
void kunmap_high(struct page *page)
{
	unsigned long vaddr;
	unsigned long nr;
	int need_wakeup;
	// 获取kmap锁, 用来保护虚拟字段和pkmap_count数组。
	spin_lock(&kmap_lock);
	// 获取虚拟页面
	vaddr = (unsigned long) page->virtual;
	// 如果没有设置虚拟字段,那这是两次解除映射的操作或是对没有映射页面的解
	// 除映射操作,则调用BUG。
	if (!vaddr)
		BUG();
	// 获取在pkmap_count数组中的下标。
	nr = PKMAP_NR(vaddr);

	/*
	 * A count must never go down to zero
	 * without a TLB flush!
	 */
	// 在缺省情况下,并不需要唤醒进程来调用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、自动解除高端内存页面映射

(1)kunmap_atomic

    整个函数是调试代码。其原因是页面只在此处自动映射,那么在解除映射以前只会在很小的范围且较短的时间内被使用。保留页面在那里是安全的,因为在解除映射之后不会再引用它们,而对同一个槽的另一个映射会简单地替换它。

// 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();
	// 如果提供的地址不在定长区域内,则返回。
	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
	 */
	// 现在解除页面映射,如果再次引用它,则会产生oops。
	pte_clear(kmap_pte-idx);
	__flush_tlb_one(vaddr);
#endif
}

5、弹性缓冲区

(1)创建弹性缓冲区

① create_bounce

9.5.2 创建弹性缓冲区

    函数调用图如图 9.3 所示。它是创建弹性缓冲区的高层函数。它由两部分组成,即分配必要的资源和从模板复制数据。

// mm/highmem.c
// 函数的参数如下所示:
//	rw 如果是写缓冲区,则设置为1。
//	bh_orig 是模板缓冲区头,是复制数据源。
struct buffer_head * create_bounce(int rw, struct buffer_head * bh_orig)
{
	struct page *page;
	struct buffer_head *bh;
	// 如果模板缓冲区头已在低端内存中,则简单地返回它。
	if (!PageHighMem(bh_orig->b_page))
		return bh_orig;
	// 从slab分配器分别缓冲区头,如果失败则从紧急池分配。
	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();
	// 将分配的页面与分配的缓冲区头关联起来。
	set_bh_page(bh, page, 0);

// 这块产生新的缓冲区头。
	// 逐个复制必要的信息,除了 b_list 字段,因为该缓冲区并不与该链表上的其他部分直
	// 接连接。
	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
// 仅用于调试的信息。
	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) {
// 如果这个缓冲区将被写入,则用于结束I/O的回调函数是bounce_end_io_write()
// (见L 5. 2.1节),它在设备接收所有信息后调用。由于数据在高端内存中,则从
// copy_from_high_bh() (见 I. 5. 2. 3)复制 “下来” 。
		bh->b_end_io = bounce_end_io_write;
		copy_from_high_bh(bh, bh_orig);
	} else
// 如果等待设备写数据到缓冲区中,则调用bounce_end_io_read() (见I. 5.2.2)
// 回调函数。
		bh->b_end_io = bounce_end_io_read;
// 从模板缓冲区头复制剩下的信息。
	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
	// 返回新弹性缓冲区
	return bh;
}
⑴ ⇒ alloc_bounce_bh

    alloc_bounce_bh 函数

⑵ ⇒ alloc_bounce_page

    alloc_bounce_page 函数

⑶ ⇔ 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 函数

⑸ ⇒ bounce_end_io_write

    bounce_end_io_write 函数

⑹ ⇒ bounce_end_io_read

    bounce_end_io_read 函数

② alloc_bounce_bh

    这个函数首先尝试从 slab 分配器分配一个 buffer_head ,如果失败,则使用紧急池。

// mm/highmem.c
struct buffer_head *alloc_bounce_bh (void)
{
	struct list_head *tmp;
	struct buffer_head *bh;
	// 尝试从slab分配器分配一个新buffer_head。 请注意,如何产生不使用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);
	// 如果池不空,则从链表取得一个缓冲区并减小nr_emergency_bhs计数器。
	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 */
	// 如果失败,则表示内存不足,那么补充池的惟一方法便是完成高端内存I/O操作。就
	// 这样,请求从tq_disk开始,然后写数据到磁盘,接着释放进程中适当的页面。
	run_task_queue(&tq_disk);
	// 出让处理器。
	yield();
	// 再次尝试从紧急池分配。
	goto repeat_alloc;
}
③ alloc_bounce_page

    这个函数本质上与 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.
	 */
	// 取得紧急池缓冲区头链表尾部。 	
	tmp = &emergency_pages;
	// 获取保护池的锁。
	spin_lock_irq(&emergency_lock);
	// 如果池不空,则从链表取得页面并减小可用nr_emergency_pages的计数。
	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)用弹性缓冲区复制

① bounce_end_io_write

    在弹性缓冲区用于向设备写数据而完成 I/O 操作时,调用该函数。由于缓冲区用于从高端内存复制数据到设备,除了回收资源没有其他更多的任务待处理。

// 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 函数

② bounce_end_io_read

    在数据从设备读出且待复制到高端内存时,调用该函数。由于在中断中调用,所以要更加谨慎。

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

	// 调用copy_to_high_bh_irq() 从弹性缓冲区复制数据并移至高端内存。
	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 函数

⑵ ⇒ bounce_end_io

    bounce_end_io 函数

③ copy_from_high_bh

    这个函数用于从高端内存 buffer_head 复制数据到弹性缓冲区。

// 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;
	// 映射高端内存页面到低端内存。该执行路径由IRQ安全锁io_request_lock保护,这
	// 样可以安全地调用kmap_atomic()(见I. 2.1小节)。
	vfrom = kmap_atomic(p_from, KM_USER0);
	// 复制数据。
	memcpy(to->b_data, vfrom + bh_offset(from), to->b_size);
	// 解除页面映射。
	kunmap_atomic(vfrom, KM_USER0);
}
⑴ ⇒ kmap_atomic

    kmap_atomic 函数

⑵ ⇒ kunmap_atomic

    kunmap_atomic 函数

④ copy_to_high_bh_irq

    在设备完成写数据到弹性缓冲区后,从中断中调用该函数。这个函数复制数据到高端内存。

// 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();
	// 映射高端内存页面到低端内存。
	vto = kmap_atomic(p_to, KM_BOUNCE_READ);
	// 复制数据。
	memcpy(vto + bh_offset(to), from->b_data, to->b_size);
	// 解除页面映射。
	kunmap_atomic(vto, KM_BOUNCE_READ);
	// 恢复中断标志位。
	__restore_flags(flags);
}
⑴ ⇒ kmap_atomic

    kmap_atomic 函数

⑵ ⇒ kunmap_atomic

    kunmap_atomic 函数

⑤ bounce_end_io

    这个函数回收由弹性缓冲区使用的资源。如果紧急池耗尽,则添加资源给它。

// 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 调用I/O完成的回调函数。
	bh_orig->b_end_io(bh_orig, uptodate);
	// 获取指向待释放的缓冲区页面的指针。
	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、 紧急池

    只有一个函数与紧急池相关,那就是其初始化函数。在系统启动时调用它,接着就删除它的代码,因为不再需要它。

(1)init_emergency_pool

    这个函数为紧急页面和紧急缓冲区头创建一个池。

// mm/highmem.c
static __init int init_emergency_pool(void)
{
	struct sysinfo i;
        si_meminfo(&i);
        si_swapinfo(&i);
 	// 如果没有可用的高端内存,则返回。       
        if (!i.totalhigh)
        	return 0;
	// 获取保护紧急池的锁。
	spin_lock_irq(&emergency_lock);
// 从伙伴分配器分配POOL_SIZE个页面并添加它们到链接表中。然后用
// nr_emergency_pages 记录紧急池中页面的数量。
	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记录池中缓冲区头的数量。
	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 文章来源地址https://www.toymoban.com/news/detail-475883.html

到了这里,关于深入理解Linux虚拟内存管理(六)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 深入理解Linux内核——内存管理(4)——伙伴系统(1)

    提要:本系列文章主要参考 MIT 6.828课程 以及两本书籍 《深入理解Linux内核》 《深入Linux内核架构》 对Linux内核内容进行总结。 内存管理的实现覆盖了多个领域: 内存中的物理内存页的管理 分配大块内存的伙伴系统 分配较小内存的slab、slub、slob分配器 分配非连续内存块的

    2024年02月10日
    浏览(52)
  • 深入理解Java虚拟机——内存分配与回收策略

    在读这篇博客之前,你需要了解分代收集理论中,收集器应该将Java堆划分出不同的区域**,**然后将回收对象依据其年龄(年龄即对象熬过垃圾收集过程的次数)分配到不同的区域之中存储。 例如 appel式回收 ,HotSpot虚拟机中的新生代收集器都采用了appel式回收来设计新生代内

    2024年02月04日
    浏览(46)
  • 《深入理解Java虚拟机》读书笔记:内存分配策略

    Java技术体系中所提倡的自动内存管理最终可以归结为自动化地解决了两个问题:给对象分配内存以及回收分配给对象的内存。关于回收内存这一点,我们已经使用了大量篇幅去介绍虚拟机中的垃圾收集器体系以及运作原理,现在我们再一起来探讨一下给对象分配内存的那点事

    2024年02月13日
    浏览(56)
  • 深入理解Java虚拟机(二)Java内存区域与内存溢出异常

            对于Java程序员来说,在虚拟机自动内存管理机制的帮助下,不再需要为每一个new操作去写配对的delete/free代码,不容易出现内存泄漏和内存溢出问题,看起来由虚拟机管理内存一切都很美好。不过,也正是因为Java程序员把控制内存的权力交给了Java虚拟机,一旦出

    2024年02月16日
    浏览(71)
  • 《深入理解Java虚拟机》读书笔记:Java内存区域

    Java内存区域包含程序计数器、虚拟机栈、本地方法栈、Java堆、方法区五个区域。 运行时数据区分类   Java内存区域   程序计数器(Program Counter Register)是一块较小的内存空间,它可以看作是当前线程所执行的字节码的信号指示器。 字节码解释器工作时就是通过改变这个计数

    2024年02月14日
    浏览(51)
  • 深入理解Java虚拟机jvm-对象的内存布局

    在HotSpot虚拟机里,对象在堆内存中的存储布局可以划分为三个部分:对象头(Header)、实例 数据(Instance Data)和对齐填充(Padding)。 HotSpot虚拟机对象的对象头部分包括两类信息。第一类是用于存储对象自身的运行时数据,如哈 希码(HashCode)、GC分代年龄、锁状态标志、

    2024年02月09日
    浏览(55)
  • 《深入理解Java虚拟机》读书笔记:内存分配与回收策略

    Java技术体系中所提倡的自动内存管理最终可以归结为自动化地解决了两个问题:给对象分配内存以及回收分配给对象的内存。关于回收内存这一点,我们已经使用了大量篇幅去介绍虚拟机中的垃圾收集器体系以及运作原理,现在我们再一起来探讨一下给对象分配内存的那点事

    2024年02月13日
    浏览(41)
  • 深入理解C++内存管理

    C++的高抽象层次,又兼具高性能,是其他语言所无法替代的,C++标准保持稳定发展,更加现代化,更加强大。但在各种活跃编程语言中,C++门槛依然很高,尤其C++的内存问题(内存泄露,内存溢出,内存宕机,堆栈破坏等问题),需要理解C++标准对象模型,C++标准库,标准

    2023年04月08日
    浏览(37)
  • 【深入理解C】动态内存管理

    大家在编写C程序的时候,是否会遇到数组空间不够大,或者一次性就把空间开大了, 又怕造成空间开销浪费等内存大小相关问题不得其解的时候呢? 那么这一章内容将会给你带来解决这类问题的好办法—— 动态内存开辟 为什么要动态内存分配 动态内存函数的介绍 malloc与

    2023年04月08日
    浏览(37)
  • 深入理解C&C++内存管理

    我们先来看一下下面的代码,分析一下对于每个变量的存储位置: 【说明】 栈 又叫堆栈–非静态局部变量/函数参数/返回值等等,栈是向下增长的。 内存映射段 是高效的I/O映射方式,用于装载一个共享的动态内存库。用户可使用系统接口创建共享共享内存,做进程间通信。

    2023年04月26日
    浏览(43)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包