linux设备驱动归纳总结(五):1.在内核空间分配内存

xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

一般的,用户空间使用函数malloc在堆上分配内存空间,同样的,在内核空间同样有一套类似的函数来分配空间。下面的知识会涉及页式管理的内存机制,如果不懂的要先复习一下,在S3C2440数据手册的MMU部分有介绍。

xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

一、内核空间和用户空间有什么不同

学c语言的时候应该学过,从用户空间看,每个进程都傻乎乎的以为自己有4G的内存空间,其中位于高地址(3G-4G)的1G空间给内核用,另外的3G(0-3G)都是它一个人独占的。所以用户空间很慷慨的把3G的空间分了好几个区域,如堆、栈、代码段等。其中,malloc()分配的空间位于堆,而程序中的自动变量,如你在函数内定义的“int
i”,它是放在栈上,同时。用户空间的栈是可变栈,即随着数据的增多,对应函数的栈空间也会增多。

位下)的定长栈。出于这样的原因,大的数据结构就不能在栈中分配,只能请求内核分配新的空间来存放数据,如函数kmalloc()。

xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

二、内存的基本单位是字节吗?

在介绍分配内存空间的函数前,我们还要了解一下内存是怎么被划分的。

内核不仅知道用户空间中看到的1G内核空间是假的,它还知道实际的物理内存是多少(我的开发板是64M)。所以,内核的其中一个任务就是,当这段虚假内存中的数据需要调用时,内核把这段虚拟内存与实际的物理内存对应上,运行完后又把两段内存的对应关系撤销掉给另外的虚拟内存用。

既然知道虚拟内存与物理内存的关系,那它们是怎么对应的,难道是一个一个字节?如果这样子做的话内核肯定觉得崩溃。

位的系统中,一页的大小为4KB。所以,64M的物理内存将被分为16384个页。每一个物理页对应地用一个struct
page来维护,注意,该结构体是用来维护物理页,而不是虚拟也,结构体记录该页是否被使用,对应的虚拟地址是多少等信息。

个区。

位的地址空间寻址,出于这总访问限制,linux把前16MB划分为ZONE_DMA——用于直接内存访问(MDA)。

在x86体系里,高于896M的内存空间称为高端内存,这段内存区域的页和普通的内存页操作后有差异,这段区域划分为ZONE_HIGHMEM。

剩下的,加载这两段区域之间的就是我们平时用的普通内存区域——ZONE_NORMAL。

这这里要注意一下:

)这些分区是指linux自己分的,当然,如果普通分区不够用,当然也可以占用其他区的空间。

)分区的大小是根据体系结构而定的,一般的ARM下,ZONE_NORMAL就是所有的可用内存区域。

xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

三、分配内存时使用的标记gfp_mask

在讲如何分配内存之前,先讲一下分配内存时将会用到的gfp_mask。简单地讲,这个标记指定了分配内存时的要求。具体分三类:

行为修饰符:表示内核应当如何分配内存,如指定不能休眠等。

区修饰符:指定内存将要分配到上面讲的三个区中的哪一个。

类型标记:这包含了上面两种修饰符(或运算),这些标记是为了让用户更好地去使用。

标记有很多,我这里不一一介绍,需要的可以自己查阅《linux内核设计与实现(第三版)》P238页。这里我讲两个常用的类型标记:

)GFP_KERNEL:最常用的标记,用于可睡眠的进程上下文。

)GFP_ATOMIC:使用了这个标记,内存分配函数不会引起随眠。

)GFP_USER:当需要给用户空间分配内存空间时使用该标记。

xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

四、分配内存的第一种方法——按页分配

这是内核提供的一种请求内存的底层机制,都是以页为单位分配内存。以下函数包含在

这分为两个步骤:

、请求内核分配页,获得物理页对应的结构体struct
page:

static inline struct page * alloc_pages(gfp_t gfp_mask, unsigned int order)

