1. 程式人生 > >Linux 內核源代碼分析 chap 2 存儲管理 (5)

Linux 內核源代碼分析 chap 2 存儲管理 (5)

利用 head 得到 技術 cut other numa targe 差距

物理頁面分配

linux 內核 2.4 中有 2 個版本號的物理頁面分配函數 alloc_pages()。 一個在 mm/numa.c 中, 還有一個在 mm/page_alloc.c 中, 依據條件編譯選項 CONFIG_DISCONTIGMEM 決定取舍。

1. NUMA 結構中的alloc_pages

==================== mm/numa.c 43 43 ====================
43  #ifdef CONFIG_DISCONTIGMEM
==================== mm/numa.c 91
128 ==================== 91 /* 92 * This can be refined. Currently, tries to do round robin, instead 93 * should do concentratic circle search, starting from current node. 94 */ 95 struct page * alloc_pages(int gfp_mask, unsigned long order) 96 { 97 struct page *ret = 0; 98 pg_data_t *start
, *temp; 99 #ifndef CONFIG_NUMA 100 unsigned long flags; 101 static pg_data_t *next = 0; 102 #endif 103 104 if (order >= MAX_ORDER) 105 return NULL; 106 #ifdef CONFIG_NUMA 107 temp = NODE_DATA(numa_node_id()); 108 #else 109 spin_lock_irqsave(&node_lock, flags); 110 if (!next
) next = pgdat_list; 111 temp = next; 112 next = next->node_next; 113 spin_unlock_irqrestore(&node_lock, flags); 114 #endif 115 start = temp; 82 116 while (temp) { 117 if ((ret = alloc_pages_pgdat(temp, gfp_mask, order))) 118 return(ret); 119 temp = temp->node_next; 120 } 121 temp = pgdat_list; 122 while (temp != start) { 123 if ((ret = alloc_pages_pgdat(temp, gfp_mask, order))) 124 return(ret); 125 temp = temp->node_next; 126 } 127 return(0); 128 }
  1. 通過設置 CONFIG_DISCONTIGMEM 這個條件編譯選項之後。 這段代碼才幹得到編譯。
    我們能夠把 不連續的物理存儲空間 看做是一種廣義的 NUMA, 兩塊內存之間的孤島看成是非均質的。這樣。 在處理不連續的物理空間的時候, 也須要像處理 NUMA 一樣劃分出若幹連續且均勻的 “存儲節點”, 因而。 也有一個 pg_data_t 的數據結構的隊列。
  2. 調用參數中的 gfp_mask, 他是一個整數代表著一種分配策略。它相應的實際上是給定節點中數組node_zonelist[] 的下標, 而這個node_zonelist[] 表征的是物理內存頁面分配時候的一個策略。 在node_zonelist 中, 維護了一個zone_t 的數組, 這個數組就用來表征 分配物理頁面的時候, 先到 zone[i]管理區嘗試分配。 假設不行。 轉到zone[i + 1]。因為, 有多種分配的策略。 因而, 定義了一個node_zonelist[] 的分配策略數組。
    技術分享圖片
  3. 第二個參數 order 表征所須要的物理塊的大小,能夠看到他是一個unsigned long 類型。計算機中是採用二進制形式的, 我們分配頁面的時候, 也是依照 2n 的形式來進行分配。

    非常容易能夠想到, 這個order 就代表了須要分配 空間 大小 中 2 的冪次的形式。 ie, 分配的頁面大小為 2order

  4. 對於 NUMA 結構的系統而言。 我們能夠通過NUMA_DATA 和 numa_node_id() 找到cpu 所在節點的pg_data_t 的數據結構隊列。

  5. 對於 非連續的 UMA 結構, 也有一個pgdat_list 的結構。我們能夠這麽理解他, UMA 相當於NUMA 中全部區域的材質變成一致時候的特殊情形, ie, pgdat_list 的長度為 1.
  6. 也就是說, 代碼的106 ~ 114 實際上獲取到了一個 pgdat_t 節點的指針。然後, 115 ~ 126 遍歷這個pgdat_list 所在的pgdat_list 鏈表中的每一個pgdat_t 節點, 試圖在這些存儲節點上分配所需的物理頁面。為什麽是兩個while 呢, 因為我們一開始拿到的pgdat_t 存儲節點 可能僅僅是這個鏈表中間的某一節點而已

