Linux 虚拟内存管理详解

虚拟内存的重要性

作为科技创业者,我深刻理解虚拟内存管理在操作系统中的核心地位。虚拟内存不仅解决了物理内存容量限制的问题,还为进程提供了独立的地址空间,保证了系统的安全性和稳定性。深入理解虚拟内存机制,对于系统性能优化和故障排查具有重要意义。

虚拟内存的基本概念

地址空间布局

Linux 进程的虚拟地址空间通常分为以下几个部分:

高地址
+------------------+
|     内核空间     |  (3GB-4GB, 内核态访问)
+------------------+
|     栈空间       |  (向下增长)
+------------------+
|     共享库       |  (动态链接库)
+------------------+
|     堆空间       |  (向上增长)
+------------------+
|     BSS段        |  (未初始化全局变量)
+------------------+
|     数据段       |  (已初始化全局变量)
+------------------+
|     代码段       |  (程序指令)
+------------------+
低地址 (0x08048000)

页表机制

虚拟内存通过页表实现虚拟地址到物理地址的映射:

// 页表项结构 (x86)
typedef struct {
    unsigned long present : 1;    // 是否在物理内存中
    unsigned long rw : 1;         // 读写权限
    unsigned long user : 1;       // 用户/内核权限
    unsigned long pwt : 1;        // 写透标志
    unsigned long pcd : 1;        // 禁用缓存
    unsigned long accessed : 1;   // 是否被访问过
    unsigned long dirty : 1;      // 是否被修改过
    unsigned long pat : 1;        // 页属性表
    unsigned long global : 1;     // 全局页
    unsigned long soft : 3;       // 软件可用位
    unsigned long pfn : 20;       // 物理页框号
} pte_t;

// 页目录项结构
typedef struct {
    unsigned long present : 1;
    unsigned long rw : 1;
    unsigned long user : 1;
    unsigned long pwt : 1;
    unsigned long pcd : 1;
    unsigned long accessed : 1;
    unsigned long reserved : 1;
    unsigned long ps : 1;         // 页大小
    unsigned long global : 1;
    unsigned long soft : 3;
    unsigned long pfn : 20;       // 页表物理地址
} pde_t;

// 地址转换示例
static inline unsigned long virt_to_phys(void *addr)
{
    return __pa(addr);
}

static inline void *phys_to_virt(unsigned long addr)
{
    return __va(addr);
}

内存分配机制

伙伴系统

伙伴系统用于管理物理内存页面:

// 伙伴系统数据结构
struct zone {
    unsigned long watermark[NR_WMARK];
    struct per_cpu_pageset *pageset;
    struct free_area free_area[MAX_ORDER];
    // ...
};

struct free_area {
    struct list_head free_list[MIGRATE_TYPES];
    unsigned long nr_free;
};

// 分配页面
struct page *__alloc_pages(gfp_t gfp_mask, unsigned int order,
                           struct zonelist *zonelist)
{
    struct page *page;
    
    // 尝试从 preferred zone 分配
    page = rmqueue(zonelist_zone(zonelist), order, gfp_mask);
    if (page)
        return page;
    
    // 如果失败,尝试从其他 zone 分配或进行内存回收
    page = __alloc_pages_slowpath(gfp_mask, order, zonelist);
    
    return page;
}

// 释放页面
void __free_pages(struct page *page, unsigned int order)
{
    if (put_page_testzero(page)) {
        // 如果页面引用计数为0,释放到伙伴系统
        free_the_page(page, order);
    }
}

Slab 分配器

Slab 分配器用于高效分配小块内存:

// Slab 缓存结构
struct kmem_cache {
    const char *name;
    unsigned int object_size;
    unsigned int size;
    unsigned int align;
    slab_flags_t flags;
    unsigned int useroffset;
    unsigned int usersize;
    
    struct kmem_cache_node *node[MAX_NUMNODES];
    // ...
};

// 创建 slab 缓存
struct kmem_cache *kmem_cache_create(const char *name,
                                     size_t size,
                                     size_t align,
                                     unsigned long flags,
                                     void (*ctor)(void *))
{
    struct kmem_cache *s;
    
    s = kzalloc(sizeof(struct kmem_cache), GFP_KERNEL);
    if (!s)
        return NULL;
    
    s->name = name;
    s->object_size = size;
    s->align = align;
    s->ctor = ctor;
    
    // 初始化 slab 管理结构
    // ...
    
    return s;
}

// 从 slab 分配对象
void *kmem_cache_alloc(struct kmem_cache *cachep, gfp_t flags)
{
    void *objp;
    struct slab *slabp;
    
    // 获取当前 CPU 的 slab
    slabp = cachep->cpu_slab->slab;
    
    if (slabp && slabp->freelist) {
        // 从当前 slab 分配
        objp = slabp->freelist;
        slabp->freelist = get_free_obj(slabp, objp);
    } else {
        // 当前 slab 已满,分配新的 slab
        objp = new_slab_objects(cachep, flags);
    }
    
    return objp;
}

