本文基于linux-3.10的内核

申请内存时需要先确定两个值,分别是high_zoneidx和migratetype。这两个值从哪里获取到呢?都是利用上面传递过来的GFP flag。
对于high_zoneidx是通过gfp_zone函数获取的,而migratetype是通过gfpflags_to_migratetype来获取的。其中gfp_zone是利用的GFP标志位的bit[0-3]这4个bits来确定的,而migratetype是bit[3-4]这两个bits。需要注意他们中间是存在重复的__GFP_MOVABLE标志的,说明它不仅仅用来确定zone也是用来确定migratetype的一个因素。

gfp_zone

 #define ___GFP_DMA      0x01u                                                                                                                                                                    #define ___GFP_HIGHMEM      0x02u#define ___GFP_DMA32        0x04u#define ___GFP_MOVABLE      0x08u

上面是用于寻找zone的4个标志位,每个标志位占用单独一个bit。根据这4个bit相互搭配一个存在16中组合,如下所示:

216  *       bit       result
217  *       =================
218  *       0x0    => NORMAL
219  *       0x1    => DMA or NORMAL
220  *       0x2    => HIGHMEM or NORMAL
221  *       0x3    => BAD (DMA+HIGHMEM)
222  *       0x4    => DMA32 or DMA or NORMAL
223  *       0x5    => BAD (DMA+DMA32)
224  *       0x6    => BAD (HIGHMEM+DMA32)
225  *       0x7    => BAD (HIGHMEM+DMA32+DMA)
226  *       0x8    => NORMAL (MOVABLE+0)
227  *       0x9    => DMA or NORMAL (MOVABLE+DMA)
228  *       0xa    => MOVABLE (Movable is valid only if HIGHMEM is set too)
229  *       0xb    => BAD (MOVABLE+HIGHMEM+DMA)
230  *       0xc    => DMA32 (MOVABLE+HIGHMEM+DMA32)
231  *       0xd    => BAD (MOVABLE+DMA32+DMA)
232  *       0xe    => BAD (MOVABLE+DMA32+HIGHMEM)
233  *       0xf    => BAD (MOVABLE+DMA32+HIGHMEM+DMA)

左边是这些组合得到的值,右边是该组合要申请内存的zone,可以认为上面这个表就是GFP flag到zone的映射表。
内核中规定前三个GFP标志位是互斥的,不能同时设置,而___GFP_MOVABLE则可以搭配设置。因而有一些组合得到的bit值是不符合要求,上表中右边写上BAD的即是这种情况。

GFP和ZONE已然存在上面的这个关系,那么内核的函数实现就是如何快速的查找该表,从GFP组合中快速的寻找ZONE,为了提升性能,内核实现都是利用左移和右移操作来实现,因而构建了一个便于左移和右移查找的一个TABLE:

249  #define GFP_ZONE_TABLE ( \
250     (ZONE_NORMAL << 0 * GFP_ZONES_SHIFT)                       \
251     | (OPT_ZONE_DMA << ___GFP_DMA * GFP_ZONES_SHIFT)               \
252     | (OPT_ZONE_HIGHMEM << ___GFP_HIGHMEM * GFP_ZONES_SHIFT)           \
253     | (OPT_ZONE_DMA32 << ___GFP_DMA32 * GFP_ZONES_SHIFT)               \
254     | (ZONE_NORMAL << ___GFP_MOVABLE * GFP_ZONES_SHIFT)            \
255     | (OPT_ZONE_DMA << (___GFP_MOVABLE | ___GFP_DMA) * GFP_ZONES_SHIFT)    \
256     | (ZONE_MOVABLE << (___GFP_MOVABLE | ___GFP_HIGHMEM) * GFP_ZONES_SHIFT)\
257     | (OPT_ZONE_DMA32 << (___GFP_MOVABLE | ___GFP_DMA32) * GFP_ZONES_SHIFT)\
258 )

左边是zone的index,右边是匹配的GFP组合值,加入zone index要么为0,要么为1,那么把zone的index值按照GFP组合值大小来进行左移操作即可把它保存到对应的bit上,该bit上的0和1分别表示不同的两个zone。然而现实是zone index它可能是大于1的值,因而需要GFP_ZONES_SHIFT个bits来保存zone的index,所以右边统一乘以GFP_ZONES_SHIFT。

通过这种方式就创建了一个信息查找表,而查找函数就是如下的函数:

