• 如何从GFP确定最后申请的内存来自哪个zone?


    本文基于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尝试从本zone1中申请时需要考虑的预留内存。也就是说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。

    from: https://blog.csdn.net/rikeyone/article/details/109638783

  • 相关阅读:
    robotframework框架
    pytest系列(四)- pytest+allure+jenkins
    robotframework框架
    接口测试时遇到 java 代码加密请求数据,用 python 的我该怎么办?
    selenium原理应用
    pytest系列(一):什么是单元测试界的高富帅?
    python appium搭建app自动化测试环境
    python selenium
    python3.4 + pycharm 环境安装 + pycharm使用
    requests(三):json请求中中文乱码处理
  • 原文地址:https://www.cnblogs.com/aspirs/p/13983026.html
Copyright © 2020-2023  润新知