歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
Linux教程網 >> Linux基礎 >> Linux教程 >> Linux內存管理之伙伴系統(內存分配)

Linux內存管理之伙伴系統(內存分配)

日期:2017/2/28 15:59:59   编辑:Linux教程
一、Linux伙伴系統分配器

伙伴系統分配器大體上分為兩類。__get_free_pages()類函數返回分配的第一個頁面的線性地址;alloc_pages()類函數返回頁面描述符地址。不管以哪種函數進行分配,最終會調用alloc_pages()進行分配頁面。

為清楚了解其分配制度,先給個伙伴系統數據的存儲框圖

也就是每個order對應一個free_area結構,free_area以不同的類型以鏈表的方式存儲這些內存塊。

二、主分配函數

下面我們來看這個函數(在UMA模式下)

  1. #define alloc_pages(gfp_mask, order) \
  2. alloc_pages_node(numa_node_id(), gfp_mask, order)
  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 < 0)
  6. nid = numa_node_id();
  7. return __alloc_pages(gfp_mask, order, node_zonelist(nid, gfp_mask));
  8. }
  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()

  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()

  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 < NR_WMARK);
  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. }

主分配函數

  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 << order個頁面,這裡進行管理區空閒頁面統計計數*/
  66. __mod_zone_page_state(zone, NR_FREE_PAGES, -(1 << order));
  67. spin_unlock(&zone->lock);/* 這裡僅僅打開自旋鎖,待後面統計計數設置完畢後再開中斷*/
  68. if (!page)
  69. goto failed;
  70. }
  71. /*事件統計計數,調試*/
  72. __count_zone_vm_events(PGALLOC, zone, 1 << order);
  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. }
Copyright © Linux教程網 All Rights Reserved