[VMM]分享一个用SystemC编写的页表管理程序

分享一个用SystemC编写的页表管理程序

       摘要:分享一个用SystemC编写的页表管理的程序,这个程序将模拟页表(PDE和PTE)的创建、虚拟地址(VA)到物理地址(PA)的转换,以及对内存的读写操作。

为了简化实现,我们做出以下假设:

页表是两级结构:PDE (Page Directory Entry) 和 PTE (Page Table Entry)。可扩展为三级、四级。
虚拟地址(VA)分为三级:PDE索引、PTE索引和页内偏移。
每个页大小为4KB(12位偏移)。
内存类型(mem_type)和虚拟帧ID(vfid)用于区分不同的内存区域或上下文。
物理内存(cmem)用一个数组模拟。
使用简单的哈希映射来管理虚拟地址分配。

以下是完整的SystemC代码实现:

一、基本数据结构定义

#include <systemc.h>
#include <map>
#include <vector>
#include <list>
#include <iostream>
#include <cmath>

// 可配置页大小 (单位:字节)
enum PageSize {
    PAGE_4K = 4096,      // 4KB
    PAGE_16K = 16384,    // 16KB
    PAGE_64K = 65536,    // 64KB
    PAGE_2M = 2097152,   // 2MB
    PAGE_1G = 1073741824 // 1GB
};

// 页表级别
enum PageTableLevel {
    LEVEL_2 = 2, // 2级页表
    LEVEL_3 = 3, // 3级页表
    LEVEL_4 = 4  // 4级页表
};

// 模拟物理内存 (cmem)
#define CMEM_SIZE (1ULL << 30) // 1GB 物理内存
static uint8_t cmem[CMEM_SIZE];

// 虚拟地址区域,用于地址分配
struct VAArea {
    uint64_t start_va;
    uint64_t size;
    bool allocated;
    VAArea(uint64_t start, uint64_t sz) : start_va(start), size(sz), allocated(false) {}
};

// 页表条目结构,增加大页支持
struct PageTableEntry {
    bool valid;
    bool is_huge;          // 是否为大页映射
    uint64_t physical_base; // 物理基地址
    PageTableEntry* next_level; // 指向下一级页表的指针
    uint64_t page_size;    // 当前条目对应的页大小
    PageTableEntry() : valid(false), is_huge(false), physical_base(0), next_level(nullptr), page_size(0) {}
};

 二、页表管理模块的实现

// 页表管理模块
class PageTableManager : public sc_module {
public:
    SC_HAS_PROCESS(PageTableManager);
    PageTableManager(sc_module_name name, PageSize base_page_size, PageTableLevel level)
        : sc_module(name), base_page_size_(base_page_size), level_(level), next_physical_base_(0) {
        SC_METHOD(run);
        dont_initialize();
        initializePageTableConfig();
    }

private:
    PageSize base_page_size_;   // 基础页大小(最小页大小)
    PageTableLevel level_;      // 页表级别
    uint64_t next_physical_base_; // 下一个可用的物理基地址
    uint32_t bits_per_level_;   // 每级页表的位数
    uint64_t base_page_mask_;   // 基础页大小掩码
    std::vector<uint32_t> level_bits_; // 每级页表的位数分配
    std::vector<uint64_t> level_page_sizes_; // 每级页表支持的页大小(用于大页)

    // 页表存储:pid -> 页表层次结构
    std::map<uint32_t, std::vector<PageTableEntry*>> page_tables_;

    // 虚拟地址区域管理
    std::map<uint32_t, std::list<VAArea>> va_areas_;

    void run() {
        // 初始化代码,如果需要可以在此添加
    }

