69天探索操作系统-第50天:虚拟内存管理系统

1. 介绍

虚拟内存是现代操作系统的关键组成部分,它允许进程通过在RAM和磁盘之间交换数据来使用比物理内存更多的内存。提供的代码定义了虚拟内存管理的基本结构,例如用于页表条目的pte_t和用于物理内存页的struct page。这些结构对于管理内存映射、页表和页面故障至关重要。

struct vm_area_struct 结构体表示一个虚拟内存区域(VMA),它是一个具有特定属性(如权限和后端存储)的连续虚拟内存区域。VMA 用于管理进程的内存映射,确保每个进程都有其独立的地址空间。这种设置是内核中实现虚拟内存管理的基础。

2. 页表管理

页表用于将虚拟地址转换为物理地址。结构体page_table表示一个页表,其中包含条目(PTEs),这些条目将虚拟页映射到物理页。allocate_page_table函数分配并初始化一个新的页表,而walk_page_table函数遍历页表层次结构以查找或创建给定虚拟地址的PTE。

c 复制代码
struct page_table {
    pte_t *entries;
    unsigned int level;
    spinlock_t lock;
    struct page *page;
};

static inline unsigned long pte_index(virtual_addr_t addr, int level) {
    return (addr >> (PAGE_SHIFT + level * 9)) & 0x1FF;
}

static int allocate_page_table(struct page_table *pt) {
    struct page *page = alloc_page(GFP_KERNEL);
    if (!page)
        return -ENOMEM;

    pt->entries = page_address(page);
    pt->page = page;
    memset(pt->entries, 0, PAGE_SIZE);

    return 0;
}

static pte_t* walk_page_table(struct page_table *pgd,
                            virtual_addr_t addr,
                            bool create) {
    struct page_table *current_pt = pgd;
    pte_t *pte;
    int level;

    for (level = 3; level > 0; level--) {
        unsigned long index = pte_index(addr, level);
        pte = &current_pt->entries[index];

        if (!(*pte & PTE_PRESENT)) {
            if (!create)
                return NULL;

            struct page_table *new_pt;
            int ret = allocate_page_table(new_pt);
            if (ret)
                return NULL;

            *pte = page_to_phys(new_pt->page) | PTE_PRESENT | PTE_USER;
        }

        current_pt = phys_to_virt(PTE_ADDR(*pte));
    }

    return &current_pt->entries[pte_index(addr, 0)];
}

walk_page_table 函数对于地址转换至关重要,因为它遍历页表层次结构以查找或创建适当的页表条目(PTE)。这确保了虚拟地址可以高效地映射到物理地址。

3. 内存映射

内存映射允许进程将文件或匿名内存映射到其地址空间。struct vm_operations_struct 定义了管理内存映射的操作,如打开、关闭和故障处理。map_page_range 函数将虚拟地址范围映射到物理地址,而 find_vma 函数则定位给定虚拟地址的 VMA。

c 复制代码
struct vm_operations_struct {
    void (*open)(struct vm_area_struct *area);
    void (*close)(struct vm_area_struct *area);
    int (*fault)(struct vm_area_struct *area, struct vm_fault *vmf);
};

static int map_page_range(struct vm_area_struct *vma,
                         virtual_addr_t start,
                         unsigned long size,
                         physical_addr_t phys_addr,
                         pgprot_t prot) {
    unsigned long addr;
    int ret = 0;

    for (addr = start; addr < start + size; addr += PAGE_SIZE) {
        pte_t *pte = walk_page_table(vma->mm->pgd, addr, true);
        if (!pte) {
            ret = -ENOMEM;
            break;
        }

        physical_addr_t pa = phys_addr + (addr - start);
        *pte = pa | pgprot_val(prot);
        flush_tlb_page(vma, addr);
    }

    return ret;
}