277 static inline enum zone_type gfp_zone(gfp_t flags)
278 {279     enum zone_type z;
280     int bit = (__force int) (flags & GFP_ZONEMASK);
281
282     z = (GFP_ZONE_TABLE >> (bit * GFP_ZONES_SHIFT)) &
283                      ((1 << GFP_ZONES_SHIFT) - 1);
284     VM_BUG_ON((GFP_ZONE_BAD >> bit) & 1);
285     return z;
286 }

这个函数就是通过GFP flag利用左移和右移操作快速的查找到对应的ZONE index。

有了这个ZONE index就一定会从这个zone中分配内存了吗?还是想的太天真了,对于内存的申请,内核中存在一个fallback列表,当我们从第一选择zone中去查找空闲内存,如果发现该zone中的空闲内存不足,那么此时就返回失败吗?

当然不是,内核会尝试从fallback zone中去查找空闲内存。比如内核根据GFP flag得到一个zone index,对应的ZONE是Normal Zone,而此时Normal Zone中的空闲内存不足申请失败,但是DMA zone中还存在很多空闲内存,那么此时内核就会从DMA zone中去再次去尝试申请。

这个内存申请的fallback顺序为:

MOVABLE=>HIGHMEM=>NORMAL=>DMA32=>DMA

到这里还涉及到一个问题,那么对于每个Zone来说,如果都可以被其他Zone来申请,大家都可以互相申请,那么区分这些zone还有什么意义呢?

实际上并不是如此,为了保证每个zone中的内存不会被其他zone全部申请走,内核针对每个zone定义了一个保留内存:

zone1 : lowmem_reserve[]: 0 0 126976 126976

它是一个数组,比如上面的数组表示系统存在4个zone,假设为zone1,zone2,zone3,zone4。
当zone3或者zone4尝试从zone1中申请时需要考虑的预留内存是126976个页面。也就是说zone3判断zone1中是否存在足够的空闲内存用于申请时,需要把这个预留内存给去除掉。具体的计算方法如下:

2360 static bool __zone_watermark_ok(struct zone *z, int order, unsigned long mark,
2361               int classzone_idx, int alloc_flags, long free_pages)
2362 {
2363     /* free_pages my go negative - that's OK */
2364     long min = mark;
2365     long lowmem_reserve = z->lowmem_reserve[classzone_idx];
2366     int o;
2367     long free_cma = 0;
2368
2369     free_pages -= (1 << order) - 1;
2370     if (alloc_flags & ALLOC_HIGH)
2371         min -= min / 2;
2372     if (alloc_flags & ALLOC_HARDER)
2373         min -= min / 4;
2374 #ifdef CONFIG_CMA
2375     /* If allocation can't use CMA areas don't use free CMA pages */
2376     if (!(alloc_flags & ALLOC_CMA))
2377         free_cma = zone_page_state(z, NR_FREE_CMA_PAGES);
2378 #endif
2379
2380     if (free_pages - free_cma <= min + lowmem_reserve)
2381         return false;
2382     for (o = 0; o < order; o++) {
2383         /* At the next order, this order's pages become unavailable */
2384         free_pages -= z->free_area[o].nr_free << o;
2385
2386         /* Require fewer higher order pages to be free */
2387         min >>= 1;
2388
2389         if (free_pages <= min)
2390             return false;
2391     }
2392     return true;
2393 }

通过这个函数来判断某一个zone中针对该GFP申请的内存,是否存在空闲页面来完成此次申请,可以看到其中就使用了lowmem_reserve来综合考虑作为判断结果。

numa