    // 初始化页表配置(根据页大小和级别分配位数,并配置大页大小)
    void initializePageTableConfig() {
        base_page_mask_ = base_page_size_ - 1;
        uint32_t total_va_bits = 48; // 假设虚拟地址为48位
        uint32_t offset_bits = log2(base_page_size_);
        uint32_t remaining_bits = total_va_bits - offset_bits;
        bits_per_level_ = remaining_bits / level_;
        level_bits_.resize(level_);
        for (int i = 0; i < level_; ++i) {
            level_bits_[i] = bits_per_level_;
        }
        level_bits_[level_ - 1] += remaining_bits % level_;

        // 配置每级页表支持的页大小(大页支持)
        level_page_sizes_.resize(level_);
        level_page_sizes_[level_ - 1] = base_page_size_; // 最后一级使用基础页大小
        for (int i = level_ - 2; i >= 0; --i) {
            level_page_sizes_[i] = level_page_sizes_[i + 1] * (1ULL << level_bits_[i + 1]);
            // 限制最大页大小为1GB
            if (level_page_sizes_[i] > PAGE_1G) {
                level_page_sizes_[i] = PAGE_1G;
            }
        }

        std::cout << "Page Table Config: Base Page Size = " << base_page_size_ << " bytes, Levels = " << level_ << std::endl;
        for (int i = 0; i < level_; ++i) {
            std::cout << "Level " << i << " bits: " << level_bits_[i] << ", Page Size: " << level_page_sizes_[i] << " bytes" << std::endl;
        }
    }

    // 获取指定级别的索引
    uint64_t getLevelIndex(uint64_t va, int level) {
        uint64_t shift = 0;
        for (int i = 0; i < level; ++i) {
            shift += level_bits_[i];
        }
        uint64_t mask = (1ULL << level_bits_[level]) - 1;
        return (va >> shift) & mask;
    }

public:
    // API 1: 创建页表条目,支持大页映射
    bool createPageTable(uint32_t pid, uint64_t va, uint64_t pa, bool use_huge_page = false, int huge_level = 0) {
        if (page_tables_.find(pid) == page_tables_.end()) {
            page_tables_[pid].resize(level_);
            for (int i = 0; i < level_; ++i) {
                page_tables_[pid][i] = new PageTableEntry[1ULL << level_bits_[i]];
            }
        }

        PageTableEntry* current = &page_tables_[pid][0][getLevelIndex(va, 0)];
        int target_level = use_huge_page ? huge_level : level_ - 1;
        for (int i = 0; i < target_level; ++i) {
            if (!current->valid) {
                current->valid = true;
                current->next_level = &page_tables_[pid][i + 1][getLevelIndex(va, i + 1)];
            }
            current = current->next_level;
        }
        current->valid = true;
        current->is_huge = use_huge_page;
        current->page_size = use_huge_page ? level_page_sizes_[target_level] : base_page_size_;
        current->physical_base = pa & ~(current->page_size - 1); // 按页大小对齐

        std::cout << "Created " << (use_huge_page ? "Huge " : "") << "page table for PID: " << pid
                  << ", VA: 0x" << std::hex << va << ", PA: 0x" << pa << ", Page Size: " << current->page_size
                  << std::dec << std::endl;
        return true;
    }

    // API 2: 分配虚拟地址,支持大页映射
    uint64_t allocateVA(uint32_t pid, uint32_t mem_type, uint32_t vfid, uint64_t size, bool use_huge_page = false) {
        if (va_areas_.find(pid) == va_areas_.end()) {
            // 初始化虚拟地址区域(假设每个PID有1TB虚拟地址空间)
            va_areas_[pid].emplace_back((uint64_t)pid << 40, 1ULL << 40);
        }

        // 确定分配的页大小
        uint64_t alloc_size = use_huge_page ? std::max(size, level_page_sizes_[0]) : std::max(size, (uint64_t)base_page_size_);
        for (auto& area : va_areas_[pid]) {
            if (!area.allocated && area.size >= alloc_size) {
                uint64_t va = area.start_va;
                area.allocated = true;
                if (area.size > alloc_size) {
                    va_areas_[pid].emplace_back(area.start_va + alloc_size, area.size - alloc_size);
                }
                area.size = alloc_size;

                // 分配对应的物理地址
                uint64_t pa = next_physical_base_;
                next_physical_base_ += alloc_size;

                // 创建页表映射,决定大页级别
                int huge_level = 0;
                if (use_huge_page) {
                    for (int i = 0; i < level_; ++i) {
                        if (level_page_sizes_[i] <= alloc_size) {
                            huge_level = i;
                            break;
                        }
                    }
                }
                createPageTable(pid, va, pa, use_huge_page, huge_level);

                std::cout << "Allocated " << (use_huge_page ? "Huge " : "") << "VA: 0x" << std::hex << va
                          << " size: 0x" << alloc_size << " for PID: " << pid << ", mem_type: " << mem_type
                          << ", vfid: " << vfid << std::dec << std::endl;
                return va;
            }
        }
        std::cerr << "No available VA for PID: " << pid << std::endl;
        return 0;
    }