static struct vm_area_struct *find_vma(struct mm_struct *mm,
                                     virtual_addr_t addr) {
    struct vm_area_struct *vma;

    if (!mm)
        return NULL;

    for (vma = mm->mmap; vma; vma = vma->vm_next) {
        if (addr >= vma->start && addr < vma->end)
            return vma;
    }

    return NULL;
}

这些函数确保内存映射被正确创建和管理,从而使进程能够高效地访问文件或匿名内存。

4. 页故障处理程序

当进程访问一个未映射到物理内存的虚拟地址时,会发生页错误。handle_page_fault 函数通过将所需的页映射到内存来处理页错误。如果错误是由于缺少页引起的,它会分配一个新的页并更新页表。如果错误是由于权限违规引起的,它会返回一个错误。

c 复制代码
struct vm_fault {
    virtual_addr_t address;
    unsigned int flags;
    pte_t *pte;
    struct page *page;
};

static int handle_page_fault(struct mm_struct *mm,
                           virtual_addr_t addr,
                           unsigned int flags) {
    struct vm_area_struct *vma;
    struct vm_fault vmf;
    int ret;

    vma = find_vma(mm, addr);
    if (!vma)
        return -EFAULT;

    vmf.address = addr;
    vmf.flags = flags;
    vmf.pte = walk_page_table(mm->pgd, addr, true);

    if (!vmf.pte)
        return -ENOMEM;

    if (vma->vm_ops && vma->vm_ops->fault) {
        ret = vma->vm_ops->fault(vma, &vmf);
        if (ret)
            return ret;
    } else {
        // Anonymous memory - allocate new page
        struct page *page = alloc_page(GFP_KERNEL);
        if (!page)
            return -ENOMEM;

        *vmf.pte = page_to_phys(page) | PTE_PRESENT | PTE_USER;
        if (flags & FAULT_FLAG_WRITE)
            *vmf.pte |= PTE_WRITABLE;
    }

    flush_tlb_page(vma, addr);
    return 0;
}

页故障处理程序确保即使内存最初未映射,进程也能访问内存,从而提供一种大型、连续的地址空间错觉。

5. 页面替换

当物理内存满时,操作系统必须清理页,以腾出空间给新页。struct lru_list 表示一个用于跟踪页使用的最少最近使用(LRU)列表。get_lru_page 函数检索最少最近使用的页面,而 add_to_lru 函数将页面添加到 LRU 列表中。page_out 函数在释放页面之前将脏页面写入交换空间。

c 复制代码
struct lru_list {
    struct list_head head;
    spinlock_t lock;
    unsigned long nr_pages;
};

static struct page* get_lru_page(struct lru_list *lru) {
    struct page *page = NULL;

    spin_lock(&lru->lock);

    if (!list_empty(&lru->head)) {
        page = list_first_entry(&lru->head, struct page, list);
        list_del(&page->list);
        lru->nr_pages--;
    }

    spin_unlock(&lru->lock);
    return page;
}

static void add_to_lru(struct lru_list *lru, struct page *page) {
    spin_lock(&lru->lock);

    list_add_tail(&page->list, &lru->head);
    lru->nr_pages++;

    spin_unlock(&lru->lock);
}

static int page_out(struct page *page) {
    if (page->flags & PAGE_DIRTY) {
        int ret = write_to_swap(page);
        if (ret)
            return ret;
    }

    free_page(page);
    return 0;
}

LRU算法确保最不常用的页面首先被移除,从而优化内存使用并最小化性能下降。

6. TLB 管理

查找缓冲区(TLB)缓存虚拟到物理地址的转换,以加快内存访问速度。结构体 tlb_entry 表示一个 TLB 条目,而结构体 tlb_manager 管理着 TLB。flush_tlb_entry 函数使单个 TLB 条目无效,而 flush_tlb_range 函数使一系列 TLB 条目无效。

c 复制代码
struct tlb_entry {
    virtual_addr_t vaddr;
    physical_addr_t paddr;
    unsigned long flags;
    unsigned long timestamp;
};

