K11 — The Full Path of Physical Page Allocation: alloc_pages and the Buddy System

Key Insight: Every call to alloc_pages embarks on a complete journey from request to physical page frame.


The alloc_pages Function Signature

Bash
alloc_pages is the core function for allocating physical pages in Linux:

struct page *alloc_pages(gfp_t gfp_mask, unsigned int order);

Parameters:
  - gfp_mask: Allocation flags (GFP_KERNEL / GFP_ATOMIC, etc.)
  - order: Allocate 2^order pages (order=01 page, order=24 pages)

Return value:
  - Success: Returns a pointer to struct page
  - Failure: Returns NULL

Key gfp_mask flags:

  Zone selection:
    __GFP_DMA: Allocate only from ZONE_DMA
    __GFP_HIGHMEM: Allocate from ZONE_HIGHMEM
    (Default: ZONE_NORMAL + ZONE_HIGHMEM)

  Behavior:
    __GFP_WAIT: Allow sleeping to wait for memory (slow path)
    __GFP_HIGH: High priority, emergency allocation

  Special:
    __GFP_ZERO: Return zeroed page
    __GFP_NOFAIL: Must not fail (keeps retrying)

GFP_KERNEL = (__GFP_WAIT | __GFP_IO | __GFP_FS)
  → Normal allocation, can wait, can do disk I/O, can access filesystem

Full Allocation Path

Bash
alloc_pages call chain:

alloc_pages(gfp_mask, order)
  → __alloc_pages(gfp_mask, order, 0, NULL)
      → get_page_from_freelist(gfp_mask, order, ...)
          → __alloc_pages_nodemask(gfp_mask, order, ...)
              → try_this_node(node, gfp_mask, order)
                  → Buddy system allocation

Actual path (with watermarks and reclaim):

alloc_pages()
  → check_new_pages()  // Fast path: check current CPU cache
  → if (NULL) __alloc_pages()
      → Watermark check (pages_high / pages_low / pages_min)
      → if (watermark OK) buddy allocation (fast path)
      → if (watermark FAIL) {
          // Wake up kswapd
          // Try direct reclaim
          // Retry buddy allocation
          // Still failing? Return NULL or trigger OOM
        }

Bash
Buddy System core allocation function:

static inline struct page *
__alloc_pages_nodemask(gfp_t gfp_mask, unsigned int order,
                        int preferred_nid, nodemask_t *nodemask)
{
    struct zone *zone;
    struct plist_head *queues;
    enum zone_type classzone_idx;

    // 1. Find the highest-priority available node and zone
    // 2. Check watermarks
    // 3. If OK, call rmqueue_pcplist (per-CPU page lists)
    // 4. If fails, call __rmqueue (actual buddy allocation)
    // 5. If buddy fails, try fallback nodes

    // __rmqueue (actual allocation):
    // for (order = requested; order <= MAX_ORDER; order++) {
    //     page = __rmqueue_smallest(zone, order, migratetype);
    //     if (page) {
    //         if (order > requested)
    //             expand(zone, page, order, requested, migratetype);
    //         return page;
    //     }
    // }
}

Watermark Checking

Bash
Linux divides memory into three watermark levels for each zone:

pages_min:   Minimum reserved pages (cannot be allocated for non-urgent requests)
pages_low:   Low watermark (wake up kswapd when below this)
pages_high:  High watermark (kswapd sleeps when above this)

  Watermark levels:
  ┌─────────────┐  ← pages_high
  │             │
  │  Normal     │  ← pages_low
  │  Zone       │
  │             │  ← pages_min
  ├─────────────┤
  │  Reserved   │  Below pages_min: only __GFP_HIGH can touch it
  └─────────────┘

  User/kernel allocation path:
    1. If watermark >= pages_high: allocate directly (fast path)
    2. If watermark < pages_low: wake up kswapd (background reclaim)
    3. If watermark < pages_min: direct reclaim (block caller)
    4. Still not enough: OOM killer

  View watermarks:
  $ cat /proc/zoneinfo
  Node 0, zone   Normal
    pages free     4061
          min      451
          low      563
          high     676

The Buddy System Core Algorithm

Bash
Buddy system algorithm:

  1. Find a block of size 2^order from the free_area[order] list
  2. If not found, go to the next order (2^(order+1)):
     a. Split the larger block into two "buddies"
     b. Put one half back in the smaller free list
     c. Use the other half
  3. Repeat until a sufficiently large block is found

  Example: Request order=0 (1 page)
  → Free list for order 0 is empty
  → Try order 1 (2 pages):
    ┌──────────┬──────────┐
    │  Buddy A │  Buddy B │  ← Split: Buddy A used, Buddy B returned to order 0
    └──────────┴──────────┘

  → Try order 2 (4 pages):
    ┌──────┬──────┬──────┬──────┐
    │ A*   │  B   │  C   │  D   │  ← Split: A used, B→order1, C+D→order1
    └──────┴──────┴──────┴──────┘          Then B split, C+D stays
Last modified: 2024年7月21日

Author

Comments

Write a Reply or Comment

Your email address will not be published.