69天探索操作系统-第40天:Linux 内核内存管理

190 阅读6分钟
pro13.webp

1.介绍

Linux内存管理是一个复杂的子系统,负责处理从物理内存分配到虚拟内存映射的所有事务。它确保用户空间和内核空间操作的高效内存使用。Linux内核使用多种技术,包括页表、内存区域和各种分配器,来有效地管理内存。

  • 页表:维护虚拟地址与物理地址之间的映射。Linux 使用多级页表结构来高效管理内存转换。
  • 内存区域:具有特定用途和可访问性特征的内存不同区域。例如,用于设备操作的DMA区域和用于常规内存操作的普通区域。
  • 内存分配器:各种分配机制,包括用于页面分配的伙伴系统和用于内核对象的slab分配器。

2.物理内存管理

伙伴系统是Linux中物理页面管理的基础。它将内存划分为不同大小的块,从而实现内存页面的高效分配和释放。伙伴系统通过合并相邻的空闲块,确保内存碎片最小化。

伙伴系统实施

以下是伙伴系统的简化实现:

#define MAX_ORDER 11
#define PAGE_SIZE 4096

struct free_area {
    struct list_head free_list;
    unsigned long nr_free;
};

struct zone {
    struct free_area free_area[MAX_ORDER];
    unsigned long free_pages;
};

void *alloc_pages(unsigned int order) {
    struct zone *zone = get_current_zone();
    unsigned int current_order;
    struct page *page;

    for (current_order = order; current_order < MAX_ORDER; current_order++) {
        if (!list_empty(&zone->free_area[current_order].free_list)) {
            page = list_first_entry(&zone->free_area[current_order].free_list,
                                  struct page, lru);
            list_del(&page->lru);
            zone->free_area[current_order].nr_free--;

            // Split blocks if necessary
            while (current_order > order) {
                current_order--;
                split_page(page, current_order);
            }

            return page_address(page);
        }
    }
    return NULL;
}

3. 虚拟内存系统

Linux 实现了一个按需分页的虚拟内存系统,该系统允许进程使用比物理内存更多的内存,通过将页面交换到磁盘上来实现。虚拟内存系统还支持共享内存和内存映射文件。

页表实现

以下是页表条目的简化实现:

struct page_table_entry {
    unsigned long pfn : 55;    // Page Frame Number
    unsigned int present : 1;
    unsigned int writable : 1;
    unsigned int user : 1;
    unsigned int accessed : 1;
    unsigned int dirty : 1;
    unsigned int unused : 4;
};

struct page_table {
    struct page_table_entry entries[512];
};

void map_page(unsigned long virtual_addr, unsigned long physical_addr,
              unsigned int flags) {
    struct page_table *pgt = get_current_page_table();
    unsigned int pgt_index = (virtual_addr >> 12) & 0x1FF;

    pgt->entries[pgt_index].pfn = physical_addr >> 12;
    pgt->entries[pgt_index].present = 1;
    pgt->entries[pgt_index].writable = (flags & PAGE_WRITABLE) ? 1 : 0;
    pgt->entries[pgt_index].user = (flags & PAGE_USER) ? 1 : 0;

    // Flush TLB for this address
    flush_tlb_single(virtual_addr);
}

4. slab分配器深度剖析

slab 分配器通过缓存频繁使用的对象,提供高效的内核对象分配。它通过重用相同类型对象的内存来减少碎片并提高性能。

slab缓存实现

以下是 slab 缓存的简化实现:

struct kmem_cache {
    unsigned int object_size;    // Size of each object
    unsigned int align;         // Alignment requirements
    unsigned int flags;         // Cache flags
    const char *name;          // Cache name
    struct array_cache *array;  // Per-CPU object cache
    unsigned int buffer_size;   // Size of each slab

    // Constructors/destructors
    void (*ctor)(void *obj);
    void (*dtor)(void *obj);

    // Slab management
    struct list_head slabs_full;
    struct list_head slabs_partial;
    struct list_head slabs_free;
};

struct kmem_cache *kmem_cache_create(const char *name, size_t size,
                                   size_t align, unsigned long flags,
                                   void (*ctor)(void*)) {
    struct kmem_cache *cache;

    cache = kzalloc(sizeof(struct kmem_cache), GFP_KERNEL);
    if (!cache)
        return NULL;

    cache->object_size = ALIGN(size, align);
    cache->align = align;
    cache->flags = flags;
    cache->name = name;
    cache->ctor = ctor;

    INIT_LIST_HEAD(&cache->slabs_full);
    INIT_LIST_HEAD(&cache->slabs_partial);
    INIT_LIST_HEAD(&cache->slabs_free);

    return cache;
}

5. 页帧管理

Linux 维护着系统内每个物理页帧、的详细信息。struct page 结构用于跟踪每个页面的状态。

