临时内核映射

本文详细介绍了临时内核映射与永久映射在Linux内核中的工作原理,包括计数机制、线性地址生成以及kmap_atomic函数的用法。重点讨论了__kmap_atomic_idx和km_type_nr在内存映射中的关键作用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

临时内核映射与永久内核映射的区别是,临时内核映射可以在中断处理程序和可延迟函数内部使用,它不堵塞当前进程。

一 原理介绍

临时内核映射的线性地址在永久内核映射的后面,范围是[FIXADDR_START, FIXADDR_TOP),其基本逻辑是获取一个计数值n,用FIXADDR_TOP - n*4k 得到映射的线性地址。n不同,得到的线性地址也就不相同,内核是如何确保不同线程得到的n值不同的呢。

有两个措施:

1. 每cpu变量__kmap_atomic_idx (见kmap_atomic_idx_push函数)

对于每cpu变量__kmap_atomic_idx,每个cpu都维护着一个计数,比如cpu1保存的计数是3,而cpu2保存的计数是5。映射高端内存时,将计数加1,取消映射时,将计数减1。

假如一个线程运行在cpu1上,映射内存时计数加1,__kmap_atomic_idx的值变为4;另一个线程运行在cpu2上,映射内存后计数加1,__kmap_atomic_idx的值为6。

2. 每个cpu有KM_TYPE_NR个固定映射的线性地址

KM_TYPE_NR的值为20,即[0, 20),为第一个cpu的取值范围,[20, 40)为第二个cpu的取值范围,以此类推。

idx = type + KM_TYPE_NR*smp_processor_id();

 上面的逻辑可以参考下图

图1

二 临时内核映射代码 

有了上面的介绍后,下面看下临时内核映射的代码,kmap_atomic

static inline int kmap_atomic_idx_push(void)
{
	int idx = __this_cpu_inc_return(__kmap_atomic_idx) - 1;

	return idx;
}

void *kmap_atomic_prot(struct page *page, pgprot_t prot)
{
	unsigned long vaddr;
	int idx, type;

	preempt_disable();
	pagefault_disable();

	// 非高端内存,直接返回其线性地址
	if (!PageHighMem(page))
		return page_address(page);

	// 获取每cpu变量__kmap_atomic_idx的值,并将值加1
	type = kmap_atomic_idx_push();
	idx = type + KM_TYPE_NR*smp_processor_id();
	vaddr = __fix_to_virt(FIX_KMAP_BEGIN + idx);
	BUG_ON(!pte_none(*(kmap_pte-idx)));
    // 将页框添加到页表中
	set_pte(kmap_pte-idx, mk_pte(page, prot));
	arch_flush_lazy_mmu_mode();

	return (void *)vaddr;
}

void *kmap_atomic(struct page *page)
{
	return kmap_atomic_prot(page, kmap_prot);
}

kmap_atomic_prot中的idx即经过前面提到的两个措施后,得到的值。

获取线性地址vaddr时,FIX_KMAP_BEGIN是个枚举值,可参考enum fixed_addresses。

vaddr = __fix_to_virt(FIX_KMAP_BEGIN + idx);

三 __fix_to_virt

__fix_to_virt的作用是将固定映射的一个值转成线性地址,其定义如下:

#define __fix_to_virt(x) (FIXADDR_TOP - ((x) << PAGE_SHIFT))

32位系统地址空间如下图所示:

图2 

对于32位系统,FIXADDR_TOP为0xFFFFF000,即地址空间最后一个4K的起始地址

对于64位系统,FIXADDR_TOP为0xFFFFFFFF FF600000,也在线性地址空间最后的位置

x为0时, __fix_to_virt得到临时内核映射线性区最后一页的线性地址;x为1时,得到倒数第二页的线性地址,以此类推。

四 kmap_pte

kmap_pte的作用,与永久内核映射中的pkmap_page_table作用的一样的,也是指向一个页表。与pkmap_page_table不同的是,kmap_pte指向的是临时映射所用页表的结束位置。

set_pte(kmap_pte-idx, mk_pte(page, prot));

kmap_pte在kmap_init中初始化

static void __init kmap_init(void)
{
	unsigned long kmap_vstart;

	/*
	 * Cache the first kmap pte:
	 */
	kmap_vstart = __fix_to_virt(FIX_KMAP_BEGIN);
	kmap_pte = kmap_get_fixmap_pte(kmap_vstart);
}

图3

kmap_vstart距离FIXADDR_TOP有 FIX_KMAP_BEGIN * 4K的,如上图所示,kmap_pte为kmap_vstart所在的页表。

五 kmap_get_fixmap_pte

调用kmap_get_fixmap_pte将kmap_vstart转换成kmap_pte。

static inline pte_t *kmap_get_fixmap_pte(unsigned long vaddr)
{
	pgd_t *pgd = pgd_offset_k(vaddr); // 页全局目录
	p4d_t *p4d = p4d_offset(pgd, vaddr);
	pud_t *pud = pud_offset(p4d, vaddr); // 页上级目录
	pmd_t *pmd = pmd_offset(pud, vaddr); // 页中间目录
	return pte_offset_kernel(pmd, vaddr); // 页表
}

六 kmap_atomic在内核中的应用

在一个函数中只调用一次kmap_atomic,如下所示:

void read_inline_data(struct page *page, struct page *ipage)
{
	struct inode *inode = page->mapping->host;
	void *src_addr, *dst_addr;

	if (PageUptodate(page))
		return;

	f2fs_bug_on(F2FS_P_SB(page), page->index);

	zero_user_segment(page, MAX_INLINE_DATA(inode), PAGE_SIZE);

	/* Copy the whole inline data block */
	src_addr = inline_data_addr(inode, ipage);
    // 映射
	dst_addr = kmap_atomic(page);
	memcpy(dst_addr, src_addr, MAX_INLINE_DATA(inode));
	flush_dcache_page(page);
    // 取消映射
	kunmap_atomic(dst_addr);
	if (!PageUptodate(page))
		SetPageUptodate(page);
}

映射后得到线性地址dst_addr,对线性地址进行操作,最后取消映射。

在一个函数中调用多次 kmap_atomic,看下面代码:

void copy_user_highpage(struct page *to, struct page *from,
	unsigned long u_vaddr, struct vm_area_struct *vma)
{
	void *kfrom = kmap_atomic(from);
	void *kto = kmap_atomic(to);
	int clean_src_k_mappings = 0;

	... ...

	kunmap_atomic(kto);
	kunmap_atomic(kfrom);
}

先映射from页框得到线性地址kfrom,再映射页框to得到线性地址kto。每调用一次kmap_atomic,运行此段代码的cpu保存的__kmap_atomic_idx就加1,因此最后得到的线性地址kfrom和ktto就不相同。

线性地址使用完毕后,调用kunmap_atomic取消映射,对应cpu保存的__kmap_atomic_idx的值也减1。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值