linux内核源码分析之伙伴系统(四)

60 篇文章 0 订阅
60 篇文章 0 订阅

目录

内核映射

1,持久内核映射

查找地址?

解除映射

2,临时内存映射

3,没有高端内存


内核映射

尽管 vmalloc 函数族可用于从高端内存域向内核映射页帧(这些在内核空间中通常是无法直接看

到的),但这并不是这些函数的实际用途。重要的是强调以下事实:内核提供了其他函数用于将

ZONE_HIGHMEM 页帧显式映射到内核空间,这些函数与 vmalloc 机制无关

1,持久内核映射

如果需要将高端页帧长期映射(作为持久映射 )到内核地址空间中,必须使用 kmap 函数。需要映射的页用指向page 的指针指定,作为该函数的参数。

如果没有启用高端支持,该函数的任务就比较简单。在这种情况下,所有页都可以 直接访问 ,因此只需要返回页的地址,无需显式创建一个映射。

如果确实存在 高端页 ,情况会比较复杂。类似于vmalloc ,内核首先必须 建立高端页和所映射到的地址 之间的关联。还必须在虚拟地址空间中分配一个区域以映射页帧,最后,内核必须记录该虚拟区域的哪些部分在使用中,哪些仍然是空闲的。

数据结构:page_address_map

建立物理内存的page实例与其在虚拟内存区中位置之间的关联。

struct page_address_map {
	struct page *page;
	void *virtual;
	struct list_head list;
};
  • 建立page→virtual的映射
  • page是一个指向全局mem_map数组中的page实例的指针;
  • virtual指定了该页在内核虚拟地址空间中分配的位置。

查找地址

page_address首先检查传递进来的 page 实例在普通内存还是在高端内存。如果是前者,页地址可以根据page 在 mem_map 数组中的位置计算。对于后者,可通过上述散列表查找虚拟地址。

创建映射

为通过 page 指针建立映射,必须使用 kmap 函数。 ① 它只是一个前端,用于确认指定的页是否确实

在高端内存域中。否则,结果返回 page_address 得到的地址。如果确实在高端内存中,则内核将工作委托给kmap_high ,该函数定义如下:

void *kmap_high(struct page *page)
{
	unsigned long vaddr;

	lock_kmap();
	vaddr = (unsigned long)page_address(page);
	if (!vaddr)
		vaddr = map_new_virtual(page);
	pkmap_count[PKMAP_NR(vaddr)]++;
	BUG_ON(pkmap_count[PKMAP_NR(vaddr)] < 2);
	unlock_kmap();
	return (void*) vaddr;
}
  • page_address函数首先检查该页是否已经映射。如果它不对应到有效地址,则必须使用map_new_virtual映射该页。

  • 从最后使用的位置(保存在全局变量last_pkmap_nr中)开始,反向扫描pkmap_count数组,直至找到一个空闲位置。如果没有空闲位置,该函数进入睡眠状态,直至内核的另一部分执行解除映射操作腾出空位。

解除映射

用kmap 映射的页,如果不再需要,必须用 kunmap 解除映射

关键函数: flush_all_zero_pkmaps

static void flush_all_zero_pkmaps(void)
{
	int i;
	int need_flush = 0;

	flush_cache_kmaps();

	for (i = 0; i < LAST_PKMAP; i++) {
		struct page *page;

		if (pkmap_count[i] != 1)
			continue;
		pkmap_count[i] = 0;

		BUG_ON(pte_none(pkmap_page_table[i]));

		page = pte_page(pkmap_page_table[i]);
		pte_clear(&init_mm, PKMAP_ADDR(i), &pkmap_page_table[i]);

		set_page_address(page, NULL);
		need_flush = 1;
	}
	if (need_flush)
		flush_tlb_kernel_range(PKMAP_ADDR(0), PKMAP_ADDR(LAST_PKMAP));
}
  • flush_cache_kmaps在内核映射上执行刷出,因为内核的全局页表已经修改;

  • 扫描整个pkmap_count数组。计数器值为1的项设置为0,从页表删除相关的项,最后删除该 映射;

  • 最后,使用flush_tlb_kernel_range函数刷出所有与PKMAP区域相关的TLB项。


2,临时内存映射

kmap_atomic,其执行是原子的。该函数的一个主要优点是它比普通的kmap快速。但它不能用于可能进入睡眠的代码。因此,它对于很快就需要一个临时页的简短代码,是非常理想的。

void *kmap_atomic(struct page *page, enum km_type type)

3,没有高端内存

使用通用版本的kmap返回页的地址,且不修改虚拟内存。不能用于中断处理程序,如果pkmap数组中没有空闲位置,该函数会进入睡眠状态,直至情形有所改善。

void *kmap_high(struct page *page);
void kunmap_high(struct page *page);

static inline void *kmap(struct page *page)
{
	BUG_ON(in_interrupt());
	if (!PageHighMem(page))
		return page_address(page);
	return kmap_high(page);
}

static inline void kunmap(struct page *page)
{
	BUG_ON(in_interrupt());
	if (!PageHighMem(page))
		return;
	kunmap_high(page);
}

void *kmap_atomic(struct page *page);
void __kunmap_atomic(void *kvaddr);

参考

《深入Linux内核架构》

剖析Linux内核物理内存管理-大学生教程-腾讯课堂 (qq.com)


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值