Linux内核中内存分配函数
1.原理说明
Linux内核 中采 用了一种同时适用于32位和64位系统的内 存分页模型,对于32位系统来说,两级页表足够用了,而在x86_64系 统中,用到了四级页表,如图2-1所示。四级页表分别为:
* 页全局目录(Page Global Directory)
* 页上级目录(Page Upper Directory)
* 页中间目录(Page Middle Directory)
* 页表(Page Table)
页全局目录包含若干页上级目录的地址,页上级目录又依次包含若干页中间目录的地址,而页中间目录又包含若干页表的地址,每一个页表项指 向一个页框。Linux中采用4KB大小的 页框作为标准的内存分配单元。
多级分页目录结构
1.1.伙伴系统算法
在实际应用中,经常需要分配一组连续的页框,而频繁地申请和释放不同大小的连续页框,必然导致在已分配页框的内存块中分散了许多小块的 空闲页框。这样,即使这些页框是空闲的,其他需要分配连续页框的应用也很难得到满足。
为了避免出现这种情况,Linux内核中引入了伙伴系统算法(buddy system)。把所有的空闲页框分组为11个 块链表,每个块链表分别包含大小为1,2,4,8,16,32,64,128,256,512和1024个连续页框的页框块。最大可以申请1024个连 续页框,对应4MB大小的连续内存。每个页框块的第一个页框的物理地址 是该块大小的整数倍。
假设要申请一个256个页框的块,先从256个页框的链表中查找空闲块,如果没有,就去512个 页框的链表中找,找到了则将页框块分为2个256个 页框的块,一个分配给应用,另外一个移到256个页框的链表中。如果512个页框的链表中仍没有空闲块,继续向1024个页 框的链表查找,如果仍然没有,则返回错误。
页框块在释放时,会主动将两个连续的页框块合并为一个较大的页框块。
1.2.slab分 配器
slab分配器源于 Solaris 2.4 的 分配算法,工作于物理内存页框分配器之上,管理特定大小对象的缓存,进行快速而高效的内存分配。
slab分配器为每种使用的内核对象建立单独的缓冲区。Linux 内核已经采用了伙伴系统管理 物理内存页框,因此 slab分配器直接工作于伙伴系 统之上。每种缓冲区由多个 slab 组成,每个 slab就是一组连续的物理内存页框,被划分成了固定数目的对象。根据对象大小的不同,缺省情况下一个 slab 最多可以由 1024个页框构成。出于对齐 等其它方面的要求,slab 中分配给对象的内存可能大于用户要求的对象实际大小,这会造成一定的 内存浪费。
2.常用内存分配函数
2.1.__get_free_pages
unsigned long __get_free_pages(gfp_t gfp_mask, unsigned int order)
__get_free_pages函数是最原始的内存分配方式,直接从伙伴系统中获取原始页框,返回值为第一个页框的起始地址。__get_free_pages在实现上只是封装了alloc_pages函 数,从代码分析,alloc_pages函数会分配长度为1<<order的 连续页框块。order参数的最大值由include/Linux/Mmzone.h文 件中的MAX_ORDER宏决定,在默认的2.6.18内 核版本中,该宏定义为10。也就是说在理论上__get_free_pages函 数一次最多能申请1<<10 * 4KB也就是4MB的 连续物理内存。但是在实际应用中,很可能因为不存在这么大量的连续空闲页框而导致分配失败。在测试中,order为10时分配成功,order为11则返回错误。
2.2.kmem_cache_alloc
struct kmem_cache *kmem_cache_create(const char *name, size_t size,
size_t align, unsigned long flags,
void (*ctor)(void*, struct kmem_cache *, unsigned long),
void (*dtor)(void*, struct kmem_cache *, unsigned long))
void *kmem_cache_alloc(struct kmem_cache *c, gfp_t flags)
kmem_cache_create/ kmem_cache_alloc是基于slab分配器的一种内存分配方式,适用于反复分配释放同一大小内存块的场合。首先用kmem_cache_create创建一个高速缓存区域,然后用kmem_cache_alloc从 该高速缓存区域中获取新的内存块。 kmem_cache_alloc一次能分配的最大内存由mm/slab.c文件中的MAX_OBJ_ORDER宏 定义,在默认的2.6.18内核版本中,该宏定义为5, 于是一次最多能申请1<<5 * 4KB也就是128KB的 连续物理内存。分析内核源码发现,kmem_cache_create函数的size参数大于128KB时会调用BUG()。测试结果验证了分析结果,用kmem_cache_create分 配超过128KB的内存时使内核崩溃。
2.3.kmalloc
void *kmalloc(size_t size, gfp_t flags)
kmalloc是内核中最常用的一种内存分配方式,它通过调用kmem_cache_alloc函 数来实现。kmalloc一次最多能申请的内存大小由include/Linux/Kmalloc_size.h的 内容来决定,在默认的2.6.18内核版本中,kmalloc一 次最多能申请大小为131702B也就是128KB字 节的连续物理内存。测试结果表明,如果试图用kmalloc函数分配大于128KB的内存,编译不能通过。
2.4.vmalloc
void *vmalloc(unsigned long size)
前面几种内存分配方式都是物理连续的,能保证较低的平均访问时间。但是在某些场合中,对内存区的请求不是很频繁,较高的内存访问时间也 可以接受,这是就可以分配一段线性连续,物理不连续的地址,带来的好处是一次可以分配较大块的内存。图3-1表 示的是vmalloc分配的内存使用的地址范围。vmalloc对 一次能分配的内存大小没有明确限制。出于性能考虑,应谨慎使用vmalloc函数。在测试过程中, 最大能一次分配1GB的空间。
Linux内核部分内存分布
2.5.dma_alloc_coherent
void *dma_alloc_coherent(struct device *dev, size_t size,
ma_addr_t *dma_handle, gfp_t gfp)
DMA是一种硬件机制,允许外围设备和主存之间直接传输IO数据,而不需要CPU 的参与,使用DMA机制能大幅提高与设备通信 的 吞吐量。DMA操作中,涉及到CPU高速缓 存和对应的内存数据一致性的问题,必须保证两者的数据一致,在x86_64体系结构中,硬件已经很 好的解决了这个问题, dma_alloc_coherent和__get_free_pages函数实现差别不大,前者实际是调用__alloc_pages函 数来分配内存,因此一次分配内存的大小限制和后者一样。__get_free_pages分配的内 存同样可以用于DMA操作。测试结果证明,dma_alloc_coherent函 数一次能分配的最大内存也为4M。
2.6.ioremap
void * ioremap (unsigned long offset, unsigned long size)
ioremap是一种更直接的内存“分配”方式,使用时直接指定物理起始地址和需要分配内存的大小,然后将该段 物理地址映射 到内核地址空间。ioremap用到的物理地址空间都是事先确定的,和上面的几种内存 分配方式并不太一样,并不是分配一段新的物理内存。ioremap多用于设备驱动,可以让CPU直接访问外部设备的IO空间。ioremap能映射的内存由原有的物理内存空间决定,所以没有进行测试。
2.7.Boot Memory
如果要分配大量的连续物理内存,上述的分配函数都不能满足,就只能用比较特殊的方式,在Linux内 核引导阶段来预留部分内存。
2.7.1.在内核引导时分配内存
void* alloc_bootmem(unsigned long size)
可以在Linux内核引导过程中绕过伙伴系统来分配大块内存。使用方法是在Linux内核引导时,调用mem_init函数之前 用alloc_bootmem函数申请指定大小的内存。如果需要在其他地方调用这块内存,可以将alloc_bootmem返回的内存首地址通过EXPORT_SYMBOL导 出,然后就可以使用这块内存了。这种内存分配方式的缺点是,申请内存的代码必须在链接到内核中的代码里才能使用,因此必须重新编译内核,而且内存管理系统 看不到这部分内存,需要用户自行管理。测试结果表明,重新编译内核后重启,能够访问引导时分配的内存块。
2.7.2.通过内核引导参数预留顶部内存
在Linux内核引导时,传入参数“mem=size”保留顶部的内存区间。比如系统有256MB内 存,参数“mem=248M”会预留顶部的8MB内存,进入系统后可以调用ioremap(0xF800000,0x800000)来申请这段内存。
3.几种分配函数的比较
分配原理 |
最大内存 |
其他 |
|
__get_free_pages |
直接对页框进行操作 |
4MB |
适用于分配较大量的连续物理内存 |
kmem_cache_alloc |
基于slab机制实现 |
128KB |
适合需要频繁申请释放相同大小内存块时使用 |
kmalloc |
基于kmem_cache_alloc实现 |
128KB |
最常见的分配方式,需要小于页框大小的内存时可以使用 |
vmalloc |
建立非连续物理内存到虚拟地址的映射 |
物理不连续,适合需要大内存,但是对地址连续性没有要求的场合 |
|
dma_alloc_coherent |
基于__alloc_pages实现 |
4MB |
适用于DMA操 作 |
ioremap |
实现已知物理地址到虚拟地址的映射 |
适用于物理地址已知的场合,如设备驱动 |
|
alloc_bootmem |
在启动kernel时,预留一段内存,内核看不见 |
小于物理内存大小,内存管理要求较高 |
对于提供了MMU(存储管理器,辅助操作系统进行内存管理,提供虚实地址转换等硬件支持)的处理器而言,Linux提供了复杂的存储管理系统,使得进程所能访问的内存达到4GB。
进程的4GB内存空间被人为的分为两个部分--用户空间与内核空间。用户空间地址分布从0到3GB(PAGE_OFFSET,在0x86中它等于0xC0000000),3GB到4GB为内核空间,如下图:
|
内核空间中,从3G到vmalloc_start这段地址是物理内存映射区域(该区域中包含了内核镜像、物理页框表mem_map等等),比如我们使用的VMware虚拟系统内存是160M,那么3G~3G+160M这片内存就应该映射物理内存。在物理内存映射区之后,就是vmalloc区域。对于160M的系统而言,vmalloc_start位置应在3G+160M附近(在物理内存映射区与vmalloc_start期间还存在一个8M的gap来防止跃界),vmalloc_end的位置接近4G(最后位置系统会保留一片128k大小的区域用于专用页面映射),如下图:
kmalloc和get_free_page申请的内存位于物理内存映射区域,而且在物理上也是连续的,它们与真实的物理地址只有一个固定的偏移,因此存在较简单的转换关系,virt_to_phys()可以实现内核虚拟地址转化为物理地址:
#define __pa(x) ((unsigned long)(x)-PAGE_OFFSET) extern inline unsigned long virt_to_phys(volatile void * address) { return __pa(address); } |
上面转换过程是将虚拟地址减去3G(PAGE_OFFSET=0XC000000)。
与之对应的函数为phys_to_virt(),将内核物理地址转化为虚拟地址:
#define __va(x) ((void *)((unsigned long)(x)+PAGE_OFFSET)) extern inline void * phys_to_virt(unsigned long address) { return __va(address); } |
virt_to_phys()和phys_to_virt()都定义在include/asm-i386/io.h中。
而vmalloc申请的内存则位于vmalloc_start~vmalloc_end之间,与物理地址没有简单的转换关系,虽然在逻辑上它们也是连续的,但是在物理上它们不要求连续。
我们用下面的程序来演示kmalloc、get_free_page和vmalloc的区别:
#include <linux/module.h> #include <linux/slab.h> #include <linux/vmalloc.h> MODULE_LICENSE("GPL"); unsigned char *pagemem; unsigned char *kmallocmem; unsigned char *vmallocmem; int __init mem_module_init(void) kmallocmem = (unsigned char*)kmalloc(100, 0); vmallocmem = (unsigned char*)vmalloc(1000000); return 0; void __exit mem_module_exit(void) module_init(mem_module_init); |
我们的系统上有160MB的内存空间,运行一次上述程序,发现pagemem的地址在0xc7997000(约3G+121M)、kmallocmem地址在0xc9bc1380(约3G+155M)、vmallocmem的地址在0xcabeb000(约3G+171M)处,符合前文所述的内存布局。
#include <linux/slab.h> void *kmalloc(size_t size, int flags);
给 kmalloc 的第一个参数是要分配的块的大小. 第 2 个参数, 分配标志, 非常有趣, 因为它以几个方式控制 kmalloc 的行为.
最一般使用的标志, GFP_KERNEL, 意思是这个分配((内部最终通过调用 __get_free_pages 来进行, 它是 GFP_ 前缀的来源) 代表运行在内核空间的进程而进行的. 换句话说, 这意味着调用函数是代表一个进程在执行一个系统调用. 使用 GFP_KENRL 意味着 kmalloc 能够使当前进程在少内存的情况下睡眠来等待一页. 一个使用 GFP_KERNEL 来分配内存的函数必须, 因此, 是可重入的并且不能在原子上下文中运行. 当当前进程睡眠, 内核采取正确的动作来定位一些空闲内存, 或者通过刷新缓存到磁盘或者交换出去一个用户进程的内存.
GFP_KERNEL 不一直是使用的正确分配标志; 有时 kmalloc 从一个进程的上下文的外部调用. 例如, 这类的调用可能发生在中断处理, tasklet, 和内核定时器中. 在这个情况下, 当前进程不应当被置为睡眠, 并且驱动应当使用一个 GFP_ATOMIC 标志来代替. 内核正常地试图保持一些空闲页以便来满足原子的分配. 当使用 GFP_ATOMIC 时, kmalloc 能够使用甚至最后一个空闲页. 如果这最后一个空闲页不存在, 但是, 分配失败.
其他用来代替或者增添 GFP_KERNEL 和 GFP_ATOMIC 的标志, 尽管它们 2 个涵盖大部分设备驱动的需要. 所有的标志定义在 <linux/gfp.h>, 并且每个标志用一个双下划线做前缀, 例如 __GFP_DMA. 另外, 有符号代表常常使用的标志组合; 这些缺乏前缀并且有时被称为分配优先级. 后者包括:
- GFP_ATOMIC
-
用来从中断处理和进程上下文之外的其他代码中分配内存. 从不睡眠.
- GFP_KERNEL
-
内核内存的正常分配. 可能睡眠.
- GFP_USER
-
用来为用户空间页来分配内存; 它可能睡眠.
- GFP_HIGHUSER
-
如同 GFP_USER, 但是从高端内存分配, 如果有. 高端内存在下一个子节描述.
- GFP_NOIO
- GFP_NOFS
-
这个标志功能如同 GFP_KERNEL, 但是它们增加限制到内核能做的来满足请求. 一个 GFP_NOFS 分配不允许进行任何文件系统调用, 而 GFP_NOIO 根本不允许任何 I/O 初始化. 它们主要地用在文件系统和虚拟内存代码, 那里允许一个分配睡眠, 但是递归的文件系统调用会是一个坏注意.
上面列出的这些分配标志可以是下列标志的相或来作为参数, 这些标志改变这些分配如何进行:
- __GFP_DMA
-
这个标志要求分配在能够 DMA 的内存区. 确切的含义是平台依赖的并且在下面章节来解释.
- __GFP_HIGHMEM
-
这个标志指示分配的内存可以位于高端内存.
- __GFP_COLD
-
正常地, 内存分配器尽力返回"缓冲热"的页 -- 可能在处理器缓冲中找到的页. 相反, 这个标志请求一个"冷"页, 它在一段时间没被使用. 它对分配页作 DMA 读是有用的, 此时在处理器缓冲中出现是无用的. 一个完整的对如何分配 DMA 缓存的讨论看"直接内存存取"一节在第 1 章.
- __GFP_NOWARN
-
这个很少用到的标志阻止内核来发出警告(使用 printk ), 当一个分配无法满足.
- __GFP_HIGH
-
这个标志标识了一个高优先级请求, 它被允许来消耗甚至被内核保留给紧急状况的最后的内存页.
- __GFP_REPEAT
- __GFP_NOFAIL
- __GFP_NORETRY
-
这些标志修改分配器如何动作, 当它有困难满足一个分配. __GFP_REPEAT 意思是" 更尽力些尝试" 通过重复尝试 -- 但是分配可能仍然失败. __GFP_NOFAIL 标志告诉分配器不要失败; 它尽最大努力来满足要求. 使用 __GFP_NOFAIL 是强烈不推荐的; 可能从不会有有效的理由在一个设备驱动中使用它. 最后, __GFP_NORETRY 告知分配器立即放弃如果得不到请求的内存.
-
kmalloc 能够分配的内存块的大小有一个上限. 这个限制随着体系和内核配置选项而变化. 如果你的代码是要完全可移植, 它不能指望可以分配任何大于 128 KB. 如果你需要多于几个 KB, 但是, 有个比 kmalloc 更好的方法来获得内存, 我们在本章后面描述.
-
这方面的原因:
-
kmalloc并不直接从分页机制中获得空闲页面而是从slab页面分配器那儿获得需要的页面,slab的实现代码限制了最大分配的大小为128k,即131072bytes,理论上你可以通过更改slab.c中的 cache_sizes数组中的最大值使得kmalloc可以获得更大的页面数,不知道有没有甚么副效应或者没有必要这样做,因为获取较大内存的方法有很多,想必128k是经验总结后的合适值。
-
alloc_page( )可以分配的最大连续页面是4M吧。MAX_ORDER =10
-
46 static inline struct page * alloc_pages(unsigned int gfp_mask, unsigned int order)
47 {
48 /*
49 * Gets optimized away by the compiler.
50 */
51 if (order >= MAX_ORDER)
52 return NULL;
53 return _alloc_pages(gfp_mask, order);
54 }
alloc_pages最大分配页面数为512个,则可用内存数最大为2^9*4K=2M
Linux内核中内存分配函数相关推荐
- 深入分析linux内核的内存分配函数devm_kzalloc
在分析驱动代码的时候,经常会遇到使用devm_kzalloc()为一个设备分配一片内存的情况.devm_kzalloc()是内核用来分配内存的函数,同样可以分配内存的内核函数还有devm_kmallo ...
- Linux 基础知识(2)---Linux内核空间内存申请函数kmalloc、kzalloc、vmalloc的区别
Linux内核空间内存申请函数kmalloc.kzalloc.vmalloc的区别 kzalloc与kmalloc区别 这个函数就是原来的两个函数的整合 , 即原来我们每次申请内存的时候都会这么 ...
- Linux内核中内存管理相关配置项的详细解析3
接前一篇文章:Linux内核中内存管理相关配置项的详细解析2 5. 2:1 compression allocator (zbud) 对应配置变量为:CONFIG_ZBUD. 此项默认为选中(如果前一 ...
- Linux内核空间内存申请函数kmalloc、kzalloc、vmalloc的区别
Table of Contents kmalloc() kzalloc() vmalloc() 总结 内核中的内存申请:kmalloc.vmalloc.kzalloc.kcalloc.get_free ...
- linux 进城 io字节,(2)linux内核之内存分配与IO口操作
/***************************************************分配内存******************************************** ...
- linux内核中的hook函数详解,linux内核中的hook函数详解
在编写linux内核中的网络模块时,用到了钩子函数也就是hook函数.现在来看看linux是如何实现hook函数的. 先介绍一个结构体: struct nf_hook_ops,这个结构体是实现钩子函数 ...
- linux hook 任意内核函数,linux内核中的hook函数详解
在编写linux内核中的网络模块时,用到了钩子函数也就是hook函数.现在来看看linux是如何实现hook函数的. 先介绍一个结构体: struct nf_hook_ops,这个结构体是实现钩子函数 ...
- linux内核中的睡眠函数*delay、*sleep
目录 一.睡眠函数种类 1.原子上下文 2.非原子上下文 二.使用环境 1.使用环境的不同,选择不同的延时 2.驱动机制不同 3.内核中的计算函数执行的函数 三.实测两类函数的延时以及原因 1.测试系 ...
- Linux内核中kzalloc分配内存时用的参数GFP_KERNEL详解
简介 GFP(Get Free Pages缩写)在include/linux/gfp.h中定义. GFP_KERNEL 是内核内存分配时最常用的,无内存可用时可引起休眠. GFP_ATOMIC 用来从 ...
最新文章
- java 理解break,continue,return
- IIC总线的原理与Verilog实现
- 微软Visual Studio 2012软件功能介绍
- linux英文包安装教程视频,Linux源码包安装过程讲解
- Java中的序列问题-2
- 语义分割之PointRend论文与源码解读
- typescript step by step interface class
- java影院座位订票代码_基于jsp的影院订票-JavaEE实现影院订票 - java项目源码
- ecshop首页调用团购信息产品购买人数
- 代理ip按功能分哪几类?
- Visual C++ 2010如何解决程序运行闪退问题
- 使用python对单幅图像进行数据增并保存增强后的结果
- htc系统Android 7.1,HTC太强大,被誉为刷机之王,一路升到安卓7
- matlab 蠓虫,蠓虫分类.doc.doc
- f2fs学习笔记 - 7. f2fs文件打开
- matlab变量与常量、数据类型
- bzoj 1917: [Ctsc2010]星际旅行 树形dp解决树上网络流
- Tomcat工作原理详解
- js 实现前端数据导出为excel表格
- (翻译)Few-Shot Object Detection with Attention-RPN and Multi-Relation Detector具有注意力RPN和多关系检测器的小样本目标检测
热门文章
- 常见的USB VID
- 宏正ATEN发行全新高端式IP-Based Cat 5 KVM多电脑切换器
- python爬虫去重_Python网络爬虫(7):URL去重
- 寻找亚马逊测评师邮箱_关于亚马逊测评一些普及
- 软件设计师-计算机网络(刷题笔记)
- AMD EPYC架构
- 两年数据对比柱形图_【系列课程】用Excel进行数据可视化组合图表的制作lt;二gt;...
- narwal机器人_Narwal云鲸智能扫拖机器人,会自己洗拖布
- [论文]欠驱动水下机器人的平面轨迹规划与跟踪控制设计
- 硕士论文要不要附matlab程序,论文必须要有附录吗_毕业论文附录一定要写吗_毕业论文中附录是不是必须要写的...