转载自:
http://blog.csdn.net/gxfan/article/details/2723455
http://blog.csdn.net/xiaojsj111/article/details/11817587
http://blog.csdn.net/decload/article/details/8080533
内存申请函数解析
//+++++++++++++++++++++++++++++++++++++++++++
1、 kmalloc()是内核中最常见的内存分配方式,它最终调用伙伴系统的__get_free_pages()函数分配,根据传递给这个函数的flags参数,决定这个函数的分配适合什么场合,如果标志是GFP_KERNEL则仅仅可以用于进程上下文中,如果标志GFP_ATOMIC则可以用于中断上下文或者持有锁的代码段中。
kmalloc返回的线形地址是直接映射的,而且用连续物理页满足分配请求,且内置了最大请求数(2**5=32页)。
2、 kmap()是主要用在高端存储器页框的内核映射中,一般是这么使用的:
使用alloc_pages()在高端存储器区得到struct page结构,然后调用kmap(struct *page)在内核地址空间PAGE_OFFSET+896M之后的地址空间中(PKMAP_BASE到FIXADDR_STAR)建立永久映射(如果page结构对应的是低端物理内存的页,该函数仅仅返回该页对应的虚拟地址)
kmap()也可能引起睡眠,所以不能用在中断和持有锁的代码中
不过kmap 只能对一个物理页进行分配,所以尽量少用。
3、 vmalloc优先使用高端物理内存,但性能上会打些折扣。
vmalloc分配的物理页不会被交换出去;
vmalloc返回的虚地址大于(PAGE_OFFSET + SIZEOF(phys memory) + GAP),为VMALLOC_START----VMALLOC_END之间的线形地址;
vmalloc使用的是vmlist链表,与管理用户进程的vm_area_struct要区别,而后者会swapped;
4、 使用kmap的原因:
对于高端物理内存(896M之后),并没有和内核地址空间建立一一对应的关系(即虚拟地址=物理地址+PAGE_OFFSET这样的关系),所以不能使用get_free_pages()这样的页分配器进行内存的分配,而必须使用alloc_pages()这样的伙伴系统算法的接口得到struct *page结构,然后将其映射到内核地址空间,注意这个时候映射后的地址并非和物理地址相差PAGE_OFFSET.
//+++++++++++++++++++++++++++++++++++++++++++
kmap的实现分析
- <span style="font-size:24px">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);//高端内存页
- }</span>
- <span style="font-size:24px">/**
- * 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;
- }</span>
- <span style="font-size:24px">static inline unsigned long map_new_virtual(struct page *page)
- {
- unsigned long vaddr;
- int count;
- start:
- count = LAST_PKMAP; // 2MB/4096KB=512 entries = <span style="font-family:Arial,Helvetica,sans-serif">LAST_PKMAP</span>
- /* 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);<span style="font-family:Arial,Helvetica,sans-serif">//#define PKMAP_ADDR(nr)</span><span style="font-family:Arial,Helvetica,sans-serif"> </span><span style="font-family:Arial,Helvetica,sans-serif">(PKMAP_BASE + ((nr) << PAGE_SHIFT))</span>
- 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;
- }</span>
上面代码中的pkmap_page_table是kmap所对应的虚拟地址[PKMAP_BASE,PAGE_OFFSET]所对应的二级映射表,即pte table,该映射表刚好是4KB用来映射2MB的虚拟到物理地址
- <span style="font-size:24px">static void __init kmap_init(void)
- {
- #ifdef CONFIG_HIGHMEM</span>
- <span style="font-size:24px"> //获取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
- }
- </span>
- <span style="font-size:24px">static pte_t * __init early_pte_alloc_and_install(pmd_t *pmd,
- unsigned long addr, unsigned long prot)
- {
- if (pmd_none(*pmd)) {//如果一级页表项无效,即还未分配该表项所指向二级页表,即pte table
- pte_t *pte = early_pte_alloc(pmd);//分配二级页表,即pte tabble
- early_pte_install(pmd, pte, prot);//将pte table的hw/pte page0,<span style="font-family:Arial,Helvetica,sans-serif">hw/pte page1分别填充到一级页表项的低4byte和高4byte</span>
- }
- BUG_ON(pmd_bad(*pmd));
- return pte_offset_kernel(pmd, addr);//返回二级页表中对应的页表项地址。
- }
- </span>
以上过程,具体见下图的映射关系图1
kmap的实验
- <span style="font-size:24px">#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></span>
- <span style="font-size:24px">struct 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);//释放对应物理页
- }</span>
- <span style="font-size:24px">struct page *page_array[5];
- #define NUM_ORDER 0
- static 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;
- }</span>
- <span style="font-size:24px">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");</span>
以上是我们的测试代码,代码编译,加载执行。
[ 0.000000] pkmap_page_table:ef7fc000, phy of pkmap_page_table:2f7fc000(759MB)
[ 0.000000] ************************************************
[ 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
2f7fc000 + 1024*2(800) + 238*4(3b8) = 2F7FCBB8(二级映射表项的物理地址)
case b: bfeef000---------->37b0c000
2f7fc000 + 1024*2(800) + 238*4(3bc) = 2F7FCBBC(二级映射表项的物理地址)
case c: bfef0000---------->37b0b000
2f7fc000 + 1024*2(800) + 240*4(3C0) = 2F7FCBC0(二级映射表项的物理地址)
//+++++++++++++++++++++++++++++++++++++++++++
Linux内核中分配页面使用了以下六个函数:
alloc_pages(gfp_mask, order):用这个函数请求2order 个连续的页框。他返回第一个所分配页框描述符的地址,或者如果失败,则返回NULL。
alloc_page(gfp_mask):用于获得一个单独页框的宏,它其实只是alloc_pages(gfp_mask, 0)。它返回所分配页框描述符的地址,或者如果分配失败,则返回NULL。
__get_free_pages(gfp_mask, order):该函数类似于alloc_pages( ),只不过它返回第一个所分配页对应的内存线性地址。
__get_free_page(gfp_mask):用于获得一个单独页框的宏,它也只是__get_free_pages(gfp_mask, 0)
get_zeroed_page(gfp_mask):函数用来获取满是0的页面,它调用alloc_pages(gfp_mask | __GFP_ZERO, 0),然后返回所获取页框的线性地址。
__get_dma_pages(gfp_mask, order):该宏获取用于DMA的页框,它扩展调用__get_free_pages(gfp_mask | _ _GFP_DMA, order)。
这六个函数之间的调用关系如下:
所以这六个函数最后调用的还是alloc_pages(),接下来就只需要研究alloc_pages()这个函数。
而alloc_pages调用过程为:
alloc_pages()
--> alloc_pages_node()
--> __alloc_pages()
--> __alloc_pages_nodemask()
--> get_page_from_freelist()
--> buffered_rmqueue()
--> __rmqueue()
具体为:
在include/linux/gfp.h中有这定义:
- #define alloc_pages(gfp_mask, order) \
- alloc_pages_node(numa_node_id(), gfp_mask, order)
其中numa_node_id() 返回0,指的是第0个节点。
之后的函数解析参考:(很详细)文章
转载:Linux伙伴系统(三)--分配页