    // 辅助函数:虚拟地址转物理地址,支持大页映射
    bool translateVAtoPA(uint32_t pid, uint64_t va, uint64_t& pa) {
        if (page_tables_.find(pid) == page_tables_.end()) {
            std::cerr << "No page table for PID: " << pid << std::endl;
            return false;
        }

        PageTableEntry* current = &page_tables_[pid][0][getLevelIndex(va, 0)];
        uint64_t offset = va & base_page_mask_;
        for (int i = 0; i < level_; ++i) {
            if (!current->valid) {
                std::cerr << "Invalid page table entry at level " << i << " for VA: 0x" << std::hex << va << std::dec << std::endl;
                return false;
            }
            if (current->is_huge) {
                // 大页映射,直接计算物理地址
                uint64_t huge_mask = current->page_size - 1;
                pa = current->physical_base + (va & huge_mask);
                return true;
            }
            if (i < level_ - 1 && current->next_level == nullptr) {
                std::cerr << "No next level at level " << i << " for VA: 0x" << std::hex << va << std::dec << std::endl;
                return false;
            }
            if (i < level_ - 1) {
                current = current->next_level;
            }
        }
        pa = current->physical_base + offset;
        return true;
    }

    // API 3: 写入物理内存
    bool writeCMem(uint32_t pid, uint32_t mem_type, uint32_t vfid, uint64_t va, uint8_t data) {
        uint64_t pa;
        if (!translateVAtoPA(pid, va, pa)) {
            std::cerr << "Translation failed for VA: 0x" << std::hex << va << std::dec << std::endl;
            return false;
        }
        if (pa >= CMEM_SIZE) {
            std::cerr << "Physical address out of range: 0x" << std::hex << pa << std::dec << std::endl;
            return false;
        }
        cmem[pa] = data;
        std::cout << "Wrote data " << (int)data << " to PA: 0x" << std::hex << pa
                  << " (VA: 0x" << va << ")" << std::dec << std::endl;
        return true;
    }

    // API 4: 读取物理内存
    bool readCMem(uint32_t pid, uint32_t mem_type, uint32_t vfid, uint64_t va, uint8_t& data) {
        uint64_t pa;
        if (!translateVAtoPA(pid, va, pa)) {
            std::cerr << "Translation failed for VA: 0x" << std::hex << va << std::dec << std::endl;
            return false;
        }
        if (pa >= CMEM_SIZE) {
            std::cerr << "Physical address out of range: 0x" << std::hex << pa << std::dec << std::endl;
            return false;
        }
        data = cmem[pa];
        std::cout << "Read data " << (int)data << " from PA: 0x" << std::hex << pa
                  << " (VA: 0x" << va << ")" << std::dec << std::endl;
        return true;
    }

    // 回收虚拟地址
    void freeVA(uint32_t pid, uint64_t va) {
        for (auto& area : va_areas_[pid]) {
            if (area.start_va == va && area.allocated) {
                area.allocated = false;
                std::cout << "Freed VA: 0x" << std::hex << va << " for PID: " << pid << std::dec << std::endl;
                // 可以进一步清理页表条目
                break;
            }
        }
    }
};

三、主函数测试代码

// 主函数测试代码
int sc_main(int argc, char* argv[]) {
    // 配置页大小和页表级别
    PageSize base_page_size = PAGE_4K;
    PageTableLevel level = LEVEL_4;

    // 创建页表管理模块
    PageTableManager ptm("PageTableManager", base_page_size, level);

    // 测试API
    uint32_t pid = 1;
    uint32_t mem_type = 2;
    uint32_t vfid = 3;

    // 测试API 2: 分配VA,普通页和小页
    uint64_t va1 = ptm.allocateVA(pid, mem_type, vfid, PAGE_4K, false); // 普通页 (4KB)
    uint64_t va2 = ptm.allocateVA(pid, mem_type, vfid, PAGE_2M, true);  // 大页 (2MB)

    // 测试API 3: 写入cmem
    uint8_t write_data = 0xAA;
    ptm.writeCMem(pid, mem_type, vfid, va1, write_data);
    ptm.writeCMem(pid, mem_type, vfid, va2, write_data);

    // 测试API 4: 读取cmem
    uint8_t read_data = 0;
    ptm.readCMem(pid, mem_type, vfid, va1, read_data);
    ptm.readCMem(pid, mem_type, vfid, va2, read_data);

    // 测试回收VA
    ptm.freeVA(pid, va2);

    sc_start();
    return 0;
}

