分段与分页技术详解
分段和分页是现代操作系统内存管理的两种核心非连续分配技术,它们解决了连续内存分配导致的内存碎片问题,并支持虚拟内存实现。
1. 技术原理对比
| 特性维度 | 分段存储管理 | 分页存储管理 |
|---|---|---|
| 基本单位 | 逻辑段(按程序结构划分) | 固定大小的物理页框和逻辑页 |
| 划分依据 | 程序的逻辑结构(如代码段、数据段) | 系统的物理内存布局 |
| 地址空间 | 二维地址(段号+段内偏移) | 一维线性地址 |
| 碎片问题 | 产生外部碎片 | 产生内部碎片 |
| 共享与保护 | 天然支持(以段为单位) | 需额外机制支持 |
| 管理开销 | 段表、段长检查 | 页表、TLB快表 |
2. 分段存储管理实现
分段基于程序的逻辑结构,将地址空间划分为若干段(Segment),每个段有独立的名称和长度。
地址转换流程:
- CPU生成逻辑地址
(s, d),s为段号,d为段内偏移 - 通过段表寄存器找到段表基址
- 用段号s索引段表,获取该段基址和段长
- 检查偏移d是否小于段长(越界保护)
- 物理地址 = 段基址 + 偏移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。
地址转换流程:
- CPU生成逻辑地址,分为页号p和页内偏移d
- 通过页表寄存器找到页表基址
- 用页号p索引页表,获取页框号f
- 物理地址 = 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. 段页式存储管理
结合分段和分页优点,先分段再分页,提供逻辑结构支持和内存利用率平衡。
地址转换流程:
- CPU生成逻辑地址
(s, p, d),s为段号,p为段内页号,d为页内偏移 - 通过段号s查找段表,获取该段的页表基址
- 通过页号p在页表中查找物理页框号f
- 物理地址 = 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等前端框架使用虚拟分页处理大规模数据
- 内存映射文件:将文件映射到进程地址空间
性能优化技术:
- TLB快表:缓存最近使用的页表项,加速地址转换
- 多级页表:减少页表内存占用,如x86-64的四级页表
- 反向页表:物理页框到进程的映射,节省大量页表空间
6. 技术演进与挑战
从早期覆盖技术、交换技术到现代虚拟内存,分段和分页技术不断演进。请求分页系统在程序运行时按需调入页面,结合页面置换算法(LRU、FIFO等)和缺页中断处理机制,实现了比物理内存更大的虚拟地址空间。
关键挑战包括:
- 内部碎片:分页系统中最后一页未用满的空间浪费
- 外部碎片:分段系统中内存分配释放产生的空隙
- TLB失效开销:频繁的地址转换影响性能
- 页面置换抖动:频繁的页面换入换出降低系统效率
分段提供自然的程序结构支持,分页提供高效的内存利用率,两者结合形成现代操作系统的内存管理基础。在实际系统设计中,需要根据具体应用场景和硬件特性选择合适的技术组合。
参考来源
- PrimeReact大数据处理终极指南:虚拟滚动与数据分页的完美解决方案
- 操作系统知识点复习(2):内存管理
- OS存储器管理相关练习
- 传统存储管理要求在程序运行前必须将其全部装入主存,若主存空间不足,则作业无法执行
- 操作系统精髓-内存管理、调度及其他基础总结
- 操作系统2-物理内存管理(连续内存分配,非连续内存分配)—— lab2习题