static struct page *
2580 get_page_from_freelist(gfp_t gfp_mask, nodemask_t *nodemask, unsigned int order,
2581         struct zonelist *zonelist, int high_zoneidx, int alloc_flags,
2582         struct zone *preferred_zone, int migratetype)
2583 {2584     struct zoneref *z;
2585     struct page *page = NULL;
2586     int classzone_idx;
2587     struct zone *zone;
2588     nodemask_t *allowednodes = NULL;/* zonelist_cache approximation */
2589     int zlc_active = 0;     /* set if using zonelist_cache */
2590     int did_zlc_setup = 0;      /* just call zlc_setup() one time */
2591
2592     classzone_idx = zone_idx(preferred_zone);
2593 zonelist_scan:
2594     /*
2595      * Scan zonelist, looking for a zone with enough free.
2596      * See also cpuset_zone_allowed() comment in kernel/cpuset.c.
2597      */
2598     for_each_zone_zonelist_nodemask(zone, z, zonelist,
2599                         high_zoneidx, nodemask) {

最后会尝试在对应nodemask上的zonelist上查找空闲页面,当nodemask为NULL时会扫描所有的node。

如何从GFP确定最后申请的内存来自哪个zone?相关推荐

  1. 通过伙伴系统申请内核内存的函数有哪些?

    本文转自:http://blog.chinaunix.net/space.php?uid=22566367&do=blog&id=2747207 在物理页面管理上实现了基于区的伙伴系统 ...

  2. kmalloc最大能申请多少内存?

    1. 概述 本文主要分析kmalloc接口申请内存的大小情况,用于记录kmalloc分配内存的过程. 内核版本:Linux 4.9 2.分析记录 针对kmalloc最大能申请多少内存,网上众说纷纭,意 ...

  3. linux 用户进程结束后 malloc申请的内存会自动释放吗,进程退出后malloc的内存是否会被释放?

    当一个进程退出后,不管是正常退出或者是异常退出,操作系统都会释放这个进程的资源.包括这个进程分配的内存,打开的文件等等. 内存泄露的前提是进程一直在运行:进程一旦退出,所占的整个虚拟内存都被销毁,所有 ...

  4. 【三分钟学习FFMPEG一个知识点】FFMPEG关于avio_alloc_context申请使用内存释放问题

    问题: 使用ffmpeg发现av_malloc申请的内存最后不能用av_free函数释放,会崩溃. 代码示例: unsigned char * iobuffer = NULL; iobuffer = ...

  5. Yarn申请的内存的精确计算(转载+应用到自己的情况中)

    这篇博客主要是利用[1]中的流程来尝试估算自己在运行spark on yarn模式时申请的内存数值. 一个spark任务会产生几个Container? count = ExecutorNum + 1 ...

  6. 关于用函数指针参数申请动态内存的问题

    今天在写一个Binary Search Tree的程序时,发现其插入有问题,下面是插入程序,每次插入完成后,节点还是NULL. template<typename Object> void ...

  7. c语言链表错误,C语言创建链表错误之通过指针参数申请动态内存实例分析

    本文实例讲述了C语言创建链表中经典错误的通过指针参数申请动态内存,分享给大家供大家参考之用.具体实例如下: #include #include // 用malloc要包含这个头文件 typedef s ...

  8. C语言为四维数组申请动态内存空间的方法(二)

    尝试了用堆栈的方式为四维数组申请动态内存空间,并将申请内存的操作封装成了子函数,方便在主程序中使用.希望对大家有用.代码如下: #include <stdio.h> #include &l ...

  9. C语言为四维数组申请动态内存空间的方法(一)

    尝试了用结构体为四维数组申请动态内存空间,希望对大家有用.代码如下: #include <stdio.h> #include <stdlib.h>typedef struct ...

最新文章

  1. 关于自注意力机制的思考
  2. 高级指令——hostname指令【作用:操作服务器的主机名(读取、设置)】、id指令【查看一个用户的一些基本信息(包含用户id,用户组id,附加组id…)】、whoami指令、ps -ef指令
  3. 虚拟现实大会ChinaVR2015报告之-From Visual Content to Virtual Reality Data-driven Intelligence Production
  4. 谁在为网易云音乐2亿用户的即时通讯保驾护航?
  5. 在c语言中large的作用,输入两个数组,调用large函数比较,计数,输出统计结果...
  6. 三大器之------生成器
  7. Tensorflow2.0.0版本和Keras2.4.3不兼容
  8. 谨以此送给所有单身的MM
  9. 春节回来,陪你去看星河云!
  10. 变频器压频比的正确设置方法
  11. SetupFactory使用
  12. 抖音热门音乐整理合集歌曲打包分享
  13. 行人重识别实验笔记3-JDAI fast-reid项目配置
  14. win7共享wifi自动获取不到ip地址的解决办法
  15. 每周分享第 23 期
  16. 自动化测试常见面试问题
  17. 在C和C++定义最大最小整数值
  18. 直流负载线和交流负载线静态工作点分析,与非线性失真类型分析
  19. 图片裁剪cropperjs的使用
  20. Java篇第三回——运算符、表达式与语句(C不好的也快来)

热门文章

  1. 写宏遇到的问题:warning: backslash and newline separated by space - C
  2. IDEA 2018 3.4 激活破解方法
  3. java程序员自己的图片转文字OCR识图工具
  4. Java编程:悲观锁、乐观锁的区别及使用场景
  5. 批量删除Redis数据库中的Key
  6. 怎样提高团队管理能力3
  7. Java 多线程学习笔记(狂神)
  8. 什么是消息队列(MQ)
  9. Linux Ubuntu系统设置成中文语言
  10. 视觉惯性组合导航技术最新综述:应用优势、主要类别及一种视觉惯性组合导航无人系统开发验证平台分享