// 释放对象到 slab
void kmem_cache_free(struct kmem_cache *cachep, void *objp)
{
    struct slab *slabp;
    unsigned int objnr;
    
    slabp = virt_to_slab(objp);
    objnr = obj_to_index(cachep, slabp, objp);
    
    // 将对象加入空闲列表
    set_free_obj(slabp, objnr, slabp->freelist);
    slabp->freelist = objp;
}

页面置换算法

LRU 页面置换

Linux 使用改进的 LRU 算法进行页面置换:

// 页面回收控制结构
struct scan_control {
    unsigned long nr_to_scan;
    unsigned long nr_scanned;
    unsigned long nr_reclaimed;
    
    gfp_t gfp_mask;
    int priority;
    unsigned int may_writepage : 1;
    unsigned int may_unmap : 1;
    unsigned int may_swap : 1;
};

// 页面回收主函数
static unsigned long shrink_zones(unsigned long nr_to_scan,
                                  struct zonelist *zonelist,
                                  struct scan_control *sc)
{
    struct zone *zone;
    unsigned long nr_reclaimed = 0;
    
    // 遍历所有 zone
    for_each_zone_zonelist(zone, zonelist) {
        if (!populated_zone(zone))
            continue;
        
        // 回收匿名页面和文件页面
        nr_reclaimed += shrink_zone(zone, sc);
    }
    
    return nr_reclaimed;
}

// 回收页面
static unsigned long shrink_page_list(struct list_head *page_list,
                                      struct pglist_data *pgdat,
                                      struct scan_control *sc,
                                      enum ttu_flags ttu_flags,
                                      unsigned long *ret_nr_dirty,
                                      unsigned long *ret_nr_writeback)
{
    struct page *page;
    unsigned long nr_reclaimed = 0;
    
    while (!list_empty(page_list)) {
        page = lru_to_page(page_list);
        list_del(&page->lru);
        
        // 检查页面是否可以回收
        if (!may_write_to_inode(page_mapping(page), sc))
            continue;
        
        // 尝试回收页面
        if (try_to_unmap(page, ttu_flags) != SWAP_FAIL) {
            // 页面成功解除映射,可以回收
            if (PageDirty(page)) {
                // 脏页面需要先写回
                writepage(page, sc);
            } else {
                // 直接回收页面
                __remove_mapping(page_mapping(page), page);
                nr_reclaimed++;
            }
        }
    }
    
    return nr_reclaimed;
}

创业视角看虚拟内存

1. 系统性能优化

作为创业者,理解虚拟内存可以帮助我们优化产品性能:

  • 内存使用优化:通过分析内存使用情况,优化内存分配策略
  • 缓存优化:合理利用页缓存,提高 I/O 性能
  • OOM 处理:设计合理的 OOM 处理机制,避免系统崩溃

2. 成本控制

虚拟内存管理可以影响产品成本:

  • 硬件选择:通过优化内存使用,可以降低对物理内存的需求
  • 云资源优化:在云环境中,合理的内存管理可以降低成本
  • 嵌入式设备:在资源受限环境中,高效的内存管理尤为重要

3. 产品稳定性

虚拟内存管理直接影响产品稳定性:

  • 内存泄漏检测:及时发现和修复内存泄漏
  • 内存碎片整理:减少内存碎片,提高内存利用率
  • 故障隔离:进程间内存隔离,防止相互影响

实践技巧

1. 内存监控

# 查看内存使用情况
free -h

# 查看详细内存统计
cat /proc/meminfo

# 查看进程内存使用
pmap -d <pid>

# 查看 slab 使用情况
slabtop

# 查看页缓存
cat /proc/sys/vm/drop_caches

2. 内存调优

# 调整 swappiness
sysctl vm.swappiness=10

# 调整脏页比例
sysctl vm.dirty_ratio=40
sysctl vm.dirty_background_ratio=10

# 调整 overcommit 策略
sysctl vm.overcommit_memory=2
sysctl vm.overcommit_ratio=80

3. 内存调试

// 检测内存泄漏
#include <linux/kmemleak.h>

void test_kmemleak(void)
{
    char *ptr = kmalloc(100, GFP_KERNEL);
    // 故意不释放,用于测试 kmemleak
    // kfree(ptr);
}

// 查看 kmemleak 报告
echo scan > /sys/kernel/debug/kmemleak
cat /sys/kernel/debug/kmemleak

总结

Linux 虚拟内存管理是一个复杂而精细的系统,它不仅为进程提供了独立的地址空间,还通过高效的页面管理算法优化了系统性能。作为创业者,深入理解虚拟内存机制,不仅可以帮助我们优化产品性能,还可以为技术选型和成本控制提供依据。

正如我的口头禅所说:"工作也要流程化",虚拟内存管理也需要建立一套系统化的流程和方法。通过合理的内存分配策略、有效的页面置换机制和完善的监控体系,我们可以构建出高性能、高稳定性的系统。

在技术创业的道路上,虚拟内存管理不仅是技术问题,更是产品竞争力的重要组成部分。只有深入理解和掌握虚拟内存技术,才能开发出满足市场需求的高质量产品。

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