与高端映射对立的是低端映射或所谓直接映射,内核中有关变量定义它们的它们的分界点,全局变量high_memory,该变量定义在mm/memory.c文件中(存在MMU的前提下),可见不区分体系结构,对于当前我手头的marvell的arm设备即对于arm体系结构,high_memory在初始化阶段的创建内存页表时初始化值,它的值就是:物理内存最后一个node的末尾,比如物理内存只有一个node,大小是256MB,再根据如下的算法就可以得出high_memory是多少:
high_memory = __va((max_low << PAGE_SHIFT) - 1) + 1;
max_low代表的是当前node的在物理内存中的物理页地址,比如物理内存从0x0开始(由PHYS_OFFSET决定),大小是256MB,即65536(0x10000)个物理页,那么max_low的值为0x10000,则high_memory的值为该物理页地址转为物理地址再转为虚拟地址的结果:0xd0000000。
high_memory之上就是高端内存的范围,这样的说法也不一定对,比如对于有的体系结构如arm,它的永久映射实际上就在high_memory之下的地方,但它依然是高端内存,所有物理内存都在初始化时映射在低端空间也是不一定正确的(这个可以在初始化时内存映射中发现,哪样的物理内存是会属于HIGHMEM区),所以我想通常意义的高端内存可以基本上定义为“不能直接通过偏移实现虚拟地址和物理地址映射”的虚拟空间、而“可以直接通过偏移实现虚拟地址和物理地址映射”的虚拟空间是低端内存(为什么低端映射也叫直接映射,这里体现出了直接的感觉)这样的方式界定比较好一些。
大体上内核有3种高端映射的机制,分别是永久映射、临时映射、非连续内存分配映射,
一、非连续内存分配(vmalloc):
1.1、vmalloc原理:
前面的文章细致描述过内核是如何通过slab/buddy获取大大小小的连续的物理内存的,它们是Linux在低端虚拟地址空间的分配机制,使用连续物理内存是有很大好处的,对于充分利用CPU的cache有极大的利好;此外buddy和slab分别尽力的避免了物理内存空间的外碎片和内碎片;
只是有些时候比如我们的物理内存本身就不大,随着运行时间增长,物理内存的碎片还是可能会越来越多,分配连续的物理内存尤其是大尺寸连续的物理内存将越来越费劲;
为了尽可能避免这种情况或者在出现这种情况下能够缓解进一步费劲,对于某些不频繁的分配释放的内存申请,可以采用一种方式,即所谓的不连续内存分配如下图:
PAGE_OFFSET |
high_memory |
VMALLOC_START |
Vmalloc 1 |
4KB隔离带 |
Vmalloc 2 |
4KB隔离带 |
Vmalloc 3 |
4KB隔离带 |
…….. |
VMALLOC_END |
PKMAP_BASE |
8MB隔离带 |
Vmalloc N |
8KB隔离带 |
低端(物理)内存映射 |
FIXADDR_START |
永久映射区 |
临时映射区 |
通过上图,不连续内存映射空间即vmalloc区在4GB地址空间的什么位置应该很清楚了,现在描述下所谓不连续内存映射空间是什么意思:
不连续内存映射,不连续是指物理内存不连续,后面描述它的具体实现就能很清楚的发现它为什么是不连续,这里直接知道结果就是它映射物理地址的方式是一页一页映射的,比如需要映射10页物理地址空间,那么它是10次调用alloc_page从buddy获取,这样就会存在物理地址不连续的可能,之所以这样做就是如上面所说,某些东西申请内存后不会频繁的访问,并且它的长度如果不是很大的话,那么它适合vmalloc,因为分配一个连续的很大的物理地址是越来越困难的;
但是它的虚拟地址是连续的,如上面的图所示,每一个vmalloc X就是一个vmalloc映射,显然虚拟地址是连续的,只是它映射的物理地址是不一定连续的;
看到这里,就会明白为什么vmalloc需要增加内存页表的表项了,这都是初始化时没有的映射关系,而且确切的说是需要二级映射的,因为虚拟地址和物理地址之间都是一页一页映射的,可见vmalloc申请起来比较麻烦;另外,虚拟地址和物理地址之间都是一页一页映射,说明最好在申请的时候也按页的整数倍即对齐值申请。
那么,宏VMALLOC_START和VMALLOC_END是怎么定义的呢?如下:
#define VMALLOC_START (((unsigned long)high_memory + VMALLOC_OFFSET) & ~(VMALLOC_OFFSET-1))
#define VMALLOC_END (PAGE_OFFSET + 0x30000000)
即vmalloc区始于high_memory加8MB的位置,结束于一个固定位置为0xF0000000;
最后看看到底哪些场合使用vmalloc:
1、swap;2、为module分配空间,见函数module_alloc就明白了;3、为IO驱动程序分配缓冲区(ioremap);
1.2、vmalloc实现:
1、接口函数:
接口函数就是vmalloc和__vmalloc,它俩的区别就是vmalloc只需指定长度即可,而__vmalloc是由调用者自行指定gfpmask和prot参数,prot参数一般都是PAGE_KERNEL,gfpmask参数一般就是GFP_KERNEL | __GFP_HIGHMEM,调用__vmalloc可能会加上标志GFP_ZERO用于清零,它们都调用函数__vmalloc_node;此外还有个用于只申请一段vmalloc区间但不映射物理地址的接口函数get_vm_area;
2、具体实现:
static void *__vmalloc_node(unsigned long size, unsigned long align,
gfp_t gfp_mask, pgprot_t prot,
int node, void *caller)
{
struct vm_struct *area;
void *addr;
unsigned long real_size = size;
/*size 页对齐,因为vmalloc映射的物理内存不连续,所以是一页一页的映射,
即映射的物理内存大小必然是页的倍数,所以必须页对齐*/
size = PAGE_ALIGN(size);
/*检查size正确性,不能为0且不能大于totalram_pages,
totalram_pages是bootmem分配器移交给伙伴系统的物理内存页数总和*/
if (!size || (size >> PAGE_SHIFT) > totalram_pages)
return NULL;
/*申请一个vm_struct插入vmlist链表,申请一个vmap_area并插入红黑树
完成非连续内存区的高端虚拟地址分配,注意size总会额外在最后加一页,用于安全区(上图的4KB隔离带)
注意: vm_struct本身是使用kmalloc_node()在slab,所以在低端内存中;
而函数alloc_vmap_area真正分配了连续的高端虚拟地址
简单的总结: 分配一个vm_struct结构,获取对应长度(注意额外加一页)高端连续地址,最终插入vmlist链表*/
area = __get_vm_area_node(size, align, VM_ALLOC, VMALLOC_START,
VMALLOC_END, node, gfp_mask, caller);
if (!area)
return NULL;
/*本函数实际的给虚拟地址映射了不连续的物理内存(调用函数alloc_page一页一页的分配物理地址,函数map_vm_area实现映射)
返回值是分配的高端虚拟地址的起始*/
addr = __vmalloc_area_node(area, gfp_mask, prot, node, caller);
/*
* A ref_count = 3 is needed because the vm_struct and vmap_area
* structures allocated in the __get_vm_area_node() function contain
* references to the virtual address of the vmalloc’ed block.
*/
kmemleak_alloc(addr, real_size, 3, gfp_mask);
/*返回值是分配的高端虚拟地址的起始*/
return addr;
}
主要就是两大部分:分配高端虚拟地址(即分配一段vmalloc区间) + 给虚拟地址映射物理地址;
1.2.1、高端地址分配:
进入函数__get_vm_area_node:
static struct vm_struct *__get_vm_area_node(unsigned long size,
unsigned long align, unsigned long flags, unsigned long start,
unsigned long end, int node, gfp_t gfp_mask, void *caller)
{
static struct vmap_area *va;
struct vm_struct *area;
BUG_ON(in_interrupt());
if (flags & VM_IOREMAP) {
int bit = fls(size);
if (bit > IOREMAP_MAX_ORDER)
bit = IOREMAP_MAX_ORDER;
else if (bit < PAGE_SHIFT)
bit = PAGE_SHIFT;
align = 1ul << bit;
}
size = PAGE_ALIGN(size);
if (unlikely(!size))
return NULL;
/*申请一个vm_struct,本质还是通过kmalloc申请,申请的是高端的虚拟内存
kmalloc可保证虚拟内存的连续性,这验证了vmalloc申请的虚拟地址是连续的
本质就是: 使用kmalloc_node()在slab中,分配一个vm_struct结构*/
area = kzalloc_node(sizeof(*area), gfp_mask & GFP_RECLAIM_MASK, node);
if (unlikely(!area))
return NULL;
/*
* We always allocate a guard page.
*/
/*vmalloc总是要将size加上一个页框的大小作为安全区*/
size += PAGE_SIZE;
/*在start到end中,分配足够size大小的内核虚拟空间*/
/*注意: vmap_area结构体(返回值va)本身也是通过kmalloc分配,所以也在低端内存中,
它的成员va_start和va_end指示了真正申请的高端虚拟内存的地址范围,可见是线性的(连续的)
[va_start—va_end]落在高端内存的非连续映射区(vmalloc区)中,va_end - va_start = size = 实际需要映射长度 + 4KB(安全区)
寻找新节点在红黑树的插入点并计算出应该的高端地址值(addr),关于红黑树,细节暂不讨论留在后续
将最终的高端地址值赋给va,并插入红黑树中*/
va = alloc_vmap_area(size, align, start, end, node, gfp_mask);
if (IS_ERR(va)) {
kfree(area);
return NULL;
}
/*将va的值(高端地址起始和长度)赋给area,最终把area插入vmlist链表*/
insert_vmalloc_vm(area, va, flags, caller);
/*这里area已经被赋值的成员有,addr和size(高端地址)、flag、caller*/
return area;
}
首先注意结构体vm_struct,它是vmalloc的管理方法非常重要:
struct vm_struct {
struct vm_struct *next; /*指向下一个vm区域*/
void *addr; /*指向第一个内存单元(线性地址)*/
unsigned long size; /*该块内存区的大小*/
unsigned long flags; /*内存类型的标识字段*/
struct page **pages; /*指向页描述符指针数组*/
unsigned int nr_pages; /*内存区大小对应的页框数*/
unsigned long phys_addr; /*用来映射硬件设备的IO共享内存,其他情况下为0*/
void *caller; /*调用vmalloc类的函数的返回地址*/
};
全局变量vmlist是管理所有vmalloc对象的链表表头,每个vmalloc映射都要把它的映射结果即一个struct vm_struct型的描述符加入链表中,成员next用于这个链表;addr指示这段vmalloc区的虚拟地址起始;size标识这段vmalloc区的长度;flags标识映射方式,在include/linux/vmalloc.h文件中有明确的使用方式,像在__vmalloc_node调用就是VM_ALLOC:
#define VM_IOREMAP 0x00000001 /* ioremap() and friends */
#define VM_ALLOC 0x00000002 /* vmalloc() */
#define VM_MAP 0x00000004 /* vmap()ed pages */
#define VM_USERMAP 0x00000008 /* suitable for remap_vmalloc_range */
#define VM_VPAGES 0x00000010 /* buffer for pages was vmalloc’ed */
成员pages是一个数组,每个成员都是所映射的物理页的page描述符地址;nr_pages标识所映射的物理页,注意它不包括一页的隔离带;phys_addr用来映射硬件设备的IO共享内存,其他情况下为0;caller是调用vmalloc类的函数的返回地址,它是用于调试和找问题的比如可以通过proc下的vmallocinfo看是哪个函数在申请高端虚拟内存;
对struct vm_struct有了个大概认识后可以具体看源码,首先通过kmalloc申请一个struct vm_struct结构变量area,注意这个结构变量本身是在低端连续内存区,然后调用函数alloc_vmap_area,这个比较重要如下:
static struct vmap_area *alloc_vmap_area(unsigned long size,
unsigned long align,
unsigned long vstart, unsigned long vend,
int node, gfp_t gfp_mask)
{
struct vmap_area *va;
struct rb_node *n;
unsigned long addr;
int purged = 0;
BUG_ON(!size);
BUG_ON(size & ~PAGE_MASK);
/*vmap_area结构体本身也是通过kmalloc分配,所以也在低端内存中*/
va = kmalloc_node(sizeof(struct vmap_area),
gfp_mask & GFP_RECLAIM_MASK, node);
if (unlikely(!va))
return ERR_PTR(-ENOMEM);
/*下面是寻找新节点在红黑树的插入点并计算出应该的高端地址值(addr),关于红黑树,细节暂不讨论留在后续*/
retry:
addr = ALIGN(vstart, align);
spin_lock(&vmap_area_lock);
if (addr + size - 1 < addr)
goto overflow;
/* XXX: could have a last_hole cache */
n = vmap_area_root.rb_node;
if (n) {
struct vmap_area *first = NULL;
do {
struct vmap_area *tmp;
tmp = rb_entry(n, struct vmap_area, rb_node);
if (tmp->va_end >= addr) {
if (!first && tmp->va_start < addr + size)
first = tmp;
n = n->rb_left;
} else {
first = tmp;
n = n->rb_right;
}
} while (n);
if (!first)
goto found;
if (first->va_end < addr) {
n = rb_next(&first->rb_node);
if (n)
first = rb_entry(n, struct vmap_area, rb_node);
else
goto found;
}
while (addr + size > first->va_start && addr + size <= vend) {
addr = ALIGN(first->va_end + PAGE_SIZE, align);
if (addr + size - 1 < addr)
goto overflow;
n = rb_next(&first->rb_node);
if (n)
first = rb_entry(n, struct vmap_area, rb_node);
else
goto found;
}
}
found:
if (addr + size > vend) {
overflow:
spin_unlock(&vmap_area_lock);
if (!purged) {
purge_vmap_area_lazy();
purged = 1;
goto retry;
}
if (printk_ratelimit())
printk(KERN_WARNING
“vmap allocation for size %lu failed: “
“use vmalloc=<size> to increase size.\n”, size);
kfree(va);
return ERR_PTR(-EBUSY);
}
BUG_ON(addr & (align-1));
/*将最终的高端地址值赋给va,并插入红黑树中*/
va->va_start = addr;
va->va_end = addr + size;
va->flags = 0;
__insert_vmap_area(va);
spin_unlock(&vmap_area_lock);
return va;
}
这个函数alloc_vmap_area作用就是根据所要申请的高端地址的长度size(注意这里的size已经是加上一页隔离带的size),在vmalloc区找到一个合适的区间并把起始虚拟地址和结尾地址通知给内核,具体说来还包括struct vmap_area的问题,它是实际维护vmalloc信息的数据结构,比较复杂,linux内核维护vmalloc信息是通过红黑树算法(一种特殊的平衡二叉树,增删查改效率高)实现,这个东西比较麻烦一些,后续专门讨论它,但不了解它不影响对vmalloc管理的分析,这里知道alloc_vmap_area函数的最终作用是得到被分配的高端虚拟地址起始和结尾地址即可;
然后最终调用函数insert_vmalloc_vm把这个vm_struct结构变量加入vmlist链表,源码不贴了就。
1.2.2、分配和映射物理地址:
这时还没有给这个vmalloc区分配和映射物理地址,现在调用函数__vmalloc_area_node:
static void *__vmalloc_area_node(struct vm_struct *area, gfp_t gfp_mask,
pgprot_t prot, int node, void *caller)
{
struct page **pages;
unsigned int nr_pages, array_size, i;
/*得到实际需要映射的页数(减去一页的安全区)*/
nr_pages = (area->size - PAGE_SIZE) >> PAGE_SHIFT;
/*并得到所需的空间(页数*page结构长度)*/
array_size = (nr_pages * sizeof(struct page *));
area->nr_pages = nr_pages;
/* Please note that the recursion is strictly bounded. */
/*不仅要映射的高端地址通过__get_vm_area_node分配高端地址,
提供映射的页指针也在高端地址分配,不足一页的话在低端地址中分配*/
if (array_size > PAGE_SIZE) {
pages = __vmalloc_node(array_size, 1, gfp_mask | __GFP_ZERO,
PAGE_KERNEL, node, caller);
area->flags |= VM_VPAGES;
} else {
pages = kmalloc_node(array_size,
(gfp_mask & GFP_RECLAIM_MASK) | __GFP_ZERO,
node);
}
/*将映射用的页表pages在分配到高端(不足一页在低端)地址后,赋给area*/
area->pages = pages;
area->caller = caller;
if (!area->pages) {
remove_vm_area(area->addr);
kfree(area);
return NULL;
}
/*从伙伴系统中进行物理内存页面的分配,注意是为每一个页面分配空间*/
for (i = 0; i < area->nr_pages; i++) {
struct page *page;
/*UMA系统*/
if (node < 0)
page = alloc_page(gfp_mask);
/*NUMA系统*/
else
page = alloc_pages_node(node, gfp_mask, 0);
if (unlikely(!page)) {
/* Successfully allocated i pages, free them in __vunmap() */
area->nr_pages = i;
goto fail;
}
/*将页表pages里的内容填充,填充的是一个一个的物理页地址*/
area->pages[i] = page;
}
/*area的addr和size代表了要映射的高端地址,pages里填充了实际被映射的物理页地址
接下来完成虚拟地址到物理地址的映射,注意最终是要创建二级映射(二级页表空间需从buddy申请,大小为1页)*/
if (map_vm_area(area, prot, &pages))
goto fail;
return area->addr;
fail:
vfree(area->addr);
return NULL;
}
首先实际需要映射的页数(注意不包含一页的隔离带),计算这个的目的是到页表所需的空间(页数*page结构长度),确切的说是二级页表所需的空间(从之前的文章可知道,二级映射的页表是动态创建的,一级页表即段页表是常驻内存),注意如果这个二级页表它所占的空间超出一页长度,那么也在vmalloc区里分配它,否则就在低端连续区分配即可;另外从编程角度看,这里递归了一下函数__vmalloc_node;
把二级页表地址和调用者成员赋值给area,接下来就开始分配物理空间了,这里可以清楚的看到是使用for循环一页一页分配的,这就是为什么物理地址不一定连续的原因;另外要注意,把这些物理页地址依次写进了二级页表表项,即“area->pages[i] = page”;
最后一个操作就是调用函数map_vm_area把分配的物理地址和高端虚拟地址做映射,
int map_vm_area(struct vm_struct *area, pgprot_t prot, struct page ***pages)
{
unsigned long addr = (unsigned long)area->addr;
unsigned long end = addr + area->size - PAGE_SIZE;
int err;
/*start和end代表了要映射的高端地址,pages里填充了实际被映射的物理页地址
注意最终是要在内核页表中创建二级映射*/
err = vmap_page_range(addr, end, prot, *pages);
if (err > 0) {
*pages += err;
err = 0;
}
return err;
}
注意都是二级映射,这里涉及了内存页表知识可以看之前的描述内存页表的那篇文章,arm的MMU只有二级映射,本函数前期基本相当于空跑即跳过linux的pud、pmd,直到函数vmap_pte_range开始创建二级映射;
1.3、vmalloc在proc:
在proc下有个vmallocinfo,可以查看当前vmalloc信息,就是通过上面说的vmlist链表查看每一个正在映射的vmalloc,打印出虚拟起始地址、虚拟结尾地址、映射长度、调用者、映射页数、映射方式,非常简单源码如下:
static int s_show(struct seq_file *m, void *p)
{
struct vm_struct *v = p;
seq_printf(m, “0x%p-0x%p %7ld”,
v->addr, v->addr + v->size, v->size);
if (v->caller) {
char buff[KSYM_SYMBOL_LEN];
seq_putc(m, ’ ‘);
sprint_symbol(buff, (unsigned long)v->caller);
seq_puts(m, buff);
}
if (v->nr_pages)
seq_printf(m, ” pages=%d”, v->nr_pages);
if (v->phys_addr)
seq_printf(m, ” phys=%lx”, v->phys_addr);
if (v->flags & VM_IOREMAP)
seq_printf(m, ” ioremap”);
if (v->flags & VM_ALLOC)
seq_printf(m, ” vmalloc”);
if (v->flags & VM_MAP)
seq_printf(m, ” vmap”);
if (v->flags & VM_USERMAP)
seq_printf(m, ” user”);
if (v->flags & VM_VPAGES)
seq_printf(m, ” vpages”);
show_numa_info(m, v);
seq_putc(m, ‘\n’);
return 0;
}
比如我的当前打印如下:
/ # cat proc/vmallocinfo
0xbf000000-0xbf0b3000 733184 module_alloc+0x54/0x60 pages=178 vmalloc
0xd085e000-0xd0860000 8192 __arm_ioremap_pfn+0x64/0x144 ioremap
0xd0861000-0xd0882000 135168 ubi_attach_mtd_dev+0x390/0x9c8 pages=32 vmalloc
0xd0883000-0xd08a4000 135168 ubi_attach_mtd_dev+0x3b0/0x9c8 pages=32 vmalloc
0xd08a5000-0xd08ac000 28672 ubi_read_volume_table+0x178/0x8cc pages=6 vmalloc
0xd08b6000-0xd08b8000 8192 __arm_ioremap_pfn+0x64/0x144 ioremap
0xd08ba000-0xd08bc000 8192 __arm_ioremap_pfn+0x64/0x144 ioremap
0xd08bd000-0xd08ce000 69632 lzo_init+0x18/0x30 pages=16 vmalloc
0xd08cf000-0xd0912000 274432 deflate_init+0x1c/0xe8 pages=66 vmalloc
0xd0913000-0xd0934000 135168 ubifs_get_sb+0x79c/0x1104 pages=32 vmalloc
0xd0935000-0xd0937000 8192 ubifs_lpt_init+0x30/0x428 pages=1 vmalloc
0xd095d000-0xd095f000 8192 ubifs_lpt_init+0x30/0x428 pages=1 vmalloc
0xd0960000-0xd0965000 20480 __arm_ioremap_pfn+0x64/0x144 ioremap
0xd0966000-0xd0987000 135168 ubi_attach_mtd_dev+0x390/0x9c8 pages=32 vmalloc
0xd0988000-0xd09a9000 135168 ubi_attach_mtd_dev+0x3b0/0x9c8 pages=32 vmalloc
0xd09aa000-0xd09b1000 28672 ubi_read_volume_table+0x178/0x8cc pages=6 vmalloc
0xd09ba000-0xd09db000 135168 ubifs_get_sb+0x79c/0x1104 pages=32 vmalloc
0xd09dc000-0xd09fd000 135168 ubifs_get_sb+0x7b8/0x1104 pages=32 vmalloc
0xd0a00000-0xd0b01000 1052672 __arm_ioremap_pfn+0x64/0x144 ioremap
0xd0bd0000-0xd0bd2000 8192 ubifs_lpt_init+0x220/0x428 pages=1 vmalloc
0xd0bd3000-0xd0bf4000 135168 ubifs_lpt_init+0x234/0x428 pages=32 vmalloc
0xd0bf5000-0xd0bf8000 12288 tpm_db_mod2_setup_jump_area+0x84/0x3cc pages=2 vmalloc
0xd0bf9000-0xd0bfb000 8192 tpm_db_mod2_setup_jump_area+0x100/0x3cc pages=1 vmalloc
0xd0bfc000-0xd0bfe000 8192 tpm_db_mod2_setup_jump_area+0x174/0x3cc pages=1 vmalloc
0xd0c00000-0xd0d01000 1052672 __arm_ioremap_pfn+0x64/0x144 ioremap
0xd0d24000-0xd0d45000 135168 ubifs_mount_orphans+0x44/0x41c pages=32 vmalloc
0xd0d46000-0xd0d48000 8192 tpm_db_mod2_setup_jump_area+0x1f4/0x3cc pages=1 vmalloc
0xd0d49000-0xd0d4b000 8192 tpm_db_mod2_setup_jump_area+0x270/0x3cc pages=1 vmalloc
0xd0d4c000-0xd0d4e000 8192 tpm_db_mod2_setup_chain_area+0xd4/0x308 pages=1 vmalloc
0xd0d4f000-0xd0d51000 8192 tpm_db_mod2_setup_chain_area+0x150/0x308 pages=1 vmalloc
0xd0d52000-0xd0d54000 8192 tpm_db_mod2_setup_chain_area+0x1b0/0x308 pages=1 vmalloc
0xd0d55000-0xd0d57000 8192 tpm_db_mod2_setup_chain_area+0xd4/0x308 pages=1 vmalloc
0xd0d58000-0xd0d5a000 8192 tpm_db_mod2_setup_chain_area+0x150/0x308 pages=1 vmalloc
0xd0d5b000-0xd0d5d000 8192 tpm_db_mod2_setup_chain_area+0x1b0/0x308 pages=1 vmalloc
0xd0d5e000-0xd0d60000 8192 tpm_db_mod2_setup_chain_area+0xd4/0x308 pages=1 vmalloc
0xd0d61000-0xd0d63000 8192 tpm_db_mod2_setup_chain_area+0x150/0x308 pages=1 vmalloc
0xd0d64000-0xd0d66000 8192 tpm_db_mod2_setup_chain_area+0x1b0/0x308 pages=1 vmalloc
0xd0d67000-0xd0d69000 8192 tpm_db_mod2_setup_chain_area+0xd4/0x308 pages=1 vmalloc
0xd0d6a000-0xd0d6c000 8192 tpm_db_mod2_setup_chain_area+0x150/0x308 pages=1 vmalloc
0xd0d6d000-0xd0d6f000 8192 tpm_db_mod2_setup_chain_area+0x1b0/0x308 pages=1 vmalloc
0xd0d70000-0xd0d72000 8192 tpm_db_mod2_setup_chain_area+0xd4/0x308 pages=1 vmalloc
0xd0d73000-0xd0d75000 8192 tpm_db_mod2_setup_chain_area+0x150/0x308 pages=1 vmalloc
0xd0d76000-0xd0d78000 8192 tpm_db_mod2_setup_chain_area+0x1b0/0x308 pages=1 vmalloc
0xd0d79000-0xd0d7b000 8192 tpm_db_mod2_setup_chain_area+0xd4/0x308 pages=1 vmalloc
0xd0d7c000-0xd0d7e000 8192 tpm_db_mod2_setup_chain_area+0x150/0x308 pages=1 vmalloc
0xd0d7f000-0xd0d81000 8192 tpm_db_mod2_setup_chain_area+0x1b0/0x308 pages=1 vmalloc
0xd0d82000-0xd0d84000 8192 tpm_db_mod2_setup_chain_area+0xd4/0x308 pages=1 vmalloc
0xd0d85000-0xd0d87000 8192 tpm_db_mod2_setup_chain_area+0x150/0x308 pages=1 vmalloc
0xd0d88000-0xd0d8a000 8192 tpm_db_mod2_setup_chain_area+0x1b0/0x308 pages=1 vmalloc
0xd0d8b000-0xd0d8d000 8192 tpm_db_mod2_setup_chain_area+0xd4/0x308 pages=1 vmalloc
0xd0d8e000-0xd0d90000 8192 tpm_db_mod2_setup_chain_area+0x150/0x308 pages=1 vmalloc
0xd0d91000-0xd0d93000 8192 tpm_db_mod2_setup_chain_area+0x1b0/0x308 pages=1 vmalloc
0xd0d94000-0xd0d96000 8192 tpm_db_mod2_setup_chain_area+0xd4/0x308 pages=1 vmalloc
0xd0d97000-0xd0d99000 8192 tpm_db_mod2_setup_chain_area+0x150/0x308 pages=1 vmalloc
0xd0d9a000-0xd0d9c000 8192 tpm_db_mod2_setup_chain_area+0x1b0/0x308 pages=1 vmalloc
0xd0d9d000-0xd0db0000 77824 tpm_db_mod2_setup_chain_area+0x264/0x308 pages=18 vmalloc
0xd0db1000-0xd0db4000 12288 tpm_db_mod2_setup_jump_area+0x84/0x3cc pages=2 vmalloc
0xd0db5000-0xd0db7000 8192 tpm_db_mod2_setup_jump_area+0x100/0x3cc pages=1 vmalloc
0xd0db8000-0xd0dba000 8192 tpm_db_mod2_setup_jump_area+0x174/0x3cc pages=1 vmalloc
0xd0dbb000-0xd0dbd000 8192 tpm_db_mod2_setup_jump_area+0x1f4/0x3cc pages=1 vmalloc
0xd0dbe000-0xd0dc0000 8192 tpm_db_mod2_setup_jump_area+0x270/0x3cc pages=1 vmalloc
0xd0dc1000-0xd0dc3000 8192 tpm_db_mod2_setup_chain_area+0xd4/0x308 pages=1 vmalloc
0xd0dc4000-0xd0dc6000 8192 tpm_db_mod2_setup_chain_area+0x150/0x308 pages=1 vmalloc
0xd0dc7000-0xd0dc9000 8192 tpm_db_mod2_setup_chain_area+0x1b0/0x308 pages=1 vmalloc
0xd0dca000-0xd0dcc000 8192 tpm_db_mod2_setup_chain_area+0xd4/0x308 pages=1 vmalloc
0xd0dcd000-0xd0dcf000 8192 tpm_db_mod2_setup_chain_area+0x150/0x308 pages=1 vmalloc
0xd0dd0000-0xd0dd2000 8192 tpm_db_mod2_setup_chain_area+0x1b0/0x308 pages=1 vmalloc
0xd0dd3000-0xd0dd5000 8192 tpm_db_mod2_setup_chain_area+0xd4/0x308 pages=1 vmalloc
0xd0dd6000-0xd0dd8000 8192 tpm_db_mod2_setup_chain_area+0x150/0x308 pages=1 vmalloc
0xd0dd9000-0xd0ddb000 8192 tpm_db_mod2_setup_chain_area+0x1b0/0x308 pages=1 vmalloc
0xd0ddc000-0xd0dde000 8192 tpm_db_mod2_setup_chain_area+0xd4/0x308 pages=1 vmalloc
0xd0ddf000-0xd0de1000 8192 tpm_db_mod2_setup_chain_area+0x150/0x308 pages=1 vmalloc
0xd0de2000-0xd0de4000 8192 tpm_db_mod2_setup_chain_area+0x1b0/0x308 pages=1 vmalloc
0xd0de5000-0xd0de7000 8192 tpm_db_mod2_setup_chain_area+0xd4/0x308 pages=1 vmalloc
0xd0de8000-0xd0dea000 8192 tpm_db_mod2_setup_chain_area+0x150/0x308 pages=1 vmalloc
0xd0deb000-0xd0ded000 8192 tpm_db_mod2_setup_chain_area+0x1b0/0x308 pages=1 vmalloc
0xd0dee000-0xd0df0000 8192 tpm_db_mod2_setup_chain_area+0xd4/0x308 pages=1 vmalloc
0xd0df1000-0xd0df3000 8192 tpm_db_mod2_setup_chain_area+0x150/0x308 pages=1 vmalloc
0xd0df4000-0xd0df6000 8192 tpm_db_mod2_setup_chain_area+0x1b0/0x308 pages=1 vmalloc
0xd0df7000-0xd0df9000 8192 tpm_db_mod2_setup_chain_area+0xd4/0x308 pages=1 vmalloc
0xd0dfa000-0xd0dfc000 8192 tpm_db_mod2_setup_chain_area+0x150/0x308 pages=1 vmalloc
0xd0dfd000-0xd0dff000 8192 tpm_db_mod2_setup_chain_area+0x1b0/0x308 pages=1 vmalloc
0xd0e00000-0xd0f01000 1052672 __arm_ioremap_pfn+0x64/0x144 ioremap
0xd0f02000-0xd0f04000 8192 tpm_db_mod2_setup_chain_area+0xd4/0x308 pages=1 vmalloc
0xd0f05000-0xd0f07000 8192 tpm_db_mod2_setup_chain_area+0x150/0x308 pages=1 vmalloc
0xd0f08000-0xd0f0a000 8192 tpm_db_mod2_setup_chain_area+0x1b0/0x308 pages=1 vmalloc
0xd0f0b000-0xd0f0d000 8192 tpm_db_mod2_setup_chain_area+0xd4/0x308 pages=1 vmalloc
0xd0f0e000-0xd0f10000 8192 tpm_db_mod2_setup_chain_area+0x150/0x308 pages=1 vmalloc
0xd0f11000-0xd0f13000 8192 tpm_db_mod2_setup_chain_area+0x1b0/0x308 pages=1 vmalloc
0xd0f14000-0xd0f27000 77824 tpm_db_mod2_setup_chain_area+0x264/0x308 pages=18 vmalloc
0xd0f28000-0xd0f2b000 12288 tpm_db_mod2_setup_jump_area+0x84/0x3cc pages=2 vmalloc
0xd0f2c000-0xd0f2e000 8192 tpm_db_mod2_setup_jump_area+0x100/0x3cc pages=1 vmalloc
0xd0f2f000-0xd0f31000 8192 tpm_db_mod2_setup_jump_area+0x174/0x3cc pages=1 vmalloc
0xd0f32000-0xd0f34000 8192 tpm_db_mod2_setup_jump_area+0x1f4/0x3cc pages=1 vmalloc
0xd0f35000-0xd0f37000 8192 tpm_db_mod2_setup_jump_area+0x270/0x3cc pages=1 vmalloc
0xd0f38000-0xd0f3a000 8192 tpm_db_mod2_setup_chain_area+0xd4/0x308 pages=1 vmalloc
0xd0f3b000-0xd0f3d000 8192 tpm_db_mod2_setup_chain_area+0x150/0x308 pages=1 vmalloc
0xd0f3e000-0xd0f40000 8192 tpm_db_mod2_setup_chain_area+0x1b0/0x308 pages=1 vmalloc
0xd0f41000-0xd0f43000 8192 tpm_db_mod2_setup_chain_area+0xd4/0x308 pages=1 vmalloc
0xd0f44000-0xd0f46000 8192 tpm_db_mod2_setup_chain_area+0x150/0x308 pages=1 vmalloc
0xd0f47000-0xd0f49000 8192 tpm_db_mod2_setup_chain_area+0x1b0/0x308 pages=1 vmalloc
0xd0f4a000-0xd0f4c000 8192 tpm_db_mod2_setup_chain_area+0xd4/0x308 pages=1 vmalloc
0xd0f4d000-0xd0f4f000 8192 tpm_db_mod2_setup_chain_area+0x150/0x308 pages=1 vmalloc
0xd0f50000-0xd0f52000 8192 tpm_db_mod2_setup_chain_area+0x1b0/0x308 pages=1 vmalloc
0xd0f53000-0xd0f55000 8192 tpm_db_mod2_setup_chain_area+0xd4/0x308 pages=1 vmalloc
0xd0f56000-0xd0f58000 8192 tpm_db_mod2_setup_chain_area+0x150/0x308 pages=1 vmalloc
0xd0f59000-0xd0f5b000 8192 tpm_db_mod2_setup_chain_area+0x1b0/0x308 pages=1 vmalloc
0xd0f5c000-0xd0f5e000 8192 tpm_db_mod2_setup_chain_area+0xd4/0x308 pages=1 vmalloc
0xd0f5f000-0xd0f61000 8192 tpm_db_mod2_setup_chain_area+0x150/0x308 pages=1 vmalloc
0xd0f62000-0xd0f64000 8192 tpm_db_mod2_setup_chain_area+0x1b0/0x308 pages=1 vmalloc
0xd0f65000-0xd0f67000 8192 tpm_db_mod2_setup_chain_area+0xd4/0x308 pages=1 vmalloc
0xd0f68000-0xd0f6a000 8192 tpm_db_mod2_setup_chain_area+0x150/0x308 pages=1 vmalloc
0xd0f6b000-0xd0f6d000 8192 tpm_db_mod2_setup_chain_area+0x1b0/0x308 pages=1 vmalloc
0xd0f6e000-0xd0f70000 8192 tpm_db_mod2_setup_chain_area+0xd4/0x308 pages=1 vmalloc
0xd0f71000-0xd0f73000 8192 tpm_db_mod2_setup_chain_area+0x150/0x308 pages=1 vmalloc
0xd0f74000-0xd0f76000 8192 tpm_db_mod2_setup_chain_area+0x1b0/0x308 pages=1 vmalloc
0xd0f77000-0xd0f79000 8192 tpm_db_mod2_setup_chain_area+0xd4/0x308 pages=1 vmalloc
0xd0f7a000-0xd0f7c000 8192 tpm_db_mod2_setup_chain_area+0x150/0x308 pages=1 vmalloc
0xd0f7d000-0xd0f7f000 8192 tpm_db_mod2_setup_chain_area+0x1b0/0x308 pages=1 vmalloc
0xd0f80000-0xd0f82000 8192 tpm_db_mod2_setup_chain_area+0xd4/0x308 pages=1 vmalloc
0xd0f83000-0xd0f85000 8192 tpm_db_mod2_setup_chain_area+0x150/0x308 pages=1 vmalloc
0xd0f86000-0xd0f88000 8192 tpm_db_mod2_setup_chain_area+0x1b0/0x308 pages=1 vmalloc
0xd0f89000-0xd0f9c000 77824 tpm_db_mod2_setup_chain_area+0x264/0x308 pages=18 vmalloc
0xd1000000-0xd1101000 1052672 __arm_ioremap_pfn+0x64/0x144 ioremap
0xd1200000-0xd1301000 1052672 __arm_ioremap_pfn+0x64/0x144 ioremap
是不是非常清楚!
一、永久映射和临时映射:
2.1、永久映射:
依然把前面描述vmalloc的文章的图搬上来:
high_memory |
VMALLOC_START |
Vmalloc 1 |
4KB隔离带 |
Vmalloc 2 |
4KB隔离带 |
Vmalloc 3 |
4KB隔离带 |
…….. |
VMALLOC_END |
PKMAP_BASE |
8MB隔离带 |
Vmalloc N |
8KB隔离带 |
低端(物理)内存映射 |
FIXADDR_START |
永久映射区 |
临时映射区 |
FIXADDR_TOP |
事实上这个图有些误导,永久映射区始于PKMAP_BASE,但并非结束于FIXADDR_START,在文件arch/arm/include/asm/highmem.h中描述了永久映射区到底是在哪里:
#define PKMAP_BASE (PAGE_OFFSET - PMD_SIZE)
PKMAP_BASE是永久映射区的起始,在这里的arm设备中,PKMAP_BASE是在内核用户分界点之下2MB处,下面这个宏LAST_PKMAP标识永久映射区有多少个条目,注意每个条目是可以映射1页:
#define LAST_PKMAP PTRS_PER_PTE
在这里的arm设备中,LAST_PKMAP值为512,即512个条目,每个条目映射一页,即可最多映射4KB * 512 = 2MB大小,所以永久映射区的范围是[PAGE_OFFSET – 2MB,PAGE_OFFSET],对这里的arm设备就是[0xBFE00000,0xC0000000];这也证明了前面文章说的,高端内存空间不一定就是在high_memory之上的地址空间;
要清楚一个问题就是,一般来说,永久映射包括临时映射是干什么用的,可以说在物理内存不大时(如这里的arm设备是256MB)并且物理地址起始偏移(PHYS_OFFSET)不很大基本上没有意义,因为所有物理内存都可以映射在内核低端内存地址空间,使用简单的偏移即可实现物理地址和虚拟地址的映射,根本用不着什么永久映射,永久映射包括临时映射事实上是在内核空间无法完全容纳物理内存时(比如超过1G的物理内存)才会显出作用;
不论是永久映射还是临时映射,本质上都是建立一个二级映射,把物理地址和虚拟地址对应起来,需要注意的是两者的区别,永久映射是有可能睡眠的,原因在后面描述时会很明显发现,而临时映射不会睡眠,另外永久映射并非一经映射无法解除,临时映射的特点顾名思义,可以不断的覆盖之前的映射关系;
下面看看永久映射的情况:
void *kmap(struct page *page)
{
might_sleep();
/*如果该页其实是低端内存页,则会直接做偏移获取其映射的虚拟地址
否则调用函数kmap_high*/
if (!PageHighMem(page))
return page_address(page);
return kmap_high(page);
}
kmap函数是体系结构自己实现,这里的arm设备就是在arch/arm/mm/highmem.c文件中,kmap函数的作用是:建立这个属于高端内存的物理页与永久内核映射区的映;
kmap的参数page是要映射的高端物理页地址,如果它是低端物理页,则直接返回它对应的虚拟地址;如果它确实是高端物理页地址,则调用函数kmap_high建立一个高端内存物理页的虚拟映射即永久映射:
void *kmap_high(struct page *page)
{
unsigned long vaddr;
/*注意底下仅仅是获取了自旋锁,是为避免多CPU访问,但没有关中断!!!
因为永久映射是可能睡眠的,所以不能被中断上下文等不可以睡眠的场合应用,
所以就不用关中断了
另外,为什么永久映射可能会睡眠? 因为可能存在所有永久映射条目都已在映射了,
现在要想再加一个永久映射,就得等待里边某一个映射的释放才行*/
/*
* 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);
pkmap_count[PKMAP_NR(vaddr)]++;
BUG_ON(pkmap_count[PKMAP_NR(vaddr)] < 2);
unlock_kmap();
return (void*) vaddr;
}
首先查看这个高端物理页是否已经做过高端映射了,通过函数page_address查看,
void *page_address(struct page *page)
{
unsigned long flags;
void *ret;
struct page_address_slot *pas;
/*如果不是高端地址,则直接返回page描述符所映射的虚拟地址*/
/*函数lowmem_page_address:
通过page_to_pfn获取page在mem_map中的位置
然后乘以页大小得到物理地址
最后通过__va计算出虚拟地址*/
if (!PageHighMem(page))
return lowmem_page_address(page);
/*返回该页(page)所在的哈希链表的表头*/
pas = page_slot(page);
ret = NULL;
/*由哈希链表头,遍历该链表查找该page所对应的虚拟地址*/
spin_lock_irqsave(&pas->lock, flags);
if (!list_empty(&pas->lh)) {
struct page_address_map *pam;
list_for_each_entry(pam, &pas->lh, list) {
if (pam->page == page) {
ret = pam->virtual;
goto done;
}
}
}
done:
spin_unlock_irqrestore(&pas->lock, flags);
return ret;
}
注意高端地址是通过哈希链表管理的,这里找出该物理页所在的哈希链表的表头,然后通过遍历找到page描述符所映射的虚拟地址来判断是否映射过,若映射过则返回其映射的虚拟地址,否则返回NULL;
回到函数kmap_high,如果从page_address返回NULL,说明这个高端页不存在高端映射,应该新建一个映射,即为这个高端页分配一个虚拟地址,调用函数map_new_virtual:
static inline unsigned long map_new_virtual(struct page *page)
{
unsigned long vaddr;
int count;
start:
/*对于我们marvell的arm,LAST_PKMAP值为512(对于普然和broadlight的mips,为1024即4M),即永久映射区可以映射的高端内存页为512个
即可以同时映射的高端内存为512 * 4K = 2M*/
count = LAST_PKMAP;
/* Find an empty entry */
/*不断试图从512个可映射页条目中找到一个空闲的,找到则break*/
for (;;) {
last_pkmap_nr = (last_pkmap_nr + 1) & LAST_PKMAP_MASK;
/*当找不到计数值为0的页表项时,就要调用flush_all_zero_pkmaps(),
将计数值为1的页表项置为0,即撤销已经不用了的映射,并且刷新TLB*/
if (!last_pkmap_nr) {
flush_all_zero_pkmaps();
count = LAST_PKMAP;
}
if (!pkmap_count[last_pkmap_nr])
break; /* Found a usable entry */
if (–count)
continue;
/*
* Sleep for somebody else to unmap their entries
*/
/*既未break退出也未返回continue,这说明现在512个可以映射的高端内存页,全都被正在映射着,
这时需要等待某个页释放它的映射,所以声明一个等待队列,并让出CPU
这也说明,申请高端内存的映射,是可能会睡眠的!所以不要用在中断上下文等不可睡眠代码的里面*/
{
DECLARE_WAITQUEUE(wait, current);
__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;
}
}
/*在512个可映射条目中找到空闲条目了,获取该页表项对应的线性地址并赋给vaddr
永久映射区中,物理页的虚拟地址也是线性的,也是通过偏移运算得出*/
vaddr = PKMAP_ADDR(last_pkmap_nr);
/*将pkmap_page_table中对应的pte设为申请映射的页框的pte,完成永久内核映射区中的页表项条目到物理页框的映射*/
set_pte_at(&init_mm, vaddr,
&(pkmap_page_table[last_pkmap_nr]), mk_pte(page, kmap_prot));
/*内核用一个pkmap_count数组来记录pkmap_page_table中每一个页表项的使用状态,
其实就是为每个页表项分配一个计数器来记录相应的页表是否已经被用来映射。计数值分为以下三种情况:
1、计数值为0:对应的页表项没有映射高端内存,即为空闲可用的
2、计数值为1: 对应的页表项没有映射高端内存,但是不可用,因为上次映射后对应的TLB项还未被淸刷
3、计数值为n(n>1):对应的页表项已经映射了一个高端内存页框,并且有n-1个内核成分正在利用这种映射关系
这里先预设置为1,在返回到kmap_high中会再加1,表示被一个内核成分正在利用该映射*/
pkmap_count[last_pkmap_nr] = 1;
/*实现映射该页的虚拟地址,将该页及其虚拟地址添加到page_address_htable链表中*/
set_page_address(page, (void *)vaddr);
/*返回映射的虚拟地址*/
return vaddr;
}
全局变量pkmap_count和last_pkmap_nr用于管理全部512个永久映射条目,这里先在for循环中遍历查找一个空白条目,如果找不到说明全部512个条目都正在映射着,如再需映射需要等其中某一个释放才行,这就需要等这个事件的发生,所以会睡眠,这就是为什么在中断上下文等不能睡眠的地方不能使用永久映射的具体原因;
在找到空白条目后会退出for循环创建映射,通过函数PKMAP_ADDR在永久映射区找一个位置,其实全局变量last_pkmap_nr保存当前永久映射的个数,所以下面的源码就很好解释了,就是移位:
#define PKMAP_ADDR(nr) (PKMAP_BASE + ((nr) << PAGE_SHIFT))
接下来创建这个映射关系,创建的依然是二级映射,只是二级页表不用动态申请了,全局变量pkmap_page_table是二级页表条目的数组。
最后把这个物理页地址和虚拟地址组成的映射节点(结构类型struct page_address_map)插入到管理永久映射的哈希链表中,调用函数set_page_address,注意不论是创建映射还是释放映射都会调用这个函数:
void set_page_address(struct page *page, void *virtual)
{
unsigned long flags;
struct page_address_slot *pas;
struct page_address_map *pam;
/*确保该物理页是高端地址*/
BUG_ON(!PageHighMem(page));
/*找出该页(page)所在的哈希链表的表头*/
pas = page_slot(page);
/*如果是映射操作*/
if (virtual) { /* Add */
/*确保空闲链表page_address_pool不为空*/
BUG_ON(list_empty(&page_address_pool));
/*在临界区执行,取出page_address_pool空闲链表的第一个成员并删除
这意味着这个空闲链表的节点要被使用了*/
spin_lock_irqsave(&pool_lock, flags);
pam = list_entry(page_address_pool.next,
struct page_address_map, list);
list_del(&pam->list);
spin_unlock_irqrestore(&pool_lock, flags);
/*加入page页、所需要映射的虚拟地址*/
pam->page = page;
pam->virtual = virtual;
/*尾插法加入这个新节点到该页所在的哈希链表*/
spin_lock_irqsave(&pas->lock, flags);
list_add_tail(&pam->list, &pas->lh);
spin_unlock_irqrestore(&pas->lock, flags);
}
/*如果是释放操作*/
else { /* Remove */
/*和映射操作整好相反,删除该页所在的哈希链表的该页的节点,
然后尾插法恢复空闲链表page_address_pool一个新节点*/
spin_lock_irqsave(&pas->lock, flags);
list_for_each_entry(pam, &pas->lh, list) {
if (pam->page == page) {
list_del(&pam->list);
spin_unlock_irqrestore(&pas->lock, flags);
spin_lock_irqsave(&pool_lock, flags);
list_add_tail(&pam->list, &page_address_pool);
spin_unlock_irqrestore(&pool_lock, flags);
goto done;
}
}
spin_unlock_irqrestore(&pas->lock, flags);
}
done:
return;
}
2.2、临时映射:
临时映射和永久映射差别不是很大,只是说临时映射顾名思义,比较临时,和永久相反,永久映射若需要修改映射关系需要先释放映射再创建新的映射关系,而临时映射就是直接再映射一次把前面的映射关系覆盖掉就行了,确实是比较临时;所以临时映射不会睡眠,它不会像永久映射,可能产生条目满需要等待其他条目被释放的情况,如果条目满了它只需覆盖掉一个就行了;
对于arm体系结构,在文件arch/arm/include/asm/fixmap.h文件中定义了临时映射的地址范围:
#define FIXADDR_START 0xfff00000UL
#define FIXADDR_TOP 0xfffe0000UL
#define FIXADDR_SIZE (FIXADDR_TOP - FIXADDR_START)
#define FIX_KMAP_BEGIN 0
#define FIX_KMAP_END (FIXADDR_SIZE >> PAGE_SHIFT)
临时映射地址范围为[0xfff00000,0xfffe0000],长度为896KB即224页的范围,显然FIX_KMAP_BEGIN和FIX_KMAP_END是临时映射条目个数的下标。
通过函数kmap_atomic创建临时映射:
void *kmap_atomic(struct page *page, enum km_type type)
{
unsigned int idx;
unsigned long vaddr;
void *kmap;
pagefault_disable();
/*确保该页是高端物理页*/
if (!PageHighMem(page))
return page_address(page);
debug_kmap_atomic(type);
/*如果这个物理页已经有映射的虚拟地址了(确切的说是是否已经存在永久映射),
那么直接返回它映射的虚拟地址,无需再映射*/
kmap = kmap_high_get(page);
if (kmap)
return kmap;
/*idx获取type(同时适配多核)*/
idx = type + KM_TYPE_NR * smp_processor_id();
/*得到对应页表项的虚拟地址,也是通过加减偏移运算*/
vaddr = __fix_to_virt(FIX_KMAP_BEGIN + idx);
#ifdef CONFIG_DEBUG_HIGHMEM
/*
* With debugging enabled, kunmap_atomic forces that entry to 0.
* Make sure it was indeed properly unmapped.
*/
BUG_ON(!pte_none(*(TOP_PTE(vaddr))));
#endif
/*将页表项与page进行关联,用kmap_pte-idx而不是用kmap_pte+idx,因为固定映射区是逆向生长的,
也就是说枚举项越靠前的部分的虚拟地址越靠后*/
set_pte_ext(TOP_PTE(vaddr), mk_pte(page, kmap_prot), 0);
/*
* When debugging is off, kunmap_atomic leaves the previous mapping
* in place, so this TLB flush ensures the TLB is updated with the
* new mapping.
*/
/*更新CPU的TLB*/
local_flush_tlb_kernel_page(vaddr);
return (void *)vaddr;
}
这部分源码的道理和永久映射的kmap非常相似,就不解释了。
高端映射其实还有很多应该理解的东西,后续随着应用不断补充吧。