分段与分页技术详解

分段和分页是现代操作系统内存管理的两种核心非连续分配技术,它们解决了连续内存分配导致的内存碎片问题,并支持虚拟内存实现。

1. 技术原理对比

特性维度 分段存储管理 分页存储管理
基本单位 逻辑段(按程序结构划分) 固定大小的物理页框和逻辑页
划分依据 程序的逻辑结构(如代码段、数据段) 系统的物理内存布局
地址空间 二维地址(段号+段内偏移) 一维线性地址
碎片问题 产生外部碎片 产生内部碎片
共享与保护 天然支持(以段为单位) 需额外机制支持
管理开销 段表、段长检查 页表、TLB快表

2. 分段存储管理实现

分段基于程序的逻辑结构,将地址空间划分为若干段(Segment),每个段有独立的名称和长度。

地址转换流程:

  1. CPU生成逻辑地址(s, d),s为段号,d为段内偏移
  2. 通过段表寄存器找到段表基址
  3. 用段号s索引段表,获取该段基址和段长
  4. 检查偏移d是否小于段长(越界保护)
  5. 物理地址 = 段基址 + 偏移d

分段管理数据结构示例:

// 段表项结构
struct SegmentTableEntry {
    int base;      // 段在内存中的起始地址
    int limit;     // 段长度限制
    int valid;     // 段是否在内存中
    int rwx;       // 读写执行权限标志
};

// 地址转换函数
int translateSegmentedAddress(int segmentNum, int offset, SegmentTableEntry* segTable) {
    if (segmentNum >= MAX_SEGMENTS || offset < 0) {
        return -1; // 非法访问
    }
    
    SegmentTableEntry entry = segTable[segmentNum];
    if (!entry.valid) {
        triggerSegmentFault(); // 段错误
        return -1;
    }
    if (offset >= entry.limit) {
        triggerProtectionFault(); // 越界保护
        return -1;
    }
    
    return entry.base + offset; // 返回物理地址
}

分段支持高效的共享与保护,例如多个进程可共享只读代码段,而各自拥有独立的数据段。

3. 分页存储管理实现

分页将物理内存和逻辑地址空间划分为固定大小的页框(Frame)和页(Page),通常大小为4KB。

地址转换流程:

  1. CPU生成逻辑地址,分为页号p和页内偏移d
  2. 通过页表寄存器找到页表基址
  3. 用页号p索引页表,获取页框号f
  4. 物理地址 = f × 页大小 + d

分页管理核心代码:

#define PAGE_SIZE 4096 // 4KB页大小
#define PAGE_MASK 0xFFF // 页内偏移掩码

// 页表项结构
struct PageTableEntry {
    int frameNum;   // 物理页框号
    int valid;      // 页是否在内存中
    int dirty;      // 页是否被修改
    int referenced; // 访问标志(用于页面置换)
    int protection; // 访问权限
};

// 分页地址转换
int translatePagedAddress(int logicalAddr, PageTableEntry* pageTable) {
    int pageNum = logicalAddr / PAGE_SIZE;     // 计算页号
    int offset = logicalAddr & PAGE_MASK;      // 计算页内偏移
    
    if (pageNum >= MAX_PAGES) {
        return -1; // 页号越界
    }
    
    PageTableEntry pte = pageTable[pageNum];
    if (!pte.valid) {
        triggerPageFault(pageNum); // 触发缺页中断
        return -1;
    }
    
    // 检查访问权限
    if ((pte.protection & CURRENT_ACCESS) == 0) {
        triggerProtectionFault();
        return -1;
    }
    
    // 更新访问标志(用于LRU等置换算法)
    pte.referenced = 1;
    
    return (pte.frameNum * PAGE_SIZE) + offset; // 合成物理地址
}

4. 段页式存储管理

结合分段和分页优点,先分段再分页,提供逻辑结构支持和内存利用率平衡。

地址转换流程:

  1. CPU生成逻辑地址(s, p, d),s为段号,p为段内页号,d为页内偏移
  2. 通过段号s查找段表,获取该段的页表基址
  3. 通过页号p在页表中查找物理页框号f
  4. 物理地址 = f × 页大小 + d
// 段页式地址转换
int translateSegPagedAddress(int segmentNum, int pageNum, int offset) {
    // 1. 段表查找
    SegmentTableEntry segEntry = segmentTable[segmentNum];
    if (!segEntry.valid) {
        triggerSegmentFault();
        return -1;
    }
    
    // 2. 获取该段的页表
    PageTableEntry* pageTable = segEntry.pageTablePtr;
    
    // 3. 页表查找
    PageTableEntry pageEntry = pageTable[pageNum];
    if (!pageEntry.valid) {
        triggerPageFault(segmentNum, pageNum);
        return -1;
    }
    
    // 4. 合成物理地址
    return (pageEntry.frameNum * PAGE_SIZE) + offset;
}

5. 实际应用场景

分段适用场景:

  • 程序模块化管理:编译器生成的目标文件通常包含.text、.data、.bss等段
  • 内存共享:多个进程共享动态链接库的代码段
  • 内存保护:设置代码段只读、数据段可读写

分页适用场景:

  • 虚拟内存系统:Linux、Windows等现代操作系统采用请求分页
  • 大数据处理:PrimeReact等前端框架使用虚拟分页处理大规模数据
  • 内存映射文件:将文件映射到进程地址空间

性能优化技术:

  1. TLB快表:缓存最近使用的页表项,加速地址转换
  2. 多级页表:减少页表内存占用,如x86-64的四级页表
  3. 反向页表:物理页框到进程的映射,节省大量页表空间

6. 技术演进与挑战

从早期覆盖技术、交换技术到现代虚拟内存,分段和分页技术不断演进。请求分页系统在程序运行时按需调入页面,结合页面置换算法(LRU、FIFO等)和缺页中断处理机制,实现了比物理内存更大的虚拟地址空间。

关键挑战包括:

  • 内部碎片:分页系统中最后一页未用满的空间浪费
  • 外部碎片:分段系统中内存分配释放产生的空隙
  • TLB失效开销:频繁的地址转换影响性能
  • 页面置换抖动:频繁的页面换入换出降低系统效率

分段提供自然的程序结构支持,分页提供高效的内存利用率,两者结合形成现代操作系统的内存管理基础。在实际系统设计中,需要根据具体应用场景和硬件特性选择合适的技术组合。


参考来源

 

Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