段页式存储管理
内存分页存储
一级页表

本质上是将虚拟地址页号 ( vpn) 通过页表映射为物理页号 (ppn) 的过程
中间涉及
- 根据页面大小 (
PAGE_SIZE),将虚拟地址 (va) 拆分为虚拟地址页号 (vpn) 和 页内偏移 (offset),做法是算数位移 - 根据页表起始地址(
page_table*)、数组元素长度 (sizeof(uint32_t)) 和偏移量 (即 虚拟地址页号 (vpn) ) 得到物理页表的值 (ppn),做法是计算数组偏移量 - 组装物理页表 (
ppn) 和页内偏移 (offset),获得物理地址 (pa)
C 语言程序示例(加入了缺页和越界错误判断,以及预留 1 bit 用于判断页表是否有效)
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#define PAGE_SIZE 4096U // 4KB
#define PAGE_OFFSET_BITS 12U // log2(PAGE_SIZE)
#define PAGE_TABLE_ENTRIES 1024U // 示例:支持 1024 个虚拟页
#define INVALID_PTE 0U // 表示无效的页表项
// 页表:每个条目为一个 32 位无符号数
// bit0: valid (1 = 映射有效;0 = 无效/未映射)
// bit31..1: 物理页号 (PPN)
static uint32_t page_table[PAGE_TABLE_ENTRIES];
/**
* 初始化指定虚拟页号 vpn 到物理页号 ppn 的映射,
* 并将其标记为有效。
*/
void map_page(uint32_t vpn, uint32_t ppn) {
if (vpn >= PAGE_TABLE_ENTRIES) {
fprintf(stderr, "Error: vpn out of range\n");
exit(1);
}
// 物理页号左移 1 位,高 31 位存 PPN;最低位设为 1(valid)
page_table[vpn] = (ppn << 1) | 1U;
}
/**
* 将虚拟地址 va 翻译为物理地址,若发生缺页则返回 INVALID_PTE。
*/
uint32_t translate_va_to_pa(uint32_t va) {
// 1) 拆分:页号和页内偏移
uint32_t vpn = va >> PAGE_OFFSET_BITS; // 虚拟页号
uint32_t offset = va & (PAGE_SIZE - 1U); // 页内偏移
if (vpn >= PAGE_TABLE_ENTRIES) {
// 虚拟地址超出支持范围
return INVALID_PTE;
}
// 2) 取出页表项
uint32_t pte = page_table[vpn];
if ((pte & 1U) == 0) {
// valid 位为 0,视为缺页(Page Fault)
return INVALID_PTE;
}
// 3) 提取物理页号
uint32_t ppn = pte >> 1;
// 4) 合成物理地址
return (ppn << PAGE_OFFSET_BITS) | offset;
}
int main(void) {
// 示例:将虚拟页 1 映射到物理页 0x10
map_page(1, 0x10);
// 测试几组虚拟地址
uint32_t test_vas[] = {
0x00000000, // VPN=0, offset=0
0x00001000, // VPN=1, offset=0
0x00001ABC, // VPN=1, offset=0xABC
0x00400000 // VPN=1024, 越界
};
for (size_t i = 0; i < sizeof(test_vas)/sizeof(test_vas[0]); i++) {
uint32_t va = test_vas[i];
uint32_t pa = translate_va_to_pa(va);
if (pa == INVALID_PTE) {
printf("VA 0x%08X → Page Fault / Invalid mapping\n", va);
} else {
printf("VA 0x%08X → PA 0x%08X\n", va, pa);
}
}
return 0;
}二级页表

二级页表地址变换流程
-
虚拟地址拆分
| 一级页号 (P1) | 二级页号 (P2) | 页内偏移 (Offset) |- 通过虚拟地址位数和页大小自动划分(如 32 位系统 +4KB 页时:P1=10bit, P2=10bit, Offset=12bit)
-
一级页表查询
- 通过
页目录基址寄存器(CR3)找到页目录表 - 用
P1作为索引定位页目录项(PDE) - 检查 PDE 的有效位(若无效触发缺页异常)
- 通过
-
二级页表查询
- 从 PDE 中获取二级页表物理基址
- 用
P2作为索引定位页表项(PTE) - 检查 PTE 的有效位(若无效触发缺页异常)
-
物理地址合成
物理地址 = (PTE中的物理页框号 << Offset位数) | Offset
硬件辅助机制
- TLB 加速:缓存最近使用的页表项
- 缺页处理:当 PTE 无效时触发异常,由 OS 加载缺失页
- 权限检查:同时验证 PTE 中的 RWX 权限位
对比单级页表
| 特性 | 单级页表 | 二级页表 |
|---|---|---|
| 空间占用 | 连续大空间(4MB) | 离散小空间(按需分配) |
| 查询速度 | 一次访存 | 两次访存(可被 TLB 优化) |
| 适用场景 | 小地址空间系统 | 32/64 位现代系统 |
分段式存储管理

