Linux | 一些和内存管理有关的小知识

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将内存空间细分为若干区域,大致如图:

b0c52250b6a0d5d84a06d2fedb9d47e6.png

对于提供了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分配。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值