struct tlb_manager {
    struct tlb_entry *entries;
    unsigned int size;
    unsigned int current;
    spinlock_t lock;
};

static void flush_tlb_entry(virtual_addr_t addr) {
    asm volatile("invlpg (%0)" :: "r"(addr) : "memory");
}

static void flush_tlb_range(struct vm_area_struct *vma,
                          virtual_addr_t start,
                          virtual_addr_t end) {
    virtual_addr_t addr;

    for (addr = start; addr < end; addr += PAGE_SIZE)
        flush_tlb_entry(addr);
}

static struct tlb_entry* find_tlb_entry(struct tlb_manager *tlb,
                                      virtual_addr_t addr) {
    unsigned int i;

    for (i = 0; i < tlb->size; i++) {
        if (tlb->entries[i].vaddr == addr)
            return &tlb->entries[i];
    }

    return NULL;
}

TLB管理确保地址转换高效缓存,减少页表遍历的开销。

7. 内存分配

内存分配器管理物理内存页的分配和释放。结构体page_allocator代表分配器,它使用伙伴系统来管理空闲内存块。alloc_pages函数分配一个页块,而free_pages函数释放一个页块。

c 复制代码
struct page_allocator {
    struct list_head free_areas[MAX_ORDER];
    spinlock_t lock;
    unsigned long *bitmap;
    unsigned long total_pages;
};

static struct page* alloc_pages(unsigned int order) {
    struct page_allocator *allocator = &global_allocator;
    struct page *page = NULL;
    int current_order;

    spin_lock(&allocator->lock);

    for (current_order = order; current_order < MAX_ORDER; current_order++) {
        if (!list_empty(&allocator->free_areas[current_order])) {
            page = list_first_entry(&allocator->free_areas[current_order],
                                  struct page, list);
            list_del(&page->list);
            break;
        }
    }

    if (page && current_order > order) {
        // Split the block
        split_page_block(allocator, page, current_order, order);
    }

    spin_unlock(&allocator->lock);
    return page;
}

static void free_pages(struct page *page, unsigned int order) {
    struct page_allocator *allocator = &global_allocator;

    spin_lock(&allocator->lock);

    // Try to merge with buddies
    while (order < MAX_ORDER - 1) {
        struct page *buddy = get_buddy_page(page, order);
        if (!page_is_buddy(buddy, order))
            break;

        list_del(&buddy->list);
        page = merge_pages(page, buddy, order);
        order++;
    }

    list_add(&page->list, &allocator->free_areas[order]);

    spin_unlock(&allocator->lock);
}

伙伴系统确保内存分配和释放高效,从而最小化碎片。

c 复制代码
struct swap_info {
    struct file *swap_file;
    unsigned long *bitmap;
    unsigned long max_pages;
    spinlock_t lock;
    atomic_t usage;
};

static int write_to_swap(struct page *page) {
    struct swap_info *si = &swap_info_struct;
    unsigned long offset;
    int ret;

    spin_lock(&si->lock);
    offset = find_first_zero_bit(si->bitmap, si->max_pages);
    if (offset >= si->max_pages) {
        spin_unlock(&si->lock);
        return -ENOSPC;
    }

    set_bit(offset, si->bitmap);
    spin_unlock(&si->lock);

    ret = write_page_to_swap_file(si->swap_file, page, offset);
    if (ret) {
        clear_bit(offset, si->bitmap);
        return ret;
    }

    page->swap_entry = swp_entry(si->type, offset);
    return 0;
}

static int read_from_swap(struct page *page, swp_entry_t entry) {
    struct swap_info *si = &swap_info_struct;
    unsigned long offset = swp_offset(entry);
    int ret;

    if (offset >= si->max_pages)
        return -EINVAL;

    ret = read_page_from_swap_file(si->swap_file, page, offset);
    if (!ret) {
        spin_lock(&si->lock);
        clear_bit(offset, si->bitmap);
        spin_unlock(&si->lock);
    }

    return ret;
}