使用:

该函数用于申请(1<<other)——< font="" style="word-wrap:
break-word;">即2的other次方个连续物理页,gfp_mask用于指定分配的方式,一般使用GFP_KERNEL或GFP_ATOMIC。注意:函数会引起睡眠

返回值:

成功返回一个指针,指向这连续物理页的第一个struct page结构体,失败返回NULL。

、分配页后还不能直接用,需要得到该页对应的虚拟地址:

void *page_address(struct page *page)

其实这个函数就是获取page的成员virtual,但千万不要直接访问,需要使用这个函数。函数返回的是物理页对应的虚拟地址,注意,如果你申请了多个物理页,分配的物理页是连续的,对应的虚拟地址也是连续的。

上面的两个步骤其实可以合成一个函数:

unsigned long __get_free_pages(gfp_t gfp_mask, unsigned int order)

这个函数的传参和alloc_pages的一样,不过它直接返回申请的物理页对应的虚拟地址。

当然,无论使用上面的哪种方法,当内存不用时,需要调用函数释放:

、如果你使用上面的第一种方法:

void __free_pages(struct
page *page, unsigned int order)

、如果你使用的是第二种方法:

void free_pages(unsigned long addr, unsigned int order)

下面来个程序:

/*5th_mm/5th_mm_1/1st/test.c*/

1 #include

2 #include

3

4 #include

5

6 struct page *p;

7 char *s;

8

9 static int __init test_init(void) //模块初始化函数

10 {

11 unsigned long virt, phys;

12

13 #define SWITCH
0 //通过定义这个来切换校验这两种不同的方法

14 #if SWITCH

15 //alloc 2 pages

16 p
= alloc_pages(GFP_KERNEL, 1);

17 if (NULL == p){ //必须检验错误

18 printk("alloc page error!\n");

19 return - ENOMEM;

20 }

21 s
= page_address(p);

22 #else

23 s
= (char *)__get_free_pages(GFP_KERNEL, 1);

24 if (NULL == s){

25 printk("alloc page error!\n");

26 return - ENOMEM;

27 }

28 #endif

29

30 phys = __pa((unsigned long)s); //通过虚拟地址获得对应的物理地址

31 virt = (unsigned long)__va(phys); //通过物理地址获得对应的虚拟地址

32 printk("virtual, s>[%p]\n", s); //打印获得的虚拟地址

33 printk("[%p]\n", (void *)phys); //打印对应的物理地址

34 printk("[%p]\n", (void *)virt); //再打印虚拟地址,其实就是分配函数返回的地址

35

36 memcpy(s, "hello mm", 20);

37

38 printk("hello kernel\n");

39 return 0;

40 }

41

42 static void __exit test_exit(void) //模块卸载函数

43 {

44 #if SWITCH

45 __free_pages(p, 1);

46 #else

47 free_pages((unsigned long)s, 1);

48 #endif

49

50 printk("good bye kernel\n");

51 }

52

53 module_init(test_init);

54 module_exit(test_exit);

55

56 MODULE_LICENSE("GPL");

57 MODULE_AUTHOR("xoao bai");

58 MODULE_VERSION("v0.1");

再检验一下:

[root: 1st]# insmod test.ko

virtual, s>[c3968000] //虚拟地址

[33968000] //对应的实际地址

[c3968000]

hello kernel [hello mm] //打印出来了,hello美眉!

上面我分配了两页后什么也没做,当然,如果你要是只分配一页,内核有贴心函数:

#define alloc_page(gfp_mask) alloc_pages(gfp_mask, 0)

#define __get_free_page(gfp_mask) __get_free_pages((gfp_mask),0)

另外还有一个函数,不仅给你分配一页空间,还帮你清零了,特别适用于给用户空间分配内存。

nsigned long get_zeroed_page(gfp_t gfp_mask)

xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

五、分配内存的第二种方法——kmalloc()