页帧跟踪

以下是页面框架跟踪的简化实现:

struct page {
    unsigned long flags;
    atomic_t _count;
    atomic_t _mapcount;
    unsigned int order;
    struct list_head lru;
    struct address_space *mapping;
    pgoff_t index;
    void *virtual;             // Page virtual address
};

void init_page_tracking(void) {
    unsigned long i;
    struct page *page;

    for (i = 0; i < num_physpages; i++) {
        page = &mem_map[i];
        atomic_set(&page->_count, 0);
        atomic_set(&page->_mapcount, -1);
        page->flags = 0;
        page->order = 0;
        INIT_LIST_HEAD(&page->lru);
    }
}

6. 内存区域和节点管理

Linux 将物理内存划分为不同的区域,用于不同的目的,如 DMA、Normal 和 HighMem。每个区域都有其自身的特性和使用限制。

区域管理实施

以下是区域管理的一个简化实现:

struct zone {
    unsigned long watermark[NR_WMARK];
    unsigned long nr_pages;
    unsigned long spanned_pages;
    unsigned long present_pages;

    struct free_area free_area[MAX_ORDER];
    spinlock_t lock;

    // Per-zone statistics
    atomic_long_t vm_stat[NR_VM_ZONE_STAT_ITEMS];

    // Zone compaction
    unsigned long compact_cached_free_pfn;
    unsigned long compact_cached_migrate_pfn;
};

void zone_init(struct zone *zone, unsigned long start_pfn,
               unsigned long size) {
    int i;

    zone->nr_pages = size;
    zone->spanned_pages = size;
    zone->present_pages = size;

    for (i = 0; i < MAX_ORDER; i++) {
        INIT_LIST_HEAD(&zone->free_area[i].free_list);
        zone->free_area[i].nr_free = 0;
    }

    spin_lock_init(&zone->lock);
}

7. 内核内存分配

Linux内核提供了多种内存分配方法,包括用于小规模分配的kmalloc()和用于大规模分配的alloc_pages()

KMALLOC 实现

以下是 kmalloc() 的简化实现:

void *kmalloc(size_t size, gfp_t flags) {
    struct kmem_cache *cache;
    void *ret = NULL;

    // Find appropriate cache for this size
    if (size <= 8) cache = kmalloc_caches[0];
    else if (size <= 16) cache = kmalloc_caches[1];
    else if (size <= 32) cache = kmalloc_caches[2];
    else if (size <= 64) cache = kmalloc_caches[3];
    else if (size <= 128) cache = kmalloc_caches[4];
    else {
        // Use page allocator for large allocations
        int order = get_order(size);
        ret = alloc_pages(flags, order);
        if (ret)
            ret = page_address(ret);
        return ret;
    }

    ret = kmem_cache_alloc(cache, flags);
    return ret;
}

8. 内存回收和交换

Linux 在需要时实现各种机制来回收内存,包括交换和页面回收。

页面回收实现

以下是页面回收的简化实现:

struct scan_control {
    unsigned long nr_to_reclaim;
    unsigned long nr_reclaimed;
    unsigned long nr_scanned;
    unsigned int priority;
    unsigned int may_writepage:1;
    unsigned int may_unmap:1;
    unsigned int may_swap:1;
};

unsigned long try_to_free_pages(struct zone *zone,
                              unsigned int order,
                              gfp_t gfp_mask) {
    struct scan_control sc = {
        .nr_to_reclaim = SWAP_CLUSTER_MAX,
        .priority = DEF_PRIORITY,
        .may_writepage = 1,
        .may_unmap = 1,
        .may_swap = 1,
    };

    unsigned long nr_reclaimed = 0;

    do {
        nr_reclaimed += shrink_inactive_list(zone, &sc);
        if (nr_reclaimed >= sc.nr_to_reclaim)
            break;
        nr_reclaimed += shrink_active_list(zone, &sc);
    } while (nr_reclaimed < sc.nr_to_reclaim);

    return nr_reclaimed;
}

9. 内存调试和监控

Linux 提供了用于调试和监控内存使用的工具和机制,如 vmstatslabtopkmemleak

内存分配

image.png

10. 性能优化

Linux内存管理的关键优化策略包括:

  • 内存紧凑化: 通过移动页面来减少碎片,创建更大的连续块。
  • 透明大页面: 通过为适当的工作负载使用更大的页面大小来减少TLB压力。
  • NUMA意识: 确保内存分配从使用内存的CPU最近的节点进行。
  • 内存块着色: 通过在内存块内对象位置添加移来提高缓存利用率。

11. 总结

Linux内核内存管理是一个复杂的系统,为内核和用户空间提供高效的内存分配和管理。了解其内部原理对于内核开发和系统优化至关重要。