内存就像墙上的气球,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,以此类推。