1.1. alloc_pages_pdat

==================== mm/numa.c 85 89 ====================
85  static struct page * alloc_pages_pgdat(pg_data_t *pgdat, int gfp_mask,
86      unsigned long order)
87  {
88      return __alloc_pages(pgdat->node_zonelists + gfp_mask, order);
89  }

能夠看到。在每一個pgdat_t 節點上, 分配頁面的時候, 都是調用了 __alloc_pages 函數。

2. UMA 中的 alloc_pages

==================== include/linux/mm.h 343 352 ====================
343  #ifndef CONFIG_DISCONTIGMEM
344  static inline struct page * alloc_pages(int gfp_mask, unsigned long order)
83
345  {
346     /*
347     * Gets optimized away by the compiler.
348     */
349     if (order >= MAX_ORDER)
350         return NULL;
351     return __alloc_pages(contig_page_data.node_zonelists+(gfp_mask), order);
352  }

這個函數比較簡單。 他僅僅有在 CONFIG_DISCONTIGMEM 未定義的時候才會編譯, ie, 他處理UMA 均質連續時候的物理頁面分配。

事實上。 個人感覺這兩段代碼, 邏輯上是統一的,因為之前也說過, UMA 能夠看成是 NUMA 的一種特例情形,在UMA 中 pgdat_list 的長度縮小到 1。因而, 就不須要再去遍歷 這個 pgdat_list 了, 或者說相當於 遍歷過程僅僅運行了1 次。

3. __alloc_pages

這段代碼有些長。 分段來看:

3.1 part 1

