一般的,用户空间使用函数malloc在堆上分配内存空间,同样的,在内核空间同样有一套类似的函数来分配空间。下面的知识会涉及页式管理的内存机制,如果不懂的要先复习一下,在S3C2440数据手册的MMU部分有介绍。xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxx
一、内核空间和用户空间有什么不同
学c语言的时候应该学过,从用户空间看,每个进程都傻乎乎的以为自己有4G 的内存空间,其中位于高地址(3G-4G)的1G空间给内核用,另外的3G(0-3G)都是它一个人独占的。所以用户空间很慷慨的把3G的空间分了好几个区域,如堆、栈、代码段等。其中,malloc()分配的空间位于堆,而程序中的自动变量,如你在函数内定义的“int i”,它是放在栈上,同时。用户空间的栈是可变栈,即随着数据的增多,对应函数的栈空间也会增多。
跟每个用户空间的进程不一样,内核只有1G的空间,同时,除了自己本身有进程运行外,内核还要允许用户空间进程调用系统调用进入内核空间去执行。所以,内核对此相当吝啬,它规定在内核中的每个进程都只有4KB或8KB(32位下)的定长栈。出于这样的原因,大的数据结构就不能在栈中分配,只能请求内核分配新的空间来存放数据,如函数kmalloc()。
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxx
二、内存的基本单位是字节吗?
在介绍分配内存空间的函数前,我们还要了解一下内存是怎么被划分的。
内核不仅知道用户空间中看到的1G内核空间是假的,它还知道实际的物理内存是多少(我的开发板是64M)。所以,内核的其中一个任务就是,当这段虚假内存中的数据需要调用时,内核把这段虚拟内存与实际的物理内存对应上,运行完后又把两段内存的对应关系撤销掉给另外的虚拟内存用。
既然知道虚拟内存与物理内存的关系,那它们是怎么对应的,难道是一个一个字节?如果这样子做的话内核肯定觉得崩溃。
页是内存管理的基本单位。内存管理器(MMU,用于虚拟地址与物理地址之间的转换)通常以页为单位进行出来。页是内存管理的最小单位。在32位的系统中,一页的大小为4KB。所以,64M的物理内存将被分为16384个页。每一个物理页对应地用一个struct page来维护,注意,该结构体是用来维护物理页,而不是虚拟也,结构体记录该页是否被使用,对应的虚拟地址是多少等信息。
由于内存访问的限制,内核又把内存分成了3个区。
如有些硬件的访问只能在24位的地址空间寻址,出于这总访问限制,linux把前16MB划分为ZONE_DMA——用于直接内存访问(MDA)。
在x86体系里,高于896M的内存空间称为高端内存,这段内存区域的页和普通的内存页操作后有差异,这段区域划分为ZONE_HIGHMEM。
剩下的,加载这两段区域之间的就是我们平时用的普通内存区域
——ZONE_NORMAL。
这这里要注意一下:
1)这些分区是指linux自己分的,当然,如果普通分区不够用,当然也可以占用其他区的空间。
2)分区的大小是根据体系结构而定的,一般的ARM下,ZONE_NORMAL就是所有的可用内存区域。
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxx
三、分配内存时使用的标记gfp_mask
在讲如何分配内存之前,先讲一下分配内存时将会用到的gfp_mask。简单地讲,这个标记指定了分配内存时的要求。具体分三类:
行为修饰符:表示内核应当如何分配内存,如指定不能休眠等。
区修饰符:指定内存将要分配到上面讲的三个区中的哪一个。
类型标记:这包含了上面两种修饰符(或运算),这些标记是为了让用户更好地去使用。
标记有很多,我这里不一一介绍,需要的可以自己查阅《linux内核设计与实现(第三版)》P238页。这里我讲两个常用的类型标记:
1)GFP_KERNEL:最常用的标记,用于可睡眠的进程上下文。
2)GFP_ATOMIC:使用了这个标记,内存分配函数不会引起随眠。
3)GFP_USER:当需要给用户空间分配内存空间时使用该标记。
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxx
四、分配内存的第一种方法——按页分配
这是内核提供的一种请求内存的底层机制,都是以页为单位分配内存。以下函数包含在<linux/gfh.h>
这分为两个步骤:
1、请求内核分配页,获得物理页对应的结构体struct page:
static inline struct page*alloc_pages(gfp_t gfp_mask,unsigned int order)
使用:
该函数用于申请(1<<other)——即2的other次方个连续物理页,gfp_mask用于指定分配的方式,一般使用GFP_KERNEL或GFP_ATOMIC。注意:函数会引起睡眠
返回值:
成功返回一个指针,指向这连续物理页的第一个struct page结构体,失败返回NULL。
2、分配页后还不能直接用,需要得到该页对应的虚拟地址:
void*page_address(struct page*page)
其实这个函数就是获取page的成员virtual,但千万不要直接访问,需要使用这个函数。函数返回的是物理页对应的虚拟地址,注意,如果你申请了多个物理页,分配的物理页是连续的,对应的虚拟地址也是连续的。
上面的两个步骤其实可以合成一个函数:
unsigned long__get_free_pages(gfp_t gfp_mask,unsigned int order)
这个函数的传参和alloc_pages的一样,不过它直接返回申请的物理页对应的虚拟地址。
当然,无论使用上面的哪种方法,当内存不用时,需要调用函数释放:
1、如果你使用上面的第一种方法:
void__free_pages(struct pag e*page,unsigned int order)
2、如果你使用的是第二种方法:
void free_pages(unsigned long addr,unsigned int order)
下面来个程序:
/*5th_mm/5th_mm_1/1st/test.c*/
1#include<linux/module.h>
2#include<linux/init.h>
3
4#include<linux/mm.h>
5
6struct page*p;
7char*s;
8
9static int__init test_init(void)//模块初始化函数
10{
11unsigned long virt,phys;
12
13#define SWITCH0//通过定义这个来切换校验这两种不同的方法
14#if SWITCH
15//alloc2pages
16p=alloc_pages(GFP_KERNEL,1);linux内核文件放在哪
17if(NULL==p){//必须检验错误
18printk("alloc page error!\n");
19return-ENOMEM;
20}
21s=page_address(p);
22#else
23s=(char*)__get_free_pages(GFP_KERNEL,1);
24if(NULL==s){
25printk("alloc page error!\n");
26return-ENOMEM;
27}
28#endif
29
30phys=__pa((unsigned long)s);//通过虚拟地址获得对应的物理地址
31virt=(unsigned long)__va(phys);//通过物理地址获得对应的虚拟地址
32printk("<p->virtual,s>[%p]\n",s);//打印获得的虚拟地址
33printk("<phys>[%p]\n",(void*)phys);//打印对应的物理地址
34printk("<virt>[%p]\n",(void*)virt);//再打印虚拟地址,其实就是分配函数返回的地址
35
36memcpy(s,"hello mm",20);
37
38printk("hello kernel\n");
39return0;
40}
41
42static void__exit test_exit(void)//模块卸载函数
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论