kmalloc()的用法和malloc差不多,只是多了一个我前面介绍的标志gfp_flag。

上函数,需要包含头文件

void *kmalloc(size_t size, gfp_t flags)

成功返回指向这块内存的地址(虚拟地址),失败返回NULL。这里注意一下,返回的内存大小不一定是size,因为内存的分配是基于页来分配的,有时需要地址对齐之类,所有分配的内存地址可能比size大。函数同样会引起睡眠,如果不能睡眠需要使用GFP_ATOMIC。

分配的内存必须释放,使用函数:

void kfree(const void *objp)

上个程序:

/*5th_mm_1/2nd/test.c*/

1 #include

2 #include

3

4 #include

5

6 char *s;

7

8 static int __init test_init(void) //模块初始化函数

9 {

10 s = kmalloc(20, GFP_KERNEL);

11 memcpy(s, "hello mm", 20);

12

13 printk("hello kernel [%s]\n", s);

14 return 0;

15 }

16

17 static void __exit test_exit(void) //模块卸载函数

18 {

19 kfree(s);

20 printk("good bye kernel\n");

21 }

22

23 module_init(test_init);

24 module_exit(test_exit);

25

26 MODULE_LICENSE("GPL");

27 MODULE_AUTHOR("xoao bai");

28 MODULE_VERSION("v0.1");

再验证一下:

[root: 2nd]# insmod test.ko

hello kernel [hello mm] //又打印出来了

xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

六、其他的内存分配函数——vmalloc

有时候,内核不一定会有很大的一块连续物理内存,这时候kmalloc就不能处理这种情况了,它只能是分配连续的物理内存。需要用以下的函数vmalloc。

分配:

void * vmalloc(unsigned long size);

同样的,成功返回首地址,失败返回NULL,切记这个函数会引起睡眠。而且没有标志可选。

释放:

void vfree(void *addr);

当指定的size没有真够大的连续空间时,这个函数就会像捡破烂一样,东捡一块西捡一块,凑成满足大小的物理内存,并连在一起形成连续的虚拟内存再把首地址返回,所以这个函数的操作很麻烦,出于性能的考虑,能不用的话尽量不用。

xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

七、slab层

为了方便一些频繁被使用的数据,内核有了slab层的概念。大概意思就是,在告诉内存空间里面,内核定义多了slab(其实就是一页),这些页可以预先定义成用来存放什么数据。这样的话就方便了,当有这样的数据要存放,进程就可以申请放在slab层,这样的话就省去了内存分配和释放的操作。具体的介绍请看《linux内核设计与实现(第三版)》P245。

xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

八、总结

这节讲了内核是如何管理和分配内存了,还重点介绍了最常用的内存分配方法kmalloc。

xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

源代码: 5th_mm_1.rar