==================== mm/page_alloc.c 270 315 ====================
[alloc_pages()>__alloc_pages()]
270  /*
271   * This is the ‘heart‘ of the zoned buddy allocator:
272   */
273  struct page * __alloc_pages(zonelist_t *zonelist, unsigned long order)
274  {
275     zone_t **zone;
276     int direct_reclaim = 0;
277     unsigned int gfp_mask = zonelist->gfp_mask;
278     struct page * page;
279
280     /*
281     * Allocations put pressure on the VM subsystem.
282     */
283     memory_pressure++;
284
285     /*
286     * (If anyone calls gfp from interrupts nonatomically then it
287     * will sooner or later tripped up by a schedule().)
288     *
289     * We are falling back to lower-level zones if allocation
290     * in a higher zone fails.
291     */
292
293     /*
294     * Can we take pages directly from the inactive_clean
295     * list?
296     */
297     if (order == 0 && (gfp_mask & __GFP_WAIT) &&
298             !(current->flags & PF_MEMALLOC))
299         direct_reclaim = 1;
300
301     /*
302     * If we are about to get low on free pages and we also have
303     * an inactive page shortage, wake up kswapd.
304     */
305     if (inactive_shortage() > inactive_target / 2 && free_shortage())
306         wakeup_kswapd(0);
307     /*
308     * If we are about to get low on free pages and cleaning
309     * the inactive_dirty pages would fix the situation,
310     * wake up bdflush.
311     */
312     else if (free_shortage() && nr_inactive_dirty_pages > free_shortage()
313             && nr_inactive_dirty_pages >= freepages.high)
314         wakeup_bdflush(0);
315
  1. 我們的第一個參數 zonelist 指向一個代表著一個詳細分配策略的zonelist_t 的數據結構, order 和前面一樣。 表征須要分配頁面的大小。
  2. gfp_mask 表征的是詳細分配策略中用於控制目的的標誌位。
  3. 假設要求分配單頁 (order = 0), 而且是等待分配完畢(__GFP_WAIT), 同一時候, 不是內存分配者(PF_MEMALLOC), 就把 局部量 direct_reclaim 設置為 1。表示, 我們能夠從相應的頁面管理區中的 不活躍幹凈頁面 的緩沖隊列中進行回收。 上一章中講到, 這樣的頁面的內容已經寫入到了交換設備中, 僅僅是還是保留著頁面的內容而已。

    因為這些頁面不一定是連續的, 所以, 僅僅有須要單頁的時候, 才從這裏回收分配。

  4. 當內存頁面短缺的時候, 會喚醒kswapd 和 bdflush 兩個內核線程。 獲取很多其它的內存頁面。 進行分配。

3.2 part 2

==================== mm/page_alloc.c 316 340 ====================
[alloc_pages()>__alloc_pages()]
316  try_again:
317     /*
318     * First, see if we have any zones with lots of free memory.
319     *
320     * We allocate free memory first because it doesn‘t contain
321     * any data ... DUH!
322     */
323     zone = zonelist->zones;
324     for (;;) {
325         zone_t *z = *(zone++);
326         if (!z)
327             break;
328         if (!z->size)
329             BUG();
330
331         if (z->free_pages >= z->pages_low) {
332             page = rmqueue(z, order);
333         if (page)
334             return page;
335         } else if (z->free_pages < z->pages_min &&
336             waitqueue_active(&kreclaimd_wait)) {
337             wake_up_interruptible(&kreclaimd_wait);
338         }
339     }
340

上面一段代碼。 會對我們所設定的分配策略數組 zone[] 中的每一個管理區 zone 進行遍歷,假設發現 管理區中 空暇頁面的數量, 高於設定的管理區中最少須要保留的頁面的數量 pages_low的時候。 就調用 requeue 試圖分配頁面。

否則要是發現空暇頁面, 比設定的最小的頁面數量 pages_min 還少,而且, kreclaimd 處於睡眠狀態, 就喚醒他, 讓他回收一些頁面備用。

3.2.1 rmqueue

==================== mm/page_alloc.c 172 211 ====================
[alloc_pages()>__alloc_pages()>rmqueue()]
172  static struct page * rmqueue(zone_t *zone, unsigned long order)
173  {
174     free_area_t * area = zone->free_area + order;
175     unsigned long curr_order = order;
176     struct list_head *head, *curr;
177     unsigned long flags;
178     struct page *page;
179
180     spin_lock_irqsave(&zone->lock, flags);
181     do {
182         head = &area->free_list;
183         curr = memlist_next(head);
184
185         if (curr != head) {
186             unsigned int index;
187
188             page = memlist_entry(curr, struct page, list);
189             if (BAD_RANGE(zone,page))
190                 BUG();
191             memlist_del(curr);
192             index = (page - mem_map) - zone->offset;
193             MARK_USED(index, curr_order, area);
194             zone->free_pages -= 1 << order;
195
196             page = expand(zone, page, index, order, curr_order, area);
197             spin_unlock_irqrestore(&zone->lock, flags);
198
199             set_page_count(page, 1);
200             if (BAD_RANGE(zone,page))
201                 BUG();
202             DEBUG_ADD_PAGE
203             return page;
204         }
205         curr_order++;
206         area++;
207     } while (curr_order < MAX_ORDER);
208     spin_unlock_irqrestore(&zone->lock, flags);
209
210     return NULL;
211  }
  1. 這個函數試圖從一個頁面管理區中分配若幹連續的內存頁面。

  2. 因為代表物理頁面的page 數據結構, 是通過雙向鏈表的形式連接在管理區的某個空暇隊列中的, 分配頁面。 就是將他們從隊列中摘除的過程。 大概這就是這個函數命名為 rmqueue 的原因吧。
  3. zone 管理區中的free_area 是個結構數組。
    技術分享圖片
  4. 通過 zone->free_area + order 能夠找到free_area 所管理的 2order大小的頁面塊的鏈表的入口。

  5. 我們通過 memlist_next 獲取第一個頁面上 list_head 結構的地址, 然後通過調用 memlist_entry (在chap 1中 C 語言部分 有介紹)獲取list_head 所在頁面 page 的地址信息。

    並將該段頁面空間 使用 memlist_del 進行移出free_area 所管理的隊列。

  6. index 表示分配的頁面在 zone 中的位置
  7. 假設一個 order 所相應的頁面塊隊列中沒有能夠分配的頁面塊。 就移動到下一個更大的order + 1 所相應的頁面塊隊列中嘗試分配。假設。 在更大的頁面塊中成功分配了頁面, 就調用expand 將剩余的頁面空間分解成小的塊連入到相應的隊列中。

3.2.2 expand

==================== mm/page_alloc.c 150 169 ====================
[alloc_pages()>__alloc_pages()>rmqueue()>expand()]
150  static inline struct page * expand (zone_t *zone, struct page *page,
151  unsigned long index, int low, int high, free_area_t * area)
152  {
153     unsigned long size = 1 << high;
154
155     while (high > low) {
156         if (BAD_RANGE(zone,page))
157             BUG();
158         area--;
159         high--;
160         size >>= 1;
161         memlist_add_head(&(page)->list, &(area)->free_list);
162         MARK_USED(index, high, area);
163         index += size;
164         page += size;
165     }
166     if (BAD_RANGE(zone,page))
167         BUG();
168     return page;
169  }
  1. 調用參數中的low 表征須要的頁面塊的大小。 high 表示 實際滿足要求的物理塊的大小。
  2. 當 high > low 的時候, 將2high?1的區塊, 連入到high - 1 相應的free_list 中去。 利用 memlist_add_head; 同一時候設置位圖, 直到全部處理完畢。

3.3 part 3

從part2 部分的代碼中。 我們知道。 我們如今當前管理區zone 的free_list 中進行分配, (遍歷該區域全部不同大小的頁面塊), 假設還是失敗的話。 就進入到下一個zone 進行分配。假設分配成功, page 結構中的引用計數通過 set_page_count 被設置為 1。 可是,假設沒有分配成功呢?

這時候, 我們須要加大分配的力度:
1. 減少對 zone 管理區中 保持 page_low 的要求
2. 將緩沖在管理區中的 不活躍幹凈頁面。 也增加考慮分配的範圍。

==================== mm/page_alloc.c 341 364 ====================
[alloc_pages()>__alloc_pages()]
341     /*
342      * Try to allocate a page from a zone with a HIGH
343      * amount of free + inactive_clean pages.
344      *
345      * If there is a lot of activity, inactive_target
346      * will be high and we‘ll have a good chance of
347      * finding a page using the HIGH limit.
348      */
349     page = __alloc_pages_limit(zonelist, order, PAGES_HIGH, direct_reclaim);
350     if (page)
351         return page;
352
353     /*
354     * Then try to allocate a page from a zone with more
355     * than zone->pages_low free + inactive_clean pages.
356     *
357     * When the working set is very large and VM activity
358     * is low, we‘re most likely to have our allocation
359     * succeed here.
360     */
361     page = __alloc_pages_limit(zonelist, order, PAGES_LOW, direct_reclaim);
362     if (page)
363         return page;
364

先後使用 pages_high 和 pages_low 來嘗試分配。

3.3.1 __alloc_pages_limit

==================== mm/page_alloc.c 213 267 ====================
[alloc_pages()>__alloc_pages()>__alloc_pages_limit()]
213  #define PAGES_MIN  0
214  #define PAGES_LOW  1
215  #define PAGES_HIGH 2
88
216
217  /*
218   * This function does the dirty work for __alloc_pages
219   * and is separated out to keep the code size smaller.
220   * (suggested by Davem at 1:30 AM, typed by Rik at 6 AM)
221   */
222  static struct page * __alloc_pages_limit(zonelist_t *zonelist,
223         unsigned long order, int limit, int direct_reclaim)
224  {
225     zone_t **zone = zonelist->zones;
226
227     for (;;) {
228         zone_t *z = *(zone++);
229         unsigned long water_mark;
230
231     if (!z)
232         break;
233     if (!z->size)
234         BUG();
235
236     /*
237     * We allocate if the number of free + inactive_clean
238     * pages is above the watermark.
239     */
240     switch (limit) {
241         default:
242         case PAGES_MIN:
243             water_mark = z->pages_min;
244             break;
245         case PAGES_LOW:
246             water_mark = z->pages_low;
247             break;
248         case PAGES_HIGH:
249             water_mark = z->pages_high;
250     }
251
252     if (z->free_pages + z->inactive_clean_pages > water_mark) {
253         struct page *page = NULL;
254         /* If possible, reclaim a page directly. */
255         if (direct_reclaim && z->free_pages < z->pages_min + 8)
256             page = reclaim_page(z);
257         /* If that fails, fall back to rmqueue. */
258         if (!page)
259             page = rmqueue(z, order);
260         if (page)
261             return page;
262     }
263 }
264
265 /* Found nothing. */
266 return NULL;
267 }
  1. 在這裏放寬了對分配時候要求zone 管理區保留的頁面的限制, 如今僅僅要求 空暇區頁面數量 加上 所管理的不活躍幹凈頁面的數量 能夠達到分配要求的 water_mark 就能夠再次嘗試分配了。
  2. 這裏, 我們先前設置的direct_reclaim 局部變量就能夠起作用了。 他使得我們在空暇頁面與要求的zone 的page_min 差距小於 8 的時候, 能夠直接 使用reclaim_page 從inactive_clean_list 中回收頁面。
  3. 假設沒有找到相應的page。 就再對這個zone 進行嘗試分配。 不行轉下一個zone (盡管這個操作, 我們之前已經做過了。 只是萬一呢?)

3.4 part 4

假設上述操作。 還是不成功的話。 就表明zone 中的頁面已經是嚴重短缺了。

==================== mm/page_alloc.c 365 399 ====================
[alloc_pages()>__alloc_pages()]
365     /*
366     * OK, none of the zones on our zonelist has lots
367     * of pages free.
368     *
369     * We wake up kswapd, in the hope that kswapd will
370     * resolve this situation before memory gets tight.
371     *
372     * We also yield the CPU, because that:
373     * - gives kswapd a chance to do something
374     * - slows down allocations, in particular the
375     *   allocations from the fast allocator that‘s
376     *   causing the problems ...
377     * - ... which minimises the impact the "bad guys"
378     *   have on the rest of the system
379     * - if we don‘t have __GFP_IO set, kswapd may be
380     *   able to free some memory we can‘t free ourselves
381     */
382     wakeup_kswapd(0);
383     if (gfp_mask & __GFP_WAIT) {
384         __set_current_state(TASK_RUNNING);
385         current->policy |= SCHED_YIELD;
386         schedule();
387     }
388
389     /*
390     * After waking up kswapd, we try to allocate a page
391     * from any zone which isn‘t critical yet.
392     *
393     * Kswapd should, in most situations, bring the situation
394     * back to normal in no time.
395     */
396     page = __alloc_pages_limit(zonelist, order, PAGES_MIN, direct_reclaim);
397     if (page)
398         return page;
399
  1. 我們喚醒內核線程kswapd。 讓他設法在換出一些頁面出來。
  2. 假設是一定要等待到分配的頁面的話, 就讓系統進行調度。 schedule, 為kswapd 讓路。
  3. 然後再次嘗試分配。

3.5 part 5

==================== mm/page_alloc.c 400 477 ====================
[alloc_pages()>__alloc_pages()]
400     /*
401     * Damn, we didn‘t succeed.
402     *
403     * This can be due to 2 reasons:
404     * - we‘re doing a higher-order allocation
405     *   --> move pages to the free list until we succeed
406     * - we‘re /really/ tight on memory
407     *   --> wait on the kswapd waitqueue until memory is freed
408     */
409     if (!(current->flags & PF_MEMALLOC)) {
410         /*
411         * Are we dealing with a higher order allocation?

412 * 413 * Move pages from the inactive_clean to the free list 414 * in the hope of creating a large, physically contiguous 415 * piece of free memory. 416 */ 417 if (order > 0 && (gfp_mask & __GFP_WAIT)) { 418 zone = zonelist->zones; 419 /* First, clean some dirty pages. */ 420 current->flags |= PF_MEMALLOC; 421 page_launder(gfp_mask, 1); 422 current->flags &= ~PF_MEMALLOC; 423 for (;;) { 424 zone_t *z = *(zone++); 425 if (!z) 426 break; 427 if (!z->size) 428 continue; 429 while (z->inactive_clean_pages) { 430 struct page * page; 431 /* Move one page to the free list. */ 432 page = reclaim_page(z); 433 if (!page) 434 break; 435 __free_page(page); 436 /* Try if the allocation succeeds. */ 437 page = rmqueue(z, order); 438 if (page) 439 return page; 440 } 441 } 442 } 443 /* 444 * When we arrive here, we are really tight on memory. 445 * 446 * We wake up kswapd and sleep until kswapd wakes us 447 * up again. After that we loop back to the start. 448 * 449 * We have to do this because something else might eat 450 * the memory kswapd frees for us and we need to be 451 * reliable. Note that we don‘t loop back for higher 452 * order allocations since it is possible that kswapd 453 * simply cannot free a large enough contiguous area 454 * of memory *ever*. 455 */ 456 if ((gfp_mask & (__GFP_WAIT|__GFP_IO)) == (__GFP_WAIT|__GFP_IO)) { 457 wakeup_kswapd(1); 458 memory_pressure++; 459 if (!order) 460 goto try_again; 461 /* 462 * If __GFP_IO isn‘t set, we can‘t wait on kswapd because 463 * kswapd just might need some IO locks /we/ are holding ... 464 * 465 * SUBTLE: The scheduling point above makes sure that 466 * kswapd does get the chance to free memory we can‘t 467 * free ourselves... 468 */ 469 } else if (gfp_mask & __GFP_WAIT) { 470 try_to_free_pages(gfp_mask); 471 memory_pressure++; 472 if (!order) 473 goto try_again; 474 } 475 476 } 477

  1. 這是非內存分配者來進一步嘗試分配頁面的情形
  2. 這裏採用的方式是, 嘗試將不活躍臟頁面中的內容寫入交換區。 將他變為不活躍幹凈頁面, 參與頁面分配。
  3. 假設回收之後。 還是不行, 兩種途徑: 要麽喚醒 kswapd 。自己休眠等待。 在kswapd完畢一輪操作之後, 在讓他喚醒自己, 假設要求分配單個頁面的話, 就又一次運行try_again 部分。

    或者調用 try_to_free_pages 獲取一部分由臟頁面轉化過來的幹凈頁面進行分配。

  4. 為什麽這裏僅僅有當要求單個頁面。 才又一次運行try_again 部分呢?個人的理解: 將臟頁面轉化成幹凈頁面後, 不能保證頁面的連續性, 所以僅僅能處理單個頁面的情形。

    我們覺得系統中管理的頁面塊大小是不一樣的

3.6 part 6

我們前面的分配過程, 都是留了一點後路的。 每一個zone 規定保留的水位 還是要高於 pages_min 的。

這次我們決定榨幹他。

==================== mm/page_alloc.c 478 521 ====================
[alloc_pages()>__alloc_pages()]
478     /*
479     * Final phase: allocate anything we can!
480     *
481     * Higher order allocations, GFP_ATOMIC allocations and
482     * recursive allocations (PF_MEMALLOC) end up here.
483     *
484     * Only recursive allocations can use the very last pages
485     * in the system, otherwise it would be just too easy to
486     * deadlock the system...
487     */
488     zone = zonelist->zones;
489     for (;;) {
490         zone_t *z = *(zone++);
491         struct page * page = NULL;
492         if (!z)
493             break;
494         if (!z->size)
495             BUG();
496
497         /*
498         * SUBTLE: direct_reclaim is only possible if the task
499         * becomes PF_MEMALLOC while looping above. This will
500         * happen when the OOM killer selects this task for
501         * instant execution...
502         */
503         if (direct_reclaim) {
504             page = reclaim_page(z);
505             if (page)
506                 return page;
507         }
508
509         /* XXX: is pages_min/4 a good amount to reserve for this? */
510         if (z->free_pages < z->pages_min / 4 &&
511             !(current->flags & PF_MEMALLOC))
512             continue;
513         page = rmqueue(z, order);
514         if (page)
515             return page;
516     }
517
518     /* No luck.. */
519     printk(KERN_ERR "__alloc_pages: %lu-order allocation failed.\n", order);
520     return NULL;
521  }
  1. 這裏做的事情就是直接進入到zone 中進行分配, 不在考慮 zone 的水位保持的問題了。
  2. 假設這個操作也失敗了, 那就沒辦法了。

4. 基本的分配流程小結

  1. 在每一個存儲節點上 pgdat_t 上嘗試分配
    1. 依據分配策略 zonelist 在每一個管理區zone 上嘗試分配 (__alloc_pages)
      1. 在zone 管理區 的free_area 區域嘗試直接分配
      2. 在zone管理區 嘗試利用 free_area 和 inactive_clean_list 嘗試分配, 並減少對水位的要求
      3. 利用kswapd 交換出一些頁面來嘗試分配
      4. 將inactive_dirty_list 數據寫入交換區。 再嘗試分配
      5. 利用kswapd 釋放些頁面出來, 嘗試分配單個頁面
      6. 假設上面都不行, 不考慮水位要求。 直接在zone 上嘗試分配

Linux 內核源代碼分析 chap 2 存儲管理 (5)