- 给定一个逻辑地址
A,其由段号S和段内偏移W组成。 - 段表中包含每个段的 基址(Base) 和 段长(Limit)。
- 转换过程:
- 取出段号
S对应的段表项。 - 检查
W是否小于该段的段长M(越界检查)。 - 检查段表项中的段长 C 是否大于偏移量 W。
- 如果不越界,则物理地址
E = 基址 + 偏移量。
- 取出段号
#include <stdio.h>
#define NUM_SEGMENTS 4
typedef struct {
int limit; // 段长
int base; // 段基址
} SegmentDescriptor;
// 模拟段表
SegmentDescriptor segment_table[NUM_SEGMENTS] = {
{1024, 6144}, // 段0: 1KB, 基址6KB
{600, 4096}, // 段1: 600B, 基址4KB
{500, 8192}, // 段2: 500B, 基址8KB
{200, 9200} // 段3: 200B, 基址9200
};
// 逻辑地址结构:段号 + 段内偏移
typedef struct {
int segment; // 段号S
int offset; // 段内偏移W
} LogicalAddress;
// 地址转换函数
int logical_to_physical(LogicalAddress la) {
if (la.segment < 0 || la.segment >= NUM_SEGMENTS) {
printf("错误:无效的段号 %d\n", la.segment);
return -1;
}
SegmentDescriptor sd = segment_table[la.segment];
if (la.offset >= sd.limit) {
printf("错误:段内偏移越界,offset = %d, limit = %d\n", la.offset, sd.limit);
return -1;
}
int physical_address = sd.base + la.offset;
printf("逻辑地址 (段号: %d, 偏移: %d) => 物理地址: %d\n", la.segment, la.offset, physical_address);
return physical_address;
}
int main() {
LogicalAddress la = {2, 100}; // 图中的示例:段号2,偏移100
logical_to_physical(la);
return 0;
}
优缺点及对比
一、表项存储位置与进程对应关系
| 表项类型 | 存储位置 | 进程对应关系 |
|---|---|---|
| 页表项 (PTE) | 存放在内存的页表中(多级页表时分散存储) | 每个进程有独立的页目录和页表,表项数量=虚拟地址空间大小/页大小 |
| 段表项 | 存放在内核空间的段表中 | 每个进程有独立的段表,表项数量=进程拥有的段数量(通常 4-16 个) |
二、分页与分段的核心对比
1. 分页机制(Paging)
✅ 优点
- 内存利用率高:消除外部碎片,按固定大小分配
- 透明性:对程序员不可见,由 OS 和硬件管理
- 支持虚拟内存:缺页中断实现按需调页
- 适合大地址空间:多级页表节省内存
❌ 缺点
- 存在内部碎片(平均半页浪费)
- 共享困难:需保持各进程页表同步
- 无逻辑保护:单页可能包含混合类型数据
2. 分段机制(Segmentation)
✅ 优点
- 逻辑性强:按程序自然结构(代码/数据/堆栈)划分
- 易于共享:以段为单位设置权限
- 动态扩展:段长度可动态增长
- 安全保护:段边界检查防止越界访问
❌ 缺点
- 产生外部碎片:需配合紧凑技术
- 内存管理复杂:需维护可变大小的内存块
- 不适合大地址空间:段表会变得庞大
三、现代系统的典型实现
-
x86 架构:采用段页式混合管理
- 先通过段机制进行逻辑地址转换(现代 OS 通常设为平坦模式)
- 再通过多级页表(如 4 级页表)转换线性地址到物理地址
-
进程切换代价:
- 页表切换:需更新 CR3 寄存器(TLB 会失效)
- 段表切换:修改段寄存器(如 GDTR/LDTR)