一、Linux伙伴系统分配器

伙伴系统分配器大体上分为两类。__get_free_pages()类函数返回分配的第一个页面的线性地址;alloc_pages()类函数返回页面描述符地址。不管以哪种函数进行分配,最终会调用alloc_pages()进行分配页面。

为清楚了解其分配制度,先给个伙伴系统数据的存储框图

也就是每个order对应一个free_area结构,free_area以不同的类型以链表的方式存储这些内存块。

二、主分配函数

下面我们来看这个函数(在UMA模式下)

[cpp] view plaincopyprint?
  1. #define alloc_pages(gfp_mask, order) \
  2. alloc_pages_node(numa_node_id(), gfp_mask, order)
[cpp] view plaincopyprint?
  1. static inline struct page *alloc_pages_node(int nid, gfp_t gfp_mask,
  2. unsigned int order)
  3. {
  4. /* Unknown node is current node */
  5. if (nid
  6. nid = numa_node_id();
  7. return __alloc_pages(gfp_mask, order, node_zonelist(nid, gfp_mask));
  8. }
[cpp] view plaincopyprint?
  1. static inline struct page *
  2. __alloc_pages(gfp_t gfp_mask, unsigned int order,
  3. struct zonelist *zonelist)
  4. {
  5. return __alloc_pages_nodemask(gfp_mask, order, zonelist, NULL);
  6. }

上层分配函数__alloc_pages_nodemask()

[cpp] view plaincopyprint?
  1. /*
  2. * This is the 'heart' of the zoned buddy allocator.
  3. */
  4. /*上层分配器运用了各种方式进行*/
  5. struct page *
  6. __alloc_pages_nodemask(gfp_t gfp_mask, unsigned int order,
  7. struct zonelist *zonelist, nodemask_t *nodemask)
  8. {
  9. enum zone_type high_zoneidx = gfp_zone(gfp_mask);
  10. struct zone *preferred_zone;
  11. struct page *page;
  12. /* Convert GFP flags to their corresponding migrate type */
  13. int migratetype = allocflags_to_migratetype(gfp_mask);
  14. gfp_mask &= gfp_allowed_mask;
  15. /*调试用*/
  16. lockdep_trace_alloc(gfp_mask);
  17. /*如果__GFP_WAIT标志设置了,需要等待和重新调度*/
  18. might_sleep_if(gfp_mask & __GFP_WAIT);
  19. /*没有设置对应的宏*/
  20. if (should_fail_alloc_page(gfp_mask, order))
  21. return NULL;
  22. /*
  23. * Check the zones suitable for the gfp_mask contain at least one
  24. * valid zone. It's possible to have an empty zonelist as a result
  25. * of GFP_THISNODE and a memoryless node
  26. */
  27. if (unlikely(!zonelist->_zonerefs->zone))
  28. return NULL;
  29. /* The preferred zone is used for statistics later */
  30. /* 英文注释所说*/
  31. first_zones_zonelist(zonelist, high_zoneidx, nodemask, &preferred_zone);
  32. if (!preferred_zone)
  33. return NULL;
  34. /* First allocation attempt */
  35. /*从pcp和伙伴系统中正常的分配内存空间*/
  36. page = get_page_from_freelist(gfp_mask|__GFP_HARDWALL, nodemask, order,
  37. zonelist, high_zoneidx, ALLOC_WMARK_LOW|ALLOC_CPUSET,
  38. preferred_zone, migratetype);
  39. if (unlikely(!page))/*如果上面没有分配到空间,调用下面函数慢速分配,允许等待和回收*/
  40. page = __alloc_pages_slowpath(gfp_mask, order,
  41. zonelist, high_zoneidx, nodemask,
  42. preferred_zone, migratetype);
  43. /*调试用*/
  44. trace_mm_page_alloc(page, order, gfp_mask, migratetype);
  45. return page;
  46. }

三、从pcp和伙伴系统中正常的分配内存空间

函数get_page_from_freelist()

[cpp] view plaincopyprint?
  1. /*
  2. * get_page_from_freelist goes through the zonelist trying to allocate
  3. * a page.
  4. */
  5. /*为分配制定内存空间,遍历每个zone*/
  6. static struct page *
  7. get_page_from_freelist(gfp_t gfp_mask, nodemask_t *nodemask, unsigned int order,
  8. struct zonelist *zonelist, int high_zoneidx, int alloc_flags,
  9. struct zone *preferred_zone, int migratetype)
  10. {
  11. struct zoneref *z;
  12. struct page *page = NULL;
  13. int classzone_idx;
  14. struct zone *zone;
  15. nodemask_t *allowednodes = NULL;/* zonelist_cache approximation */
  16. int zlc_active = 0;     /* set if using zonelist_cache */
  17. int did_zlc_setup = 0;      /* just call zlc_setup() one time */
  18. /*zone对应的下标*/
  19. classzone_idx = zone_idx(preferred_zone);
  20. zonelist_scan:
  21. /*
  22. * Scan zonelist, looking for a zone with enough free.
  23. * See also cpuset_zone_allowed() comment in kernel/cpuset.c.
  24. */
  25. /*遍历每个zone,进行分配*/
  26. for_each_zone_zonelist_nodemask(zone, z, zonelist,
  27. /*在UMA模式下不成立*/              high_zoneidx, nodemask) {
  28. if (NUMA_BUILD && zlc_active &&
  29. !zlc_zone_worth_trying(zonelist, z, allowednodes))
  30. continue;
  31. if ((alloc_flags & ALLOC_CPUSET) &&
  32. !cpuset_zone_allowed_softwall(zone, gfp_mask))
  33. goto try_next_zone;
  34. BUILD_BUG_ON(ALLOC_NO_WATERMARKS
  35. /*需要关注水位*/
  36. if (!(alloc_flags & ALLOC_NO_WATERMARKS)) {
  37. unsigned long mark;
  38. int ret;
  39. /*从flags中取的mark*/
  40. mark = zone->watermark[alloc_flags & ALLOC_WMARK_MASK];
  41. /*如果水位正常,从本zone中分配*/
  42. if (zone_watermark_ok(zone, order, mark,
  43. classzone_idx, alloc_flags))
  44. goto try_this_zone;
  45. if (zone_reclaim_mode == 0)/*如果上面检查的水位低于正常值,且没有设置页面回收值*/
  46. goto this_zone_full;
  47. /*在UMA模式下下面函数直接返回0*/
  48. ret = zone_reclaim(zone, gfp_mask, order);
  49. switch (ret) {
  50. case ZONE_RECLAIM_NOSCAN:
  51. /* did not scan */
  52. goto try_next_zone;
  53. case ZONE_RECLAIM_FULL:
  54. /* scanned but unreclaimable */
  55. goto this_zone_full;
  56. default:
  57. /* did we reclaim enough */
  58. if (!zone_watermark_ok(zone, order, mark,
  59. classzone_idx, alloc_flags))
  60. goto this_zone_full;
  61. }
  62. }
  63. try_this_zone:/*本zone正常水位*/
  64. /*先从pcp中分配,然后不行的话再从伙伴系统中分配*/
  65. page = buffered_rmqueue(preferred_zone, zone, order,
  66. gfp_mask, migratetype);
  67. if (page)
  68. break;
  69. this_zone_full:
  70. if (NUMA_BUILD)/*UMA模式为0*/
  71. zlc_mark_zone_full(zonelist, z);
  72. try_next_zone:
  73. if (NUMA_BUILD && !did_zlc_setup && nr_online_nodes > 1) {
  74. /*
  75. * we do zlc_setup after the first zone is tried but only
  76. * if there are multiple nodes make it worthwhile
  77. */
  78. allowednodes = zlc_setup(zonelist, alloc_flags);
  79. zlc_active = 1;
  80. did_zlc_setup = 1;
  81. }
  82. }
  83. if (unlikely(NUMA_BUILD && page == NULL && zlc_active)) {
  84. /* Disable zlc cache for second zonelist scan */
  85. zlc_active = 0;
  86. goto zonelist_scan;
  87. }
  88. return page;/*返回页面*/
  89. }

主分配函数

[cpp] view plaincopyprint?
  1. /*
  2. * Really, prep_compound_page() should be called from __rmqueue_bulk().  But
  3. * we cheat by calling it from here, in the order > 0 path.  Saves a branch
  4. * or two.
  5. */
  6. /*先考虑从pcp中分配空间,当order大于0时再考虑从伙伴系统中分配*/
  7. static inline
  8. struct page *buffered_rmqueue(struct zone *preferred_zone,
  9. struct zone *zone, int order, gfp_t gfp_flags,
  10. int migratetype)
  11. {
  12. unsigned long flags;
  13. struct page *page;
  14. int cold = !!(gfp_flags & __GFP_COLD);/*如果分配参数指定了__GFP_COLD标志,则设置cold标志*/
  15. int cpu;
  16. again:
  17. cpu  = get_cpu();
  18. if (likely(order == 0)) {/*分配一个页面时,使用pcp*/
  19. struct per_cpu_pages *pcp;
  20. struct list_head *list;
  21. /*找到zone对应的pcp*/
  22. pcp = &zone_pcp(zone, cpu)->pcp;
  23. list = &pcp->lists[migratetype];/*pcp中对应类型的list*/
  24. /* 这里需要关中断,因为内存回收过程可能发送核间中断,强制每个核从每CPU
  25. 缓存中释放页面。而且中断处理函数也会分配单页。 */
  26. local_irq_save(flags);
  27. if (list_empty(list)) {/*如果pcp中没有页面,需要补充*/
  28. /*从伙伴系统中获得batch个页面
  29. batch为一次分配的页面数*/
  30. pcp->count += rmqueue_bulk(zone, 0,
  31. pcp->batch, list,
  32. migratetype, cold);
  33. /*如果链表仍然为空,申请失败返回*/
  34. if (unlikely(list_empty(list)))
  35. goto failed;
  36. }
  37. /* 如果分配的页面不需要考虑硬件缓存(注意不是每CPU页面缓存)
  38. ,则取出链表的最后一个节点返回给上层*/
  39. if (cold)
  40. page = list_entry(list->prev, struct page, lru);
  41. else/* 如果要考虑硬件缓存,则取出链表的第一个页面,这个页面是最近刚释放到每CPU
  42. 缓存的,缓存热度更高 */
  43. page = list_entry(list->next, struct page, lru);
  44. list_del(&page->lru);/*从pcp中脱离*/
  45. pcp->count--;/*pcp计数减一*/
  46. }
  47. else {/*当order为大于1时,不从pcp中分配,直接考虑从伙伴系统中分配*/
  48. if (unlikely(gfp_flags & __GFP_NOFAIL)) {
  49. /*
  50. * __GFP_NOFAIL is not to be used in new code.
  51. *
  52. * All __GFP_NOFAIL callers should be fixed so that they
  53. * properly detect and handle allocation failures.
  54. *
  55. * We most definitely don't want callers attempting to
  56. * allocate greater than order-1 page units with
  57. * __GFP_NOFAIL.
  58. */
  59. WARN_ON_ONCE(order > 1);
  60. }
  61. /* 关中断,并获得管理区的锁*/
  62. spin_lock_irqsave(&zone->lock, flags);
  63. /*从伙伴系统中相应类型的相应链表中分配空间*/
  64. page = __rmqueue(zone, order, migratetype);
  65. /* 已经分配了1 <
  66. __mod_zone_page_state(zone, NR_FREE_PAGES, -(1 <
  67. spin_unlock(&zone->lock);/* 这里仅仅打开自旋锁,待后面统计计数设置完毕后再开中断*/
  68. if (!page)
  69. goto failed;
  70. }
  71. /*事件统计计数,调试*/
  72. __count_zone_vm_events(PGALLOC, zone, 1 <
  73. zone_statistics(preferred_zone, zone);
  74. local_irq_restore(flags);/*恢复中断*/
  75. put_cpu();
  76. VM_BUG_ON(bad_range(zone, page));
  77. /* 这里进行安全性检查,并进行一些善后工作。
  78. 如果页面标志破坏,返回的页面出现了问题,则返回试图分配其他页面*/
  79. if (prep_new_page(page, order, gfp_flags))
  80. goto again;
  81. return page;
  82. failed:
  83. local_irq_restore(flags);
  84. put_cpu();
  85. return NULL;
  86. }

3.1 pcp缓存补充

从伙伴系统中获得batch个页面,batch为一次分配的页面数rmqueue_bulk()函数。

[cpp] view plaincopyprint?
  1. /*
  2. * Obtain a specified number of elements from the buddy allocator, all under
  3. * a single hold of the lock, for efficiency.  Add them to the supplied list.
  4. * Returns the number of new pages which were placed at *list.
  5. */
  6. /*该函数返回的是1<
  7. 处理中调用,其他地方没看到,order为0
  8. 也就是说返回的是页面数,加入的链表为
  9. 对应调用pcp的链表*/
  10. static int rmqueue_bulk(struct zone *zone, unsigned int order,
  11. unsigned long count, struct list_head *list,
  12. int migratetype, int cold)
  13. {
  14. int i;
  15. spin_lock(&zone->lock);/* 上层函数已经关了中断,这里需要操作管理区,获取管理区的自旋锁 */
  16. for (i = 0; i /* 重复指定的次数,从伙伴系统中分配页面*/
  17. /* 从伙伴系统中取出页面 */
  18. struct page *page = __rmqueue(zone, order, migratetype);
  19. if (unlikely(page == NULL))/*分配失败*/
  20. break;
  21. /*
  22. * Split buddy pages returned by expand() are received here
  23. * in physical page order. The page is added to the callers and
  24. * list and the list head then moves forward. From the callers
  25. * perspective, the linked list is ordered by page number in
  26. * some conditions. This is useful for IO devices that can
  27. * merge IO requests if the physical pages are ordered
  28. * properly.
  29. */
  30. if (likely(cold == 0))/*根据调用者的要求,将页面放到每CPU缓存链表的头部或者尾部*/
  31. list_add(&page->lru, list);
  32. else
  33. list_add_tail(&page->lru, list);
  34. set_page_private(page, migratetype);/*设置private属性为页面的迁移类型*/
  35. list = &page->lru;
  36. }
  37. /*递减管理区的空闲页面计数*/
  38. __mod_zone_page_state(zone, NR_FREE_PAGES, -(i <
  39. spin_unlock(&zone->lock);/*释放管理区的子璇锁*/
  40. return i;
  41. }

3.2 从伙伴系统中取出页面

__rmqueue()函数

[cpp] view plaincopyprint?
  1. /*
  2. * Do the hard work of removing an element from the buddy allocator.
  3. * Call me with the zone->lock already held.
  4. */
  5. /*采用两种范式试着分配order个page*/
  6. static struct page *__rmqueue(struct zone *zone, unsigned int order,
  7. int migratetype)
  8. {
  9. struct page *page;
  10. retry_reserve:
  11. /*从指定order开始从小到达遍历,优先从指定的迁移类型链表中分配页面*/
  12. page = __rmqueue_smallest(zone, order, migratetype);
  13. /*
  14. * 如果满足以下两个条件,就从备用链表中分配页面:
  15. *        快速流程没有分配到页面,需要从备用迁移链表中分配.
  16. *        当前不是从保留的链表中分配.因为保留的链表是最后可用的链表,
  17. *  不能从该链表分配的话,说明本管理区真的没有可用内存了.
  18. */
  19. if (unlikely(!page) && migratetype != MIGRATE_RESERVE) {
  20. /*order从大到小遍历,从备用链表中分配页面*/
  21. page = __rmqueue_fallback(zone, order, migratetype);
  22. /*
  23. * Use MIGRATE_RESERVE rather than fail an allocation. goto
  24. * is used because __rmqueue_smallest is an inline function
  25. * and we want just one call site
  26. */
  27. if (!page) {/* 备用链表中没有分配到页面,从保留链表中分配页面了 */
  28. migratetype = MIGRATE_RESERVE;
  29. goto retry_reserve;/* 跳转到retry_reserve,从保留的链表中分配页面*/
  30. }
  31. }
  32. /*调试代码*/
  33. trace_mm_page_alloc_zone_locked(page, order, migratetype);
  34. return page;
  35. }

3.2.1 从指定的迁移类型链表中分配页面

从指定order开始从小到达遍历,优先从指定的迁移类型链表中分配页面__rmqueue_smallest(zone, order, migratetype);

[cpp] view plaincopyprint?
  1. /*
  2. * Go through the free lists for the given migratetype and remove
  3. * the smallest available page from the freelists
  4. */
  5. /*从给定的order开始,从小到大遍历;
  6. 找到后返回页面基址,合并分割后的空间*/
  7. static inline
  8. struct page *__rmqueue_smallest(struct zone *zone, unsigned int order,
  9. int migratetype)
  10. {
  11. unsigned int current_order;
  12. struct free_area * area;
  13. struct page *page;
  14. /* Find a page of the appropriate size in the preferred list */
  15. for (current_order = order; current_order
  16. area = &(zone->free_area[current_order]);/*得到指定order的area*/
  17. /*如果area指定类型的伙伴系统链表为空*/
  18. if (list_empty(&area->free_list[migratetype]))
  19. continue;/*查找下一个order*/
  20. /*对应的链表不空,得到链表中数据*/
  21. page = list_entry(area->free_list[migratetype].next,
  22. struct page, lru);
  23. list_del(&page->lru);/*从伙伴系统中删除;*/
  24. rmv_page_order(page);/*移除page中order的变量*/
  25. area->nr_free--;/*空闲块数减一*/
  26. /*拆分、合并*/
  27. expand(zone, page, order, current_order, area, migratetype);
  28. return page;
  29. }
  30. return NULL;
  31. }

伙伴系统内存块拆分和合并

看一个辅助函数,用于伙伴系统中内存块的拆分、合并

[cpp] view plaincopyprint?
  1. /*
  2. * The order of subdivision here is critical for the IO subsystem.
  3. * Please do not alter this order without good reasons and regression
  4. * testing. Specifically, as large blocks of memory are subdivided,
  5. * the order in which smaller blocks are delivered depends on the order
  6. * they're subdivided in this function. This is the primary factor
  7. * influencing the order in which pages are delivered to the IO
  8. * subsystem according to empirical testing, and this is also justified
  9. * by considering the behavior of a buddy system containing a single
  10. * large block of memory acted on by a series of small allocations.
  11. * This behavior is a critical factor in sglist merging's success.
  12. *
  13. * -- wli
  14. */
  15. /*此函数主要用于下面这种情况:
  16. 分配函数从high中分割出去了low大小的内存;
  17. 然后要将high留下的内存块合并放到伙伴系统中;*/
  18. static inline void expand(struct zone *zone, struct page *page,
  19. int low, int high, struct free_area *area,
  20. int migratetype)
  21. {
  22. unsigned long size = 1 <
  23. while (high > low) {/*因为去掉了low的大小,所以最后肯定剩下的
  24. 是low的大小(2的指数运算)*/
  25. area--;/*减一到order减一的area*/
  26. high--;/*order减一*/
  27. size >>= 1;/*大小除以2*/
  28. VM_BUG_ON(bad_range(zone, &page[size]));
  29. /*加到指定的伙伴系统中*/
  30. list_add(&page[size].lru, &area->free_list[migratetype]);
  31. area->nr_free++;/*空闲块加一*/
  32. set_page_order(&page[size], high);/*设置相关order*/
  33. }
  34. }

3.2.2 从备用链表中分配页面

[cpp] view plaincopyprint?
  1. /* Remove an element from the buddy allocator from the fallback list */
  2. static inline struct page *
  3. __rmqueue_fallback(struct zone *zone, int order, int start_migratetype)
  4. {
  5. struct free_area * area;
  6. int current_order;
  7. struct page *page;
  8. int migratetype, i;
  9. /* Find the largest possible block of pages in the other list */
  10. /* 从最高阶搜索,这样可以尽量的将其他迁移列表中的大块分割,避免形成过多的碎片 */
  11. for (current_order = MAX_ORDER-1; current_order >= order;
  12. --current_order) {
  13. for (i = 0; i
  14. /*回调到下一个migratetype*/
  15. migratetype = fallbacks[start_migratetype][i];
  16. /* MIGRATE_RESERVE handled later if necessary */
  17. /* 本函数不处理MIGRATE_RESERVE类型的迁移链表,如果本函数返回NULL,
  18. 则上层函数直接从MIGRATE_RESERVE中分配 */
  19. if (migratetype == MIGRATE_RESERVE)
  20. continue;/*访问下一个类型*/
  21. area = &(zone->free_area[current_order]);
  22. /*如果指定order和类型的链表为空*/
  23. if (list_empty(&area->free_list[migratetype]))
  24. continue;/*访问下一个类型*/
  25. /*得到指定类型和order的页面基址*/
  26. page = list_entry(area->free_list[migratetype].next,
  27. struct page, lru);
  28. area->nr_free--;/*空闲块数减一*/
  29. /*
  30. * If breaking a large block of pages, move all free
  31. * pages to the preferred allocation list. If falling
  32. * back for a reclaimable kernel allocation, be more
  33. * agressive about taking ownership of free pages
  34. */
  35. if (unlikely(current_order >= (pageblock_order >> 1)) ||/* 要分割的页面是一个大页面,则将整个页面全部迁移到当前迁移类型的链表中,
  36. 这样可以避免过多的碎片 */
  37. start_migratetype == MIGRATE_RECLAIMABLE ||/* 目前分配的是可回收页面,这类页面有突发的特点,将页面全部迁移到可回收链表中,
  38. 可以避免将其他迁移链表分割成太多的碎片 */
  39. page_group_by_mobility_disabled) {/* 指定了迁移策略,总是将被分割的页面迁移 */
  40. unsigned long pages;
  41. /*移动到先前类型的伙伴系统中*/
  42. pages = move_freepages_block(zone, page,
  43. start_migratetype);
  44. /* Claim the whole block if over half of it is free */
  45. /* pages是移动的页面数,如果可移动的页面数量较多,
  46. 则将整个大内存块的迁移类型修改 */
  47. if (pages >= (1 <
  48. page_group_by_mobility_disabled)
  49. /*设置页面标示*/
  50. set_pageblock_migratetype(page,
  51. start_migratetype);
  52. migratetype = start_migratetype;
  53. }
  54. /* Remove the page from the freelists */
  55. list_del(&page->lru);
  56. rmv_page_order(page);
  57. /* Take ownership for orders >= pageblock_order */
  58. if (current_order >= pageblock_order)//大于pageblock_order的部分设置相应标示
  59. /*这个不太可能,因为pageblock_order为10*/
  60. change_pageblock_range(page, current_order,
  61. start_migratetype);
  62. /*拆分和合并*/
  63. expand(zone, page, order, current_order, area, migratetype);
  64. trace_mm_page_alloc_extfrag(page, order, current_order,
  65. start_migratetype, migratetype);
  66. return page;
  67. }
  68. }
  69. return NULL;
  70. }

备用链表

[cpp] view plaincopyprint?
  1. /*
  2. * This array describes the order lists are fallen back to when
  3. * the free lists for the desirable migrate type are depleted
  4. */
  5. /*指定类型的链表为空时,这个数组规定
  6. 回调的到那个类型的链表*/
  7. static int fallbacks[MIGRATE_TYPES][MIGRATE_TYPES-1] = {
  8. [MIGRATE_UNMOVABLE]   = { MIGRATE_RECLAIMABLE, MIGRATE_MOVABLE,   MIGRATE_RESERVE },
  9. [MIGRATE_RECLAIMABLE] = { MIGRATE_UNMOVABLE,   MIGRATE_MOVABLE,   MIGRATE_RESERVE },
  10. [MIGRATE_MOVABLE]     = { MIGRATE_RECLAIMABLE, MIGRATE_UNMOVABLE, MIGRATE_RESERVE },
  11. [MIGRATE_RESERVE]     = { MIGRATE_RESERVE,     MIGRATE_RESERVE,   MIGRATE_RESERVE }, /* Never used */
  12. };

移动到指定类型的伙伴系统中

[cpp] view plaincopyprint?
  1. /*将指定区域段的页面移动到指定类型的
  2. 伙伴系统中,其实就是将页面的类型做了
  3. 更改,但是是采用移动的方式
  4. 功能和上面函数类似,但是要求以
  5. 页面块方式对其*/
  6. static int move_freepages_block(struct zone *zone, struct page *page,
  7. int migratetype)
  8. {
  9. unsigned long start_pfn, end_pfn;
  10. struct page *start_page, *end_page;
  11. /*如下是对齐操作,其中变量pageblock_nr_pages为MAX_ORDER-1*/
  12. start_pfn = page_to_pfn(page);
  13. start_pfn = start_pfn & ~(pageblock_nr_pages-1);
  14. start_page = pfn_to_page(start_pfn);
  15. end_page = start_page + pageblock_nr_pages - 1;
  16. end_pfn = start_pfn + pageblock_nr_pages - 1;
  17. /* Do not cross zone boundaries */
  18. if (start_pfn zone_start_pfn)
  19. start_page = page;
  20. /*结束边界检查*/
  21. if (end_pfn >= zone->zone_start_pfn + zone->spanned_pages)
  22. return 0;
  23. /*调用上面函数*/
  24. return move_freepages(zone, start_page, end_page, migratetype);
  25. }
[cpp] view plaincopyprint?
  1. /*
  2. * Move the free pages in a range to the free lists of the requested type.
  3. * Note that start_page and end_pages are not aligned on a pageblock
  4. * boundary. If alignment is required, use move_freepages_block()
  5. */
  6. /*将指定区域段的页面移动到指定类型的
  7. 伙伴系统中,其实就是将页面的类型做了 更改,但是是采用移动的方式*/
  8. static int move_freepages(struct zone *zone,
  9. struct page *start_page, struct page *end_page,
  10. int migratetype)
  11. {
  12. struct page *page;
  13. unsigned long order;
  14. int pages_moved = 0;
  15. #ifndef CONFIG_HOLES_IN_ZONE
  16. /*
  17. * page_zone is not safe to call in this context when
  18. * CONFIG_HOLES_IN_ZONE is set. This bug check is probably redundant
  19. * anyway as we check zone boundaries in move_freepages_block().
  20. * Remove at a later date when no bug reports exist related to
  21. * grouping pages by mobility
  22. */
  23. BUG_ON(page_zone(start_page) != page_zone(end_page));
  24. #endif
  25. for (page = start_page; page <= end_page;) {
  26. /* Make sure we are not inadvertently changing nodes */
  27. VM_BUG_ON(page_to_nid(page) != zone_to_nid(zone));
  28. if (!pfn_valid_within(page_to_pfn(page))) {
  29. page++;
  30. continue;
  31. }
  32. if (!PageBuddy(page)) {
  33. page++;
  34. continue;
  35. }
  36. order = page_order(page);
  37. list_del(&page->lru);/*将页面块从原来的伙伴系统链表*/
  38. /*中删除,注意,这里不是一个页面
  39. *而是以该页面的伙伴块*/
  40. list_add(&page->lru,/*添加到指定order和类型下的伙伴系统链表*/
  41. &zone->free_area[order].free_list[migratetype]);
  42. page += 1 </*移动页面数往上定位*/
  43. pages_moved += 1 </*移动的页面数*/
  44. }
  45. return pages_moved;
  46. }

四、慢速分配,允许等待和回收

[cpp] view plaincopyprint?
  1. /**
  2. * 当无法快速分配页面时,如果调用者允许等待
  3. ,则通过本函数进行慢速分配。
  4. * 此时允许进行内存回收。
  5. */
  6. static inline struct page *
  7. __alloc_pages_slowpath(gfp_t gfp_mask, unsigned int order,
  8. struct zonelist *zonelist, enum zone_type high_zoneidx,
  9. nodemask_t *nodemask, struct zone *preferred_zone,
  10. int migratetype)
  11. {
  12. const gfp_t wait = gfp_mask & __GFP_WAIT;
  13. struct page *page = NULL;
  14. int alloc_flags;
  15. unsigned long pages_reclaimed = 0;
  16. unsigned long did_some_progress;
  17. struct task_struct *p = current;
  18. /*
  19. * In the slowpath, we sanity check order to avoid ever trying to
  20. * reclaim >= MAX_ORDER areas which will never succeed. Callers may
  21. * be using allocators in order of preference for an area that is
  22. * too large.
  23. *//*参数合法性检查*/
  24. if (order >= MAX_ORDER) {
  25. WARN_ON_ONCE(!(gfp_mask & __GFP_NOWARN));
  26. return NULL;
  27. }
  28. /*
  29. * GFP_THISNODE (meaning __GFP_THISNODE, __GFP_NORETRY and
  30. * __GFP_NOWARN set) should not cause reclaim since the subsystem
  31. * (f.e. slab) using GFP_THISNODE may choose to trigger reclaim
  32. * using a larger set of nodes after it has established that the
  33. * allowed per node queues are empty and that nodes are
  34. * over allocated.
  35. */
  36. /**
  37. * 调用者指定了GFP_THISNODE标志,表示不能进行内存回收。
  38. * 上层调用者应当在指定了GFP_THISNODE失败后,使用其他标志进行分配。
  39. */
  40. if (NUMA_BUILD && (gfp_mask & GFP_THISNODE) == GFP_THISNODE)
  41. goto nopage;
  42. restart:/*如果调用者没有禁止kswapd,则唤醒该线程进行内存回收。*/
  43. wake_all_kswapd(order, zonelist, high_zoneidx);
  44. /*
  45. * OK, we're below the kswapd watermark and have kicked background
  46. * reclaim. Now things get more complex, so set up alloc_flags according
  47. * to how we want to proceed.
  48. */
  49. /*根据分配标志确定内部标志,主要是用于水线 */
  50. alloc_flags = gfp_to_alloc_flags(gfp_mask);
  51. /**
  52. * 与快速分配流程相比,这里的分配标志使用了低的水线。
  53. * 在进行内存回收操作前,我们使用低水线再尝试分配一下。
  54. * 当然,不管是否允许ALLOC_NO_WATERMARKS标志,我们都将它清除。
  55. */
  56. /* This is the last chance, in general, before the goto nopage. */
  57. page = get_page_from_freelist(gfp_mask, nodemask, order, zonelist,
  58. high_zoneidx, alloc_flags & ~ALLOC_NO_WATERMARKS,
  59. preferred_zone, migratetype);
  60. if (page)/*分配成功,找到页面*/
  61. goto got_pg;
  62. rebalance:
  63. /* Allocate without watermarks if the context allows */
  64. /* 某些上下文,如内存回收进程及被杀死的任务,都允许它完全突破水线的限制分配内存。 */
  65. if (alloc_flags & ALLOC_NO_WATERMARKS) {
  66. page = __alloc_pages_high_priority(gfp_mask, order,
  67. zonelist, high_zoneidx, nodemask,
  68. preferred_zone, migratetype);
  69. if (page))/* 在不考虑水线的情况下,分配到了内存 */
  70. goto got_pg;
  71. }
  72. /* Atomic allocations - we can't balance anything */
  73. /* 调用者希望原子分配内存,此时不能等待内存回收,返回NULL */
  74. if (!wait)
  75. goto nopage;
  76. /* Avoid recursion of direct reclaim */
  77. /* 调用者本身就是内存回收进程,不能进入后面的内存回收处理流程,否则死锁 */
  78. if (p->flags & PF_MEMALLOC)
  79. goto nopage;
  80. /* Avoid allocations with no watermarks from looping endlessly */
  81. /**
  82. * 当前线程正在被杀死,它可以完全突破水线分配内存。这里向上层返回NULL,是为了避免系统进入死循环。
  83. * 当然,如果上层调用不允许失败,则死循环继续分配,等待其他线程释放一点点内存。
  84. */
  85. if (test_thread_flag(TIF_MEMDIE) && !(gfp_mask & __GFP_NOFAIL))
  86. goto nopage;
  87. /* Try direct reclaim and then allocating */
  88. /**
  89. * 直接在内存分配上下文中进行内存回收操作。
  90. */
  91. page = __alloc_pages_direct_reclaim(gfp_mask, order,
  92. zonelist, high_zoneidx,
  93. nodemask,
  94. alloc_flags, preferred_zone,
  95. migratetype, &did_some_progress);
  96. if (page))/* 庆幸,回收了一些内存后,满足了上层分配需求 */
  97. goto got_pg;
  98. /*
  99. * If we failed to make any progress reclaiming, then we are
  100. * running out of options and have to consider going OOM
  101. */
  102. /* 内存回收过程没有回收到内存,系统真的内存不足了 */
  103. if (!did_some_progress) {
  104. /**
  105. * 调用者不是文件系统的代码,允许进行文件系统操作,并且允许重试。
  106. * 这里需要__GFP_FS标志可能是进入OOM流程后会杀进程或进入panic,需要文件操作。
  107. */
  108. if ((gfp_mask & __GFP_FS) && !(gfp_mask & __GFP_NORETRY)) {
  109. if (oom_killer_disabled)/* 系统禁止了OOM,向上层返回NULL */
  110. goto nopage;
  111. /**
  112. * 杀死其他进程后再尝试分配内存
  113. */
  114. page = __alloc_pages_may_oom(gfp_mask, order,
  115. zonelist, high_zoneidx,
  116. nodemask, preferred_zone,
  117. migratetype);
  118. if (page)
  119. goto got_pg;
  120. /*
  121. * The OOM killer does not trigger for high-order
  122. * ~__GFP_NOFAIL allocations so if no progress is being
  123. * made, there are no other options and retrying is
  124. * unlikely to help.
  125. */)/* 要求的页面数量较多,再试意义不大 */
  126. if (order > PAGE_ALLOC_COSTLY_ORDER &&
  127. !(gfp_mask & __GFP_NOFAIL))
  128. goto nopage;
  129. goto restart;
  130. }
  131. }
  132. /* Check if we should retry the allocation */
  133. /* 内存回收过程回收了一些内存,接下来判断是否有必要继续重试 */
  134. pages_reclaimed += did_some_progress;
  135. if (should_alloc_retry(gfp_mask, order, pages_reclaimed)) {
  136. /* Wait for some write requests to complete then retry */
  137. congestion_wait(BLK_RW_ASYNC, HZ/50);
  138. goto rebalance;
  139. }
  140. nopage:
  141. /* 内存分配失败了,打印内存分配失败的警告 */
  142. if (!(gfp_mask & __GFP_NOWARN) && printk_ratelimit()) {
  143. printk(KERN_WARNING "%s: page allocation failure."
  144. " order:%d, mode:0x%x\n",
  145. p->comm, order, gfp_mask);
  146. dump_stack();
  147. show_mem();
  148. }
  149. return page;
  150. got_pg:
  151. /* 运行到这里,说明成功分配了内存,这里进行内存检测调试 */
  152. if (kmemcheck_enabled)
  153. kmemcheck_pagealloc_alloc(page, order, gfp_mask);
  154. return page;
  155. }

总结:Linux伙伴系统主要分配流程为

正常非配(或叫快速分配)流程:

1,如果分配的是单个页面,考虑从per CPU缓存中分配空间,如果缓存中没有页面,从伙伴系统中提取页面做补充。

2,分配多个页面时,从指定类型中分配,如果指定类型中没有足够的页面,从备用类型链表中分配。最后会试探保留类型链表。

慢速(允许等待和页面回收)分配:

3,当上面两种分配方案都不能满足要求时,考虑页面回收、杀死进程等操作后在试。

(转)linux内存管理之伙伴系统(内存分配)相关推荐

  1. linux进程管理内存管理,Linux专业知识四:Linux系统进程管理及查看内存

    本文主讲Linux专业知识之Linux系统进程管理及查看内存的情况,以Redhat RHEL7操作系统为例. 一.进程 程序与进程:程序是静态的(文件),进程是动态的(运行的程序). 进程和线程:一个 ...

  2. 【Linux 内核 内存管理】优化内存屏障 ③ ( 编译器屏障 | 禁止 / 开启内核抢占 与 方法保护临界区 | preempt_disable 禁止内核抢占源码 | 开启内核抢占源码 )

    文章目录 一.禁止 / 开启内核抢占 与 方法保护临界区 二.编译器优化屏障 三.preempt_disable 禁止内核抢占 源码 四.preempt_enable 开启内核抢占 源码 一.禁止 / ...

  3. 操作系统的内存管理机制(连续分配管理、页式、段式、段页式、快表、二级页表)

    来源:https://www.bilibili.com/video/BV1YE411D7nH 操作系统的内存管理机制(连续分配管理.页式.段式.段页式.快表.二级页表) 内存被分为系统区和用户区,系统 ...

  4. 属性与内存管理(属性与内存管理都是相互关联的)

    <span style="font-size:18px;"> 属性与内存管理(属性与内存管理都是相互关联的)第一部分一,属性:属性是OC2.0之后出来的新语法,用来取代 ...

  5. JVM自动内存管理机制——Java内存区域(下)

    一.虚拟机参数配置 在上一篇<Java自动内存管理机制--Java内存区域(上)>中介绍了有关的基础知识,这一篇主要是通过一些示例来了解有关虚拟机参数的配置. 1.Java堆参数设置 a) ...

  6. 95-290-050-源码-内存管理-堆外内存与堆内内存概述

    2.概述 ​ Flink的内存管理器管理着用于排序.散列和缓存所需的内存.内存以相等大小的(Segments)表示,称为内存页.操作器通过请求多个内存页来分配内存.在Flink中,内存又分为堆内存和非 ...

  7. Java内存管理:Java内存区域 JVM运行时数据区

    Java内存管理:Java内存区域 JVM运行时数据区 在前面的一些文章了解到javac编译的大体过程.Class文件结构.以及JVM字节码指令. 下面我们详细了解Java内存区域:先说明JVM规范定 ...

  8. C++:内存管理:C++内存管理详解

    C++语言内存管理是指:对系统的分配.创建.使用这一系列操作.在内存管理中,由于是操作系统内存,使用不当会造成很麻烦的后果.本文将从系统内存的分配.创建出发,并且结合例子来说明内存管理不当会造成的结果 ...

  9. [JAVA]第二篇(内存管理,HashMap内存泄漏解决办法)

    网上看到一个关于内存泄漏处理的例子,原网址:http://www.jb51.net/article/49428.htm,下面笔者将具体分析下这篇文章中的代码,并从中学习JAVA的内存管理. (Begi ...

  10. Unity 之 Mono内存管理与泄漏 — 内存是手游的硬伤(转)

    WeTest导读 内存是游戏的硬伤,如果没有做好内存的管理问题,游戏极有可能会出现卡顿,闪退等影响用户体验的现象.本文介绍了在腾讯游戏在Unity游戏开发过程中常见的Mono内存管理问题,并介绍了一系 ...

最新文章

  1. 这份“插件英雄榜Top20”才是Chrome的正确打开方式!
  2. [云炬创业管理笔记]第四章把握创业机会测试5
  3. 判断一个路径串是否为有效目录
  4. 【机器学习算法专题(蓄力计划)】二十、实操代码MNIST 数据集
  5. 有机晶体数据库_技术专栏:一篇文章搞懂晶体学信息文件CIF及其获取方法
  6. Math常用方法,String转float并且保留两位小数,除法
  7. java从键盘输入一个数,并将其倒序输出
  8. 经常在命令提示符中所使用的命令
  9. python怎么设置图片_python 调整图片亮度的示例
  10. 安防监控项目动辄几十亿,什么样的监控系统才能胜任?
  11. linux添加软件源命令,Linux 添加源
  12. (三)五款常用的java开发工具(快来看看吧)
  13. stack的使用方法
  14. 论文解读-Intriguing properties of neural networks(ICLR2014)
  15. 基于jupyter notebook的简单爬虫学习记录
  16. 聚合支付收款码怎么申请
  17. Android开发 assets目录
  18. SVG公众号排版『大尺寸背景图重复安卓不显示』解决方法
  19. XiaoHu日志 4/17
  20. Tesserocr安装及报错解决方案

热门文章

  1. Linux云计算面试常见问题三
  2. xiao297328是个骗子,大家注意了啊
  3. 湘潭大学计算机学院研究生院官网首页,湘潭大学计算机科学与技术一级学科-湘潭大学—信息工程学院.DOC...
  4. 微信小程序开发快捷键
  5. Statcom:基于MATLAB/Simulink的静止无功补偿器仿真模型,负载端加入断路器模拟断路故障
  6. 计算机原理系列之八 -------- 可执行文件的PLT和GOT
  7. 文件损坏 无法删除 怎么使用chkdsk磁盘修复工具
  8. C语言程序设计--体测成绩信息管理系统
  9. 菲尔兹奖得主小平邦彦:数学是什么?
  10. Stata实现DID(倍差法)全流程