Linux内核内存分配函数之kmalloc
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Linux内核内存分配函数之kmalloc相关的知识,希望对你有一定的参考价值。
参考技术A本文介绍Linux内核内存分配函数 kmalloc 。
注:
1) __builtin_constant_p 编译器内联函数,判断传入参数是否为常量。如果是变量,直接调用 __kmalloc 函数。
2) KMALLOC_MAX_CACHE_SIZE 表示系统创建 slab cache 的最大值为8K,定义如下:
RockPI 4A Linux内核使能 ARM64_4K_PAGES 。可使用命令 getconf 查看 page size ,具体如下:
3) kmalloc 一般用于小内存分配,RockPI 4A Linux内核基于 slub ( CONFIG_SLUB=y )实现。系统先用页分配器分配以页为最小单位的连续物理地址,然后 kmalloc 在此基础上根据调用者的需要进行切分。如果分配超过 KMALLOC_MAX_CACHE_SIZE ,则使用 kmalloc_large 进行大内存分配,即调用页分配器分配内存。( 后续仔细学习 )
4) kmalloc 分配的内存在物理上连续,可用于DMA设备。 vmalloc 分配的内存是线性地址连续,物理地址不连续,不可用于DMA设备。
size :分配内存的大小,以字节为单位;
flags :分配内存的类型,包括:
1) GFP_USER :可能会引起休眠,用于为用户空间分配内存。
2) GFP_KERNEL :可能会引起休眠,用于内核内存正常分配。
3) GFP_ATOMIC :不会引起休眠,可用于中断处理程序中内存分配。
4) GFP_HIGHUSER :从高端内存中分配内存。
5) GFP_DMA :用于DMA内存分配。
6)其它类型见: include/linux/gfp.h
GFP 可理解为 get free page 。
返回分配内存的首地址,是虚拟地址(线性地址)。
正所谓有借有还,再借不难。每次 kmalloc ,都要有对应的内存释放函数 kfree 。定义文件: mm/slub.c ,如下:
Linux设备驱动程序 之 kmalloc
原型
kmalloc的原型如下:
1 void *kmalloc(size_t size, gfp_t flags)
第一个参数是要分配的块的大小,第二个参数是分片标志;
flags标志
最常用的标志是GFP_KERNEL,表示内存分配(最终总是调用get_free_page来实现实际的分配,这就是GFP_前缀的由来)是代表在内核空间的进程执行的;换句话说,这意味着调用它的函数正代表某个进程执行系统调用;使用GFP_KERNEL允许在kmalloc在空闲内存较少的时候把当前进程转入休眠以等待一个页面;因此,使用GFP_KERNEL分配内存的函数必须是可重入的;在当前进程休眠时,内核会采取适当的行动,或者把缓冲区的内容刷到硬盘上,或者从搞一个用户进程换出内存,已获得一个内存页面;
GFP_KERNEL分配标志并不是始终适用,有时kmalloc是在进程上下文之外被调用的,例如中断处理程序,tasklet以及内核定时器中调用;这种情况下current进程就不该休眠,驱动程序应该换用GFP_ATOMIC标志;内核通常会为原子性的分配预留一些空闲页面;使用GFP__ATOMIC标志时,kmalloc甚至可以用掉最后一个空闲页面;不过如果连最后一页都没有了,分配就返回失败;
除了GFP_KERNEL和GFP_ATOMIC外,还有一些其他标志可用于替换或者补充这两个标志,不过这两个标志已经可以满足大多数驱动程序的需要了;
下面罗列这些标记:
GFP_ATOMIC-在中断处理程序或者其他运行与进程上下文之外的代码中分配内存,不能休眠;
GFP_KERNEL-内核内存的通常分配方法,可能引起休眠;
GFP_USER-为用户空间页分配内存,可能会休眠;
GFP_HIGHUSER-类似GFP_USER,不过如果有高端内存的话,就从那里分配;
GFP_NOIO GFP_NOFS-这两个标志类似GFP_KERNEL,但是为内核分配内存的工作方式添加了一些限制,具有GFP_NOFS标志的分配不允许执行任何文件系统调用,而GFP_NOIO禁止任何IO的初始化;这两个标志主要在文件系统和虚拟内存代码中使用,这项代码中的分配内存可休眠,但不应该发生递归的文件系统调用;
上面列出的分配标志可以和下面的标志“或”起来使用;下面这些标志控制如何进行分配:
__GFP_DMA-标志请求分配发生在可进行DMA的内存区段中;
__GFP_HIGHMEM-表明要分配的内存可位于高端内存;
__GFP_COLD-通常,内存分配器会试图返回缓存热页面,即可在处理器缓存中找到的页面;相反,这个标志请求尚未使用的冷页面;对用于DMA读取的页面分配,可以使用这个标志,因为这种情况下,页面存在于处理器缓存中没有多大帮助;
__GFP_NOWARN-该标志很少使用;它可以避免内核在无法满足分配请求的时候产生经过;
__GFP_HIGH-该标志标记了一个高优先级的请求,它允许为紧急状况而消耗有内核保留的最后一个页面;
__GFP_REPEAT、__GFP_NOFAIL、__GFP_NORETRY-上述标志告诉分配器在满足分配请求而遇到困难时应该采取何种行动;__GFP_REPEAT表示努力再尝试一次,它会重新尝试分配,当仍有可能失败;__GFP_NOFAIL标志告诉分配器始终不返回失败,它会努力满足分配请求;不鼓励使用这个标志;__GFP_NORETRY告诉分配器,如果请求的内存不可获得,就立即返回;
内存区段
__GFP_DMA和__GFP_HIGHMEM的使用与平台相关,尽管在所有平台上都可以使用这两个标志;
Linux内核把内存分成三个区段:可用于DMA的内存,常规内存以及高端内存;通常的内存分配都放生在常规内存区,但通过设置上面的标志可以把请求在其他区段中分配;其思路是每种计算平台都必须知道如何把自己特定的内存范围归类到这三个区段中,而不是认为所有的RAM都一样;
可用于DMA的内存是指存在于特别地址范围内的内存,外设可以利用这些内存执行DMA访问;在大多数健全的系统上,所有内存都位于这一区段;在x86平台上,DMA区段是RAM的前16MB;PCI设备无此限制;
高端内存是32位平台为了访问大量的内存而存在的一种机制;如果不首先完成一些特殊的映射,我们就无法从内核中直接访问这些内存,因此通常较难处理;但是,如果驱动程序要使用大量的内存,那么在能够使用高端内存的大系统上可以工作的更好;
当一个页面为了满足kmalloc的要求被分配时,内核会创建一个内存区段的列表以供搜索;如果指定了__GFP_DMA标志,则只有DMA区段会被搜索;如果低端地址上没有可用内存,就会分配失败;如果没有指定特定的标志,则常规区段和DMA区段都会被搜索;而如果设置了__GFP_HIGHMEM标志,则所有三区段都会被搜索以及获取一个空闲页面,然而要注意的是,kmalloc不能分配高端内存;
size参数
内核负责管理系统物理内存,物理内存只能按页面进行分配;其结果是kmalloc和典型的用户空间的malloc在实现上有很大的差别;简单的基于堆的分配技术会遇到麻烦,因为页面编辑的处理成为一个很棘手的问题;因此内核使用了特殊的基于页的分配技术,以及最佳地利用系统RAM;
Linux处理内存分配的方法是,创建一系列的内存池对象,每个池中的内存块大小是固定一致的;处理分配请求时,就直接在包含有足够大的内存块的池中传递一个整块请求者;
驱动程序开发应该牢记的就是内核值分配一些预定义的,固定大小的字节数组;如果申请任意数量的内存,那么得到的很可能会多一些,最多会到申请数量的两倍;另外,kmalloc能处理的最小的内存块是32或者64,到底是哪个则取决于当前体系结构使用的页面大小;
对kmalloc能够分配的内存块的大小,存在一个上限;这个限制随着体系架构的不同以及内存配置选项的不同而变化;如果希望代码有完整的可移植性,则不应该分配大于128K的内存;但是,如果希望获得多余几千字节的内存,则最好是用除kmalloc之外的内存获取方法;
以上是关于Linux内核内存分配函数之kmalloc的主要内容,如果未能解决你的问题,请参考以下文章
Linux 内核 内存管理内存管理架构 ④ ( 内存分配系统调用过程 | 用户层 malloc free | 系统调用层 brk mmap | 内核层 kmalloc | 内存管理流程 )
详解linux内核中的各种内存分配函数:kmallocvmallocslab__get_free_pagesmempoll_alloc