kmap的实现分析与实验
来源:互联网 发布:求职网络打字员 编辑:程序博客网 时间:2024/06/06 02:53
kmap的实现分析
kmap/unkmap系统调用是用来映射高端物理内存页到内核地址空间的api函数,他们分配的内核虚拟地址范围属于[PKMAP_BASE,PAGE_OFFSET]即[0xbfe00000,0xc0000000]范围,大小是2M的虚拟空间,为了映射该块虚拟地址,所使用的二级页表的大小刚好是一个物理page的总计是两个pte table(4KB)
kmap的调用流程分析:
arch/arm/mm/highmem.c
void *kmap(struct page *page){might_sleep();if (!PageHighMem(page)){//如果是低端内存,则直接返内存页对应的直接映射虚拟地址//printk("low mem page\n");return page_address(page);//所有的低端内存,在内核初始化时就已经映射好了,并且是不变得,且物理到虚拟相差0xc0000000}else{//printk("high mem page\n");}return kmap_high(page);//高端内存页}
进入/trunk/mm/highmem.c的kmap_high
/** * kmap_high - map a highmem page into memory * @page: &struct page to map * * Returns the page's virtual memory address. * * We cannot call this from interrupts, as it may block. */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);//开始建立新的映射pkmap_count[PKMAP_NR(vaddr)]++;//该数组的值为1,说明映射已经建立,为2表明该应声存在着引用BUG_ON(pkmap_count[PKMAP_NR(vaddr)] < 2);unlock_kmap();return (void*) vaddr;}
static inline unsigned long map_new_virtual(struct page *page){unsigned long vaddr;int count;start:count = LAST_PKMAP; // 2MB/4096KB=512 entries = LAST_PKMAP/* Find an empty entry */for (;;) {last_pkmap_nr = (last_pkmap_nr + 1) & LAST_PKMAP_MASK;if (!last_pkmap_nr) {flush_all_zero_pkmaps();count = LAST_PKMAP;}if (!pkmap_count[last_pkmap_nr])//为0,说明该虚拟地址不存在映射,没人使用break;/* Found a usable entry */if (--count)//如果遍历了整个kmap虚拟空间,都不能找到空闲的虚拟地址,则休眠等待unkmap释放虚拟地址continue;/* * Sleep for somebody else to unmap their entries */{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;}}vaddr = PKMAP_ADDR(last_pkmap_nr);//#define PKMAP_ADDR(nr)(PKMAP_BASE + ((nr) << PAGE_SHIFT))set_pte_at(&init_mm, vaddr, &(pkmap_page_table[last_pkmap_nr]), mk_pte(page, kmap_prot));pkmap_count[last_pkmap_nr] = 1;set_page_address(page, (void *)vaddr);return vaddr;}
上面代码中的pkmap_page_table是kmap所对应的虚拟地址[PKMAP_BASE,PAGE_OFFSET]所对应的二级映射表,即pte table,该映射表刚好是4KB用来映射2MB的虚拟到物理地址
pkmap_page_table使在trunk/arch/arm/mm/mmu.c文件中设置的:
static void __init kmap_init(void){#ifdef CONFIG_HIGHMEM
//获取kmap所对应的虚拟地址[PKMAP_BASE,PAGE_OFFSET]所对应的二级映射表的开始地址。该二级映射表刚好就是一个物理页的大小pkmap_page_table = early_pte_alloc_and_install(pmd_off_k(PKMAP_BASE),PKMAP_BASE, _PAGE_KERNEL_TABLE);printk("************************************************\n");printk("pkmap_page_table:%x, phy of pkmap_page_table:%x\n",pkmap_page_table,virt_to_phys(pkmap_page_table));printk("************************************************\n");#endif}
上述函数中的pmd_off_k(PKMAP_BASE)是获取PKMAP_BASE虚拟地址对应的一级映射表中所对应的页表项地址,static pte_t * __init early_pte_alloc_and_install(pmd_t *pmd,unsigned long addr, unsigned long prot){if (pmd_none(*pmd)) {//如果一级页表项无效,即还未分配该表项所指向二级页表,即pte tablepte_t *pte = early_pte_alloc(pmd);//分配二级页表,即pte tabbleearly_pte_install(pmd, pte, prot);//将pte table的hw/pte page0,hw/pte page1分别填充到一级页表项的低4byte和高4byte}BUG_ON(pmd_bad(*pmd));return pte_offset_kernel(pmd, addr);//返回二级页表中对应的页表项地址。}
以上过程,具体见下图的映射关系图1
kmap的实验
kmap试验目的:
a:kmap映射高端内存页返回的地址是否属于0xbfe00000 - 0xc0000000范围。
b:kmap的二级映射表的虚拟地址:pkmap_page_table,在函数kmap_init中打印该虚拟地址和对应的物理地址,然后根据二级映射表的结构,找到kmap返回的虚拟地址对应的物理地址。
再根据该物理地址,使用mu内存查看工具,查看该物理页的内容是否是我们之前在驱动中通过kmap返回的虚拟地址设置的特殊数值。
详细的测试代码如下:
#include <linux/module.h>#include <linux/init.h>#include <linux/mm.h>#include <linux/fs.h>#include <linux/types.h>#include <linux/delay.h>#include <linux/moduleparam.h>#include <linux/slab.h>#include <linux/errno.h>#include <linux/ioctl.h>#include <linux/cdev.h>#include <linux/string.h>#include <linux/list.h>#include <linux/pci.h>#include <linux/gpio.h>#include <linux/gfp.h>#include <asm/highmem.h>
下面的函数,是通过kmap分配高端内存页,并且将分配得到的内存页,都特别的设置成特殊的数据,依次为:0x5a,0x5b,0x5cstruct page * map_high_mem(int order){int i=0;static int poison = 0x5a;unsigned char *buf = NULL;struct page *high_page = alloc_pages(__GFP_HIGHMEM,order);//指定可以从高端内存分配物理空闲页//struct page *high_page = alloc_pages(GFP_HIGHUSER,order);if(high_page){printk("high_page alloc success\n");}else{printk("high_page alloc failed\n");}buf = kmap(high_page);//为该高端内存页,建立临时映射,该函数可能休眠if(buf){printk("kmap success,buf addr:%x\n",buf);//如果映射成功,返回影射后的虚拟地址for(i=0;i<4096;i++)buf[i] = poison;poison++;}else{printk("kmap failed\n");}return high_page;}void free_high_mem(struct page *page,int order){kunmap(page);//拆除映射__free_pages(page,order);//释放对应物理页}
struct page *page_array[5];#define NUM_ORDER 0static int __init dev_init(void){int ret;int i;/*************************************************************/i = 0;page_array[i++] = map_high_mem(NUM_ORDER);//连续分配,映射三个物理页page_array[i++] = map_high_mem(NUM_ORDER);page_array[i++] = map_high_mem(NUM_ORDER);printk("module address,page_array:0x%x\n",page_array);return ret;}
static void __exit dev_exit(void){int i = 0;free_high_mem(page_array[i++],NUM_ORDER);free_high_mem(page_array[i++],NUM_ORDER);free_high_mem(page_array[i++],NUM_ORDER);}module_init(dev_init);module_exit(dev_exit);MODULE_LICENSE("GPL");MODULE_AUTHOR("LKN@SCUT");
以上是我们的测试代码,代码编译,加载执行。
以下log是得到pkmap_page_table的物理地址,即kmap的二级映射表的开始物理地址,该log是内核启动阶段在kmap_init函数打印出来的。
[ 0.000000] ************************************************
[ 0.000000] pkmap_page_table:ef7fc000, phy of pkmap_page_table:2f7fc000(759MB)
[ 0.000000] ************************************************
[ 0.000000] pkmap_page_table:ef7fc000, phy of pkmap_page_table:2f7fc000(759MB)
[ 0.000000] ************************************************
由于kmap是2MB的虚拟空间,刚好一个page大小的二级映射表就可以完全覆盖到。如上的log所显示,这个二级映射表的开始物理地址是:2f7fc000。
模块加载时的log:
/storage/sdcard1 # insmod mymap.ko
[ 83.289132] kernel buffer virtial address:ee155000
[ 83.293936] kernel buffer physical address:2e155000
[ 83.298801] high_page alloc success
[ 83.302264] kmap success,buf addr:bfeee000------------->(a) //第一次kmap映射返回的虚拟地址,将该页都初始化为0x5a
[ 83.306393] high_page alloc success
[ 83.309809] kmap success,buf addr:bfeef000------------->(b) //第二次kmap映射返回的虚拟地址,将该页都初始化为0x5b
[ 83.313990] high_page alloc success
[ 83.317414] kmap success,buf addr:bfef0000------------->(c) //第三次kmap映射返回的虚拟地址,将该页都初始化为0x5c
[ 83.289132] kernel buffer virtial address:ee155000
[ 83.293936] kernel buffer physical address:2e155000
[ 83.298801] high_page alloc success
[ 83.302264] kmap success,buf addr:bfeee000------------->(a) //第一次kmap映射返回的虚拟地址,将该页都初始化为0x5a
[ 83.306393] high_page alloc success
[ 83.309809] kmap success,buf addr:bfeef000------------->(b) //第二次kmap映射返回的虚拟地址,将该页都初始化为0x5b
[ 83.313990] high_page alloc success
[ 83.317414] kmap success,buf addr:bfef0000------------->(c) //第三次kmap映射返回的虚拟地址,将该页都初始化为0x5c
可见三次kmap返回的虚拟地址都是属于0xbfe00000 - 0xc0000000范围。
根据他们返回的虚拟地址,我们再结合之前得出的二级映射表的物理地址,我们推算出该三个虚拟地址所对应的物理内存页,分别如下:
由程序的虚拟地址得到对应的物理地址的公式为:virt_addr----->phy_addr
二级映射表的开始物理地址+2KB的硬件页表页内偏移+virt_addr[bit20,bit12] * 4 上存储的内容即为虚拟页对应的物理页
case a: bfeee000---------->374ce000
2f7fc000 + 1024*2(800) + 238*4(3b8) = 2F7FCBB8(二级映射表项的物理地址)
2f7fc000 + 1024*2(800) + 238*4(3b8) = 2F7FCBB8(二级映射表项的物理地址)
0x37ce000即是虚拟页bfeee000对应的物理页,我们可以看到,该页上的内容刚好就是我们之前设置的0x5a。
case b: bfeef000---------->37b0c000
2f7fc000 + 1024*2(800) + 238*4(3bc) = 2F7FCBBC(二级映射表项的物理地址)
case b: bfeef000---------->37b0c000
2f7fc000 + 1024*2(800) + 238*4(3bc) = 2F7FCBBC(二级映射表项的物理地址)
37b0c000即是虚拟页bfeef000对应的物理页,我们可以看到,该页上的内容刚好就是我们之前设置的0x5b。
case c: bfef0000---------->37b0b000
2f7fc000 + 1024*2(800) + 240*4(3C0) = 2F7FCBC0(二级映射表项的物理地址)
37b0b000即是虚拟页bfef0000对应的物理页,我们可以看到,该页上的内容刚好就是我们之前设置的0x5c。
通过以上的实验,我们验证了自己对linux arm二级映射表结构的理解,同时也明白了kmap映射的原理。
- kmap的实现分析与实验
- kmap的实现分析与实验
- kmap的实现分析与实验
- kmap的实现分析
- Kmap
- 数据压缩实验四 DPCM压缩系统的实现与分析
- 实验二 预测分析算法的设计与实现
- kmalloc,vmalloc,kmap 缺页的讨论
- kmalloc、vmalloc、kmap、malloc的区别
- Fbonacci 的python实验与分析
- 编译原理实验之预测分析算法的设计与实现
- 数据压缩原理与应用 实验四 DPCM 压缩系统的实现和分析
- PHP_性能分析与实验:性能的宏观分析
- PHP 性能分析与实验:性能的宏观分析
- PHP 性能分析与实验:性能的宏观分析
- 基于Windows Server 2008 系统的DNS服务器搭建与FTP服务器实现-《网络协议分析》实验
- 数据结构实验4(排序算法的实现及性能分析)
- 实验四 DPCM 压缩系统的实现和分析
- MYSQL字符数字转换
- plist文件修改
- Ubuntu安装mysql步骤(包括远程连接)
- 用Openssl计算ECDSA签名
- Hadoop-0.22.0分布式集群配置
- kmap的实现分析与实验
- Linux Crontab 定时任务 命令详解
- 10-windowsPhone常用控件---复选框
- 深入理解alias, alias_method和alias_method_chain
- Android控件之TextClock
- Java学习——Ubuntu下jdk的安装以及Java环境的配置
- LAMP简介
- Spring中的applicationContext.xml与SpringMVC的xxx-servlet.xml的区别
- 使用Solrj管理Solr索引