交换系统确保操作系统可以通过将较少使用的页面移动到磁盘来处理内存压力。

9. 性能监控

结构体vm_stats跟踪关键性能指标,如页面错误、交换活动以及内存分配成功/失败率。update_vm_stats函数更新这些指标,而print_vm_stats函数则显示它们。

c 复制代码
struct vm_stats {
    atomic_t page_faults;
    atomic_t major_faults;
    atomic_t minor_faults;
    atomic_t swap_ins;
    atomic_t swap_outs;
    atomic_t alloc_success;
    atomic_t alloc_fail;
};

static struct vm_stats vm_stats;

static void update_vm_stats(enum vm_stat_type type) {
    switch (type) {
        case VM_STAT_PAGE_FAULT:
            atomic_inc(&vm_stats.page_faults);
            break;
        case VM_STAT_MAJOR_FAULT:
            atomic_inc(&vm_stats.major_faults);
            break;
        case VM_STAT_MINOR_FAULT:
            atomic_inc(&vm_stats.minor_faults);
            break;
        case VM_STAT_SWAP_IN:
            atomic_inc(&vm_stats.swap_ins);
            break;
        case VM_STAT_SWAP_OUT:
            atomic_inc(&vm_stats.swap_outs);
            break;
    }
}

static void print_vm_stats(void) {
    printf("Virtual Memory Statistics:\n");
    printf("Page Faults: %d\n", atomic_read(&vm_stats.page_faults));
    printf("Major Faults: %d\n", atomic_read(&vm_stats.major_faults));
    printf("Minor Faults: %d\n", atomic_read(&vm_stats.minor_faults));
    printf("Swap Ins: %d\n", atomic_read(&vm_stats.swap_ins));
    printf("Swap Outs: %d\n", atomic_read(&vm_stats.swap_outs));
    printf("Allocation Success: %d\n", atomic_read(&vm_stats.alloc_success));
    printf("Allocation Failures: %d\n", atomic_read(&vm_stats.alloc_fail));
}

这些指标为虚拟内存系统的性能提供了宝贵的见解,帮助管理员识别和解决瓶颈。

10. 总结

虚拟内存管理是现代操作系统的复杂但关键组成部分。提供的代码展示了关键组件,如页表管理、内存映射、页面故障处理和交换。通过精心设计和实现这些机制,开发人员可以创建高效且健壮的虚拟内存系统,以满足现代应用的需求。

相关推荐
追逐时光者3 分钟前
精选 4 款开源免费、美观实用的 MAUI UI 组件库,助力轻松构建美观且功能丰富的应用程序!
后端·.net
你的人类朋友1 小时前
【Docker】说说卷挂载与绑定挂载
后端·docker·容器
间彧1 小时前
在高并发场景下,如何平衡QPS和TPS的监控资源消耗?
后端
间彧1 小时前
QPS和TPS的区别,在实际项目中,如何准确测量和监控QPS和TPS?
后端
间彧2 小时前
消息队列(RocketMQ、RabbitMQ、Kafka、ActiveMQ)对比与选型指南
后端·消息队列
brzhang3 小时前
AI Agent 干不好活,不是它笨,告诉你一个残忍的现实,是你给他的工具太难用了
前端·后端·架构
brzhang3 小时前
一文说明白为什么现在 AI Agent 都把重点放在上下文工程(context engineering)上?
前端·后端·架构
Roye_ack3 小时前
【项目实战 Day9】springboot + vue 苍穹外卖系统(用户端订单模块 + 商家端订单管理模块 完结)
java·vue.js·spring boot·后端·mybatis
AAA修煤气灶刘哥5 小时前
面试必问的CAS和ConcurrentHashMap,你搞懂了吗?
后端·面试
SirLancelot15 小时前
MinIO-基本介绍(一)基本概念、特点、适用场景
后端·云原生·中间件·容器·aws·对象存储·minio