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 提供了用于调试和监控内存使用的工具和机制,如 vmstat、slabtop 和 kmemleak。
内存分配
10. 性能优化
Linux内存管理的关键优化策略包括:
- 内存紧凑化: 通过移动页面来减少碎片,创建更大的连续块。
- 透明大页面: 通过为适当的工作负载使用更大的页面大小来减少TLB压力。
- NUMA意识: 确保内存分配从使用内存的CPU最近的节点进行。
- 内存块着色: 通过在内存块内对象位置添加移来提高缓存利用率。
11. 总结
Linux内核内存管理是一个复杂的系统,为内核和用户空间提供高效的内存分配和管理。了解其内部原理对于内核开发和系统优化至关重要。