永久内核映射

内存就像墙上的气球,32体系就好比是小孩,64位体系好比是大人。对于位置比较低的气球,抬抬脚就可以够到,这些气球相当于DMA内存(可用于设备直接内存访问);位置再高一点的气球,小孩伸手可以够到,这些气球相当于NORMAL内存;位置再高一点的气球,需要大人抱着才可以够到(相当于内存需要映射),这些气球相当于HIGH内存。对大人来说,所有的气球都可以够到,就相当于不存在HIGH区内存。

永久内核映射允许内核建立高端页框到内核地址空间的长期映射。主内核页表中一个专门的页表(其地址保存在pkmap_page_table变量中),记录用于永久内核映射的页框。对于32位体系(PAE未被激活的情况下),一个地址占4字节,一个页表可保存1024个页框,每个页框4K,因此可以映射4M的高端内存。

下图中,左边三个列表中的数据是一一对应的;pkmap_count列表中括号中的数字,是其计数。

图1

pkmap_count用于记录每个页表项的使用计数:

计数为0:对应页表项没有映射任何高端内存页框

计数为1:对应页表项映射了高端内存页框,页框没有被引用

计数大于1:相应页表项映射了1个高端内存页框,有n-1个地方引用了此页框

计数0和1的区别,可以看下flush_all_zero_pkmaps的实现。

永久内核映射的典型用法如下:

static int igb_check_lbtest_frame(struct igb_rx_buffer *rx_buffer,
				  unsigned int frame_size)
{
	unsigned char *data;
	bool match = true;

	frame_size >>= 1;

    // 如果page已经映射,返回对应的线性内存地址
    // 如果page没有映射,则进行映射,并返回对应的线性内存地址
	data = kmap(rx_buffer->page);

    // 对线性内存进程处理
	if (data[3] != 0xFF ||
	    data[frame_size + 10] != 0xBE ||
	    data[frame_size + 12] != 0xAF)
		match = false;

    // 取消映射
	kunmap(rx_buffer->page);

	return match;
}

 以气球讲解下kmap,就是:

对于小孩,可以够到的气球,自己来够,自己够不到的气球,让大人抱着来够;对于大人,可以够到所有的气球。

由于受硬件的限制,以x86为例,每个内存节点(用pg_data_t表示)的物理内存划分为3个管理区(用zone表示):

ZONE_DMA:低于(包含)16M的内存页框

ZONE_NORMAL: 32位体系-高于16M且低于896M的内存页框;64位体系-高于16M的内存页框

ZONE_HIGHMEM:32位体系-大于等于896M的内存页框;64位体系ZONE_HIGHMEM总是空的

一 kmap

kmap对高端内存和非高端内存,有不同的处理方式:非高端内存,cpu可以直接访问其物理内存,调用page_address,返回页框对应的线性地址;高端内存,cpu不能直接访问其物理内存,需要通过kmap_high进行映射后,cpu才可以访问。

void *kmap(struct page *page)
{
	might_sleep();
	if (!PageHighMem(page))
		return page_address( page);
	return kmap_high(page);
}

二 page_address

对于非高端内存,page_address大体实现逻辑如下:

__va((unsigned long)(page - mem_map) << 12)

其中page-mem_map用于计算页框的下标。

三 kmap_high

kmap_high用来实现映射的具体逻辑

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)
		// 映射页框
		vaddr = map_new_virtual(page);
    // 页框引用计数+1
	pkmap_count[PKMAP_NR(vaddr)]++;
	BUG_ON(pkmap_count[PKMAP_NR(vaddr)] < 2);
	unlock_kmap();
	return (void*) vaddr;
}

 映射的具体逻辑,在map_new_virtual中


static inline unsigned long map_new_virtual(struct page *page)
{
	unsigned long vaddr;
	int count;
	unsigned int last_pkmap_nr;
	unsigned int color = get_pkmap_color(page);

start:
	count = get_pkmap_entries_count(color);
	/* Find an empty entry */
	for (;;) {
		last_pkmap_nr = get_next_pkmap_nr(color); // last_pkmap_nr上次使用页表项的索引
		if (no_more_pkmaps(last_pkmap_nr, color)) { // last_pkmap_nr 等于 0
			flush_all_zero_pkmaps();
			count = get_pkmap_entries_count(color);
		}

        // 第last_pkmap_nr个页表项的计数为0,即没有映射页框
		if (!pkmap_count[last_pkmap_nr])
			break;	/* Found a usable entry */
		if (--count)
			continue;

		// count减到0
		/*
		 * Sleep for somebody else to unmap their entries
		 */
		{
			DECLARE_WAITQUEUE(wait, current);
			wait_queue_head_t *pkmap_map_wait =
				get_pkmap_wait_queue_head(color);

			__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 = PKMAP_ADDR(last_pkmap_nr);

    // 设置第last_pkmap_nr个页表项的页框
	set_pte_at(&init_mm, vaddr,
		   &(pkmap_page_table[last_pkmap_nr]), mk_pte(page, kmap_prot));

    // 第last_pkmap_nr个页表项已映射了页框,页框未被引用,将计数置为1
	pkmap_count[last_pkmap_nr] = 1;
	set_page_address(page, (void *)vaddr);

	return vaddr;
}

四 igb_check_lbtest_frame中kmap的执行逻辑

现在回过头来,再看下igb_check_lbtest_frame中kmap的逻辑

1. 如果rx_buffer->page不在高端内存区,直接返回其线性地址

2. 如果rx_buffer->page在高端内存区,已建立映射,将pkmap_count(图1中左侧第2列)相应计数+1

3. 如果rx_buffer->page在高端内存区,尚未建立映射,在pkmap_count中查找一个空闲位置(计数为0),将页框(pkmap_count)添加到高端内存映射页表(图1右侧第2列)对应项中,pkmap_count中对应计数置为1,因为页框已被引用,pkmap_count计数再加1

五 永久内核映射线性地址

可以参考下图1中左侧列表

永久内核映射线性地址范围为[PKMAP_BASE, FIXADDR_START),即永久内核映射页表中第1项的线性地址为PKMAP_BASE,第2项的线性地址为PKMAP_BASE+4k,以此类推。

  • 24
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值