GFP Flags
进行内核编程时,往往会时不时看到 “GFP_KERNEL” 这样的东西,这些东西是什么呢,一起来了解一下。
它们实际上被叫做 GFP flags,GFP其实是GetFreePage的缩写,被广泛应用在整个linux系统中,用来指出内存页应该怎样被分配,并不是所有的GFP flag都被所有的内存分配函数所支持,多数用户通常会直接使用GFP_KERNEL,因此很多时候我们往往看到的是GFP_KERNEL。
GFP标志位实际上有很多,他们的本质上是一个bit位,并整体上组成了一个掩码:
#define ___GFP_DMA 0x01u
#define ___GFP_HIGHMEM 0x02u
#define ___GFP_DMA32 0x04u
#define ___GFP_MOVABLE 0x08u
#define ___GFP_RECLAIMABLE 0x10u
#define ___GFP_HIGH 0x20u
......
直接表示掩码位的GFP flags会被层层包装,组合成一些常见的GFP组合:
#define GFP_ATOMIC (__GFP_HIGH|__GFP_ATOMIC|__GFP_KSWAPD_RECLAIM)
内存分配过程中,不能sleep,在无法休眠的情况下使用此标志(必须保持
原子状态),例如中断处理程序、下半部分和持有锁的进程上下文代码。
#define GFP_KERNEL (__GFP_RECLAIM | __GFP_IO | __GFP_FS)
最常用的,无内存可用时可引起休眠
#define GFP_KERNEL_ACCOUNT (GFP_KERNEL | __GFP_ACCOUNT)
与GFP_KERNEL基本相同,不同之处是分配会被kmemcg统计
#define GFP_NOWAIT (__GFP_KSWAPD_RECLAIM)
不允许在直接回收、启动物理IO或调用文件系统回调时暂停
#define GFP_NOIO (__GFP_RECLAIM)
允许直接回收,不允许调用物理IO **避免直接使用**
#define GFP_NOFS (__GFP_RECLAIM | __GFP_IO)
允许直接回收,不允许调用文件系统接口 **避免直接使用**
#define GFP_USER (__GFP_RECLAIM | __GFP_IO | __GFP_FS | __GFP_HARDWALL)
用于用户空间内存分配同时也需要被硬件或内核直接访问的情况,常见被硬件调用
于申请映射到用户空间的缓冲区。
#define GFP_HIGHUSER (GFP_USER | __GFP_HIGHMEM)
用于可以映射到用户空间的用户空间分配,不需要由内核直接访问,
但一旦使用就无法移动,从高端内存分配
......
各种malloc
在申请内存的时候,linux提供了许多种函数,比如kmalloc、vmalloc、kvmalloc等等,难免让人眼花缭乱,那这些malloc究竟有什么区别?
参考:https://www.cnblogs.com/alantu2018/p/9000778.html
Linux将内存空间细分为若干区域,大致如图:
对于提供了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
kmalloc申请的是较小的连续的物理内存,内存物理地址上连续,虚拟地址上也是连续的,使用的是内存分配器slab上的一小片。申请的内存位于物理内存的映射区域。其真正的物理地址,只相差一个固定偏移。可以通过宏_pa(address) { virt_to_phys()} 和__va(address) {phys_to_virt()}转换。
get_free_page
get_free_page()申请的内存是一整页,一页的大小一般是128K。
本质上而言,kmalloc和get_free_page最终调用实现是相同的,只不过是传入的GFP_flag不同,两者申请的内存位于物理内存映射区域,物理上逻辑上均连续,因而逻辑地址与物理地址只有一个固定偏移。
kzmalloc
kzmalloc=kmalloc+zero,即首先调用kmalloc()来申请空间,然后用memset()将空间初始化为0,等效于传入的GFP_flag多一个__GFP_ZERO。
static inline void *kzalloc(size_t size, gfp_t flags)
{
return kmalloc(size, flags | __GFP_ZERO);
}
vmalloc
vmalloc用于申请较大的内存空间,物理上不要求连续,逻辑上连续。vmalloc以字节为单位分配,一般情况下,只有硬件设备才需要物理地址连续的内存,因为硬件设备往往存在于MMU之外,根本不了解虚拟地址,很难通过虚拟地址操作到对应的物理地址。
为了性能上的考虑,内核中一般使用kmalloc,只有在需要获得大块内存时,才使用vmalloc(例如,装载模块到内核时,就把模块装载到vmalloc申请的内存上)。
vmalloc是不需要传入GFP_flag的。
kvmalloc
属于是倚天屠龙🗡,让你不再纠结使用kmalloc还是vmalloc了,上文说到,我们大内存建议vmalloc,小内存建议kmalloc,kvmalloc直接代替你做了判断,大于1页首先尝试用kmalloc,以避免内存碎片,否则用vmalloc,小于1页用kmalloc。
void *kvmalloc_node(size_t size, gfp_t flags, int node)
{
gfp_t kmalloc_flags = flags;
void *ret;
/* 传入的不是GFP_KERNEL直接调用kmalloc
* vmalloc uses GFP_KERNEL for some internal allocations (e.g page tables)
* so the given set of flags has to be compatible.
*/
if ((flags & GFP_KERNEL) != GFP_KERNEL)
return kmalloc_node(size, flags, node);
/* 先尝试对大于1页的用kmalloc,失败就算了
* We want to attempt a large physically contiguous block first because
* it is less likely to fragment multiple larger blocks and therefore
* contribute to a long term fragmentation less than vmalloc fallback.
* However make sure that larger requests are not too disruptive - no
* OOM killer and no allocation failure warnings as we have a fallback.
*/
if (size > PAGE_SIZE) {
kmalloc_flags |= __GFP_NOWARN;
if (!(kmalloc_flags & __GFP_RETRY_MAYFAIL))
kmalloc_flags |= __GFP_NORETRY;
}
// kmalloc尝试
ret = kmalloc_node(size, kmalloc_flags, node);
/* kmalloc成功了,或者小于1页,当大于1页且kmalloc没成时,调用vmalloc
* It doesn't really make sense to fallback to vmalloc for sub page
* requests
*/
if (ret || size <= PAGE_SIZE)
return ret;
return __vmalloc_node_flags_caller(size, node, flags,
__builtin_return_address(0));
}
EXPORT_SYMBOL(kvmalloc_node);
static inline void *kvmalloc(size_t size, gfp_t flags)
{
return kvmalloc_node(size, flags, NUMA_NO_NODE);
}
感觉不管什么时候,都用kvmalloc,挺合适的。
malloc
malloc分配内存和kmalloc相似,但它分配的是用户空间的内存,位于进程的堆中,在用户空间被调用,当用户空间当前内存块足够时,直接分配,当不够时,调用系统调用向内核申请。
malloc推荐阅读链接:
http://t.csdn.cn/lb1VI
https://zhuanlan.zhihu.com/p/367386292
总结
1. GFP_flag 在kmalloc和get_free_page的最终调用中使用,它限制了本次内存分配的一些行为;
2. kmalloc申请较小内存,位于物理空间,物理逻辑双连续,vmalloc申请较大内存,虚拟内存连续但是物理空间不要求,kzmalloc是kmalloc后空间清零,malloc是用户空间函数,分配用户空间内存;
3. kvmalloc智能化地选择了kmalloc或者vmalloc,选择方法是优先尝试kmalloc分配,若失败且需要分配的空间大于1页,则用vmalloc分配。