四、代码扩展说明:

可配置页大小

定义了PageSize枚举类型,支持4K、16K、64K、2M、1G页大小。
PageTableManager构造函数中传入页大小参数,并根据页大小计算偏移位数和页表掩码。

多级页表支持

定义了PageTableLevel枚举类型,支持2级、3级、4级页表。
initializePageTableConfig中动态计算每级页表的位数分配(假设虚拟地址为48位)。
使用指针实现页表层次结构,PageTableEntry包含指向下一级页表的指针。
地址转换时,遍历多级页表,直到最后一级获取物理地址。

复杂地址分配策略

引入VAArea结构,管理虚拟地址区域。
每个PID分配一个大的虚拟地址空间(如1TB),通过空闲列表管理分配和回收。
allocateVA函数从空闲区域中分配虚拟地址,支持指定大小的分配。
增加freeVA函数,支持虚拟地址回收。

模块化设计

PageTableManager类管理页表结构、地址分配和内存操作。
页表配置、地址转换和内存操作逻辑分离,便于维护和扩展。

五、使用说明:

配置页大小和页表级别:在sc_main中设置page_sizelevel参数。
编译运行:需要SystemC库支持,编译时确保链接SystemC库。
测试用例sc_main中包含了分配虚拟地址、读写内存和回收地址的测试代码。
扩展性:可以通过修改total_va_bits支持更大的虚拟地址空间;可以通过扩展VAArea结构支持更复杂的分配策略(如优先分配特定区域)。

六、可能的进一步优化:

页表压缩:支持大页(huge page)映射,减少页表层次。
地址分配优化:引入优先级队列或树结构,优化空闲区域查找。
页表缓存:模拟TLB(Translation Lookaside Buffer),提高地址转换效率。
错误处理:增加更详细的错误检查和日志记录。

如果有具体需求或进一步扩展方向,可以基于此代码继续完善。

七、代码扩展说明

大页标志和页大小

PageTableEntry结构体中增加了is_huge标志,用于指示是否为大页映射。
增加了page_size字段,记录当前页表条目对应的页大小(普通页或大页)。

每级页表支持的页大小

initializePageTableConfig中,为每级页表计算支持的页大小(大页大小)。
较高级别的页表支持更大的页大小(如1GB、2MB),以减少页表层次。

创建页表支持大页

createPageTable中,增加了use_huge_pagehuge_level参数,允许在特定级别创建大页映射。
如果使用大页映射,则在目标级别设置is_huge标志,并记录对应的页大小。

地址分配支持大页

allocateVA中,增加了use_huge_page参数,允许用户指定是否使用大页映射。
根据请求的大小和页表级别,动态选择合适的大页级别。

地址转换支持大页

translateVAtoPA中,检查当前页表条目是否为大页映射(is_huge)。
如果是大页映射,直接根据大页大小计算物理地址,跳过后续级别的页表查询,从而减少页表层次和查询开销。

测试用例

sc_main中,增加了两种分配方式:普通页(4KB)和大页(2MB),并测试读写操作。

八、优势和优化:

减少页表层次:通过大页映射,较大的内存区域可以直接在较高级别页表中完成映射,无需遍历所有级别。
性能提升:减少页表查询次数,模拟实际系统中的大页优势(如减少TLB miss)。
灵活性:支持在不同级别使用不同大小的页,用户可以根据需求选择是否使用大页。

九、可能的进一步优化:

动态大页选择:根据分配大小和系统负载,动态决定是否使用大页。
大页碎片管理:在虚拟地址和物理地址分配中,优化大页的对齐和碎片问题。
TLB模拟:增加TLB(Translation Lookaside Buffer)模拟,进一步优化地址转换性能,特别是在大页映射下的命中率。

       通过以上扩展,代码已经支持了大页映射,并有效减少了页表层次。如果有其他具体需求,可以继续完善代码。

© 版权声明
THE END
如果内容对您有所帮助,就支持一下吧!
点赞0 分享
大肚胡子酸奶瓶的头像 - 宋马
评论 抢沙发

请登录后发表评论

    暂无评论内容