【Linux开发】linux设备驱动归纳总结(五):1.在内核空间分配内存的更多相关文章

  1. linux设备驱动归纳总结(五):1.在内核空间分配内存【转】

    本文转载自:http://blog.chinaunix.net/uid-25014876-id-79134.html linux设备驱动归纳总结(五):1.在内核空间分配内存 xxxxxxxxxxxx ...

  2. linux设备驱动归纳总结

    前言: (总结已经基本写完,这段时间我会从新排版和修正.错误总会有的,望能指正!) 前段时间学习了嵌入式驱动,趁着没开始找工作,这段时间我会每天抽出时间来复习. 我的总结是根据学习时的笔记(李杨老师授 ...

  3. 【Linux】linux设备驱动归纳总结

    前言: (总结已经基本写完,这段时间我会从新排版和修正.错误总会有的,望能指正!) 前段时间学习了嵌入式驱动,趁着没开始找工作,这段时间我会每天抽出时间来复习. 我的总结是根据学习时的笔记(李杨老师授 ...

  4. 【Linux开发】linux设备驱动归纳总结(五):2.操作硬件——IO内存

    linux设备驱动归纳总结(五):2.操作硬件--IO内存 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx ...

  5. 【Linux开发】linux设备驱动归纳总结(五):3.操作硬件——IO静态映射

    linux设备驱动归纳总结(五):3.操作硬件--IO静态映射 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx ...

  6. 【Linux开发】linux设备驱动归纳总结(五):4.写个简单的LED驱动

    linux设备驱动归纳总结(五):4.写个简单的LED驱动 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx ...

  7. linux设备驱动归纳总结(五):3.操作硬件——IO静态映射【转】

    本文转载自:http://blog.chinaunix.net/uid-25014876-id-83299.html linux设备驱动归纳总结(五):3.操作硬件——IO静态映射 xxxxxxxxx ...

  8. 【Linux开发】linux设备驱动归纳总结(六):1.中断的实现

    linux设备驱动归纳总结(六):1.中断的实现 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx ...

  9. 【Linux开发】linux设备驱动归纳总结(六):2.分享中断号

    linux设备驱动归纳总结(六):2.分享中断号 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx ...

随机推荐

  1. Hadoop-No.7之行键

    和哈希表类比,HBase中的行键类似于哈希表中的键.要构造一个良好的HBase模式,关键之一就是选择一个合适的行键. 1 记录检索 行键是HBase中检索记录所使用的键.HBase记录含有的列在数量上 ...

  2. VC++ ID号如何分配

    自动生成的,有默认规则. #define _APS_NEXT_RESOURCE_VALUE 137 //下一个资源ID(插入的一些对话框.图片.图标等) #define _APS_NEXT_COMMA ...

  3. java+大文件上传+下载

    我们平时经常做的是上传文件,上传文件夹与上传文件类似,但也有一些不同之处,这次做了上传文件夹就记录下以备后用. 这次项目的需求: 支持大文件的上传和续传,要求续传支持所有浏览器,包括ie6,ie7,i ...

  4. [HNOI2004][bzoj1211] 树的计数(prufer序列)

    1211: [HNOI2004]树的计数 Time Limit: 10 Sec  Memory Limit: 162 MBSubmit: 3432  Solved: 1295[Submit][Stat ...

  5. 9030PCI CAN驱动开发点滴

    1.配置EEPROM. 使用PlxMon打开9030,基本修改Spacex(0,1,2,3), Chip selectx(0,1,2,3), 中断状态INTCSR(0x74), 其他(0x78),详细 ...

  6. objdump命令解析

    [objdump] 相关链接: 实例分析objdump反汇编用法 - 在路上 - CSDN博客  https://blog.csdn.net/u012247418/article/details/80 ...

  7. JavaWeb-SpringBoot(抖音)_一、抖音项目制作

    JavaWeb-SpringBoot(抖音)_一.抖音项目制作 传送门 JavaWeb-SpringBoot(抖音)_二.服务器间通讯 传送门 JavaWeb-SpringBoot(抖音)_三.抖音项 ...

  8. 2016 ICPC 大连网络赛 部分题解

    先讲1007,有m个人,n种石头,将n种石头分给m个人,每两个人之间要么是朋友关系,要么是敌人关系,朋友的话他们必须有一种相同颜色的石头,敌人的话他们必须所有石头的颜色都不相同.另外,一个人可以不拥有 ...

  9. OSI7层模型

    为什么来写一个osi7层模型的随便呐? 因为自己对osi模型一点不熟悉 只知道里面的协议 要学渗透必先学习osi 这是一个osi7层的大体图片 接下来我们会对每一层进行详细的解答 这是五层模型 接下来 ...

  10. 标准库函数gets()和puts()

    问题: 用标准库函数gets()和puts()实现字符串的输入输出.函数gets()用于从键盘读入一个字符串(包括空格符).它仅以回车符作为分隔符.函数gets()中的参数应是一个已存储字符串的字符数 ...