今天讲操作系统的内存管理。我们经常使用操作系统进行工作,但是否想过为什么要引入内存管理?内存管理解决了什么问题,以及内存管理是怎么实现的。分页管理与分段管理各自的优势与缺点,以及它们结合优势后形成的段页式存储。
1.为什么要引入内存管理
众所周知,内存是存放临时数据的硬件。程序执行前要先放到内存中才能被CPU处理。因为要缓和CPU与硬盘之间的速度矛盾,而程序一般存储在硬盘中。
因此操作系统必须对内存空间进行合理的划分和有效的动态分配,这就是内存管理的概念。
2.内存管理的5个主要功能
内存空间的分配与回收地址转换:逻辑地址与物理地址的转换内存空间的扩充:覆盖、交换、虚拟技术内存共享:可重入代码存储保护
3.基本分页存储管理
物理地址:将物理内存划分为固定大小的 “页框”(如 4KB / 页),每个页框是内存分配的最小单位。
逻辑地址:把程序的逻辑地址空间同样划分为等长的 “页”,页号从 0 开始编号。
动态映射:程序的页可以分散存储在不同的页框中,通过 “页表” 建立页号与页框号的映射关系。
举个例子:
假设物理内存是一本字典,页框就是字典的每一页;程序则是另一本 “虚拟字典”,页是它的每一页。页表就像一本 “翻译手册”,记录虚拟页对应的实际页码。
页表结构:
页号物理块号
地址转换步骤:
计算页号和页内偏移
逻辑地址 = 页号 × 页大小 + 页内偏移
例如:页大小 4KB(12 位),逻辑地址 0x1234 → 页号 0x1,偏移 0x234。
查页表通过页号找到对应的物理块号。
生成物理地址物理块号 × 页大小 + 偏移。
4.快表(TLB):加速地址转换
痛点:每次查页表需访问内存,导致两次访存(逻辑地址→页表→物理地址),效率降低 50%。
解决方案:
快表CPU 内置的高速缓存,存储近期访问的页表项。局部性原理程序访问具有时间和空间局部性,近期访问的页大概率再次被访问。
地址转换优化:
先查快表命中则直接获取物理地址(1 次访存)。
未命中查页表更新快表,后续访问加速。
5.多级页表:解决页表过大的 “内存杀手”
问题:32 位系统页表可能占用 4MB 内存(1024 个页表项 × 4 字节),浪费严重。
两级页表方案:
一级页表记录二级页表的起始地址。二级页表记录页号与物理块号的映射。
优势:仅加载当前需要的二级页表,节省 99% 内存。
示例:
32 位地址分为:一级页号(10 位)+ 二级页号(10 位)+ 偏移(12 位)。一级页表项仅需 1024 个,每个指向一个二级页表(1024 项)。
至于之后的分段、段页式、虚拟内存管理,下期再介绍。末尾附上使用c语言实现的简单分页存储
#include <stdio.h>#include <stdlib.h>
#define PAGE_SIZE 4096#define NUM_PAGES 10
// 页表项结构体typedef struct { int frame_number; int valid;} PageTableEntry;
// 页表PageTableEntry page_table[NUM_PAGES];
// 初始化页表void init_page_table() { for (int i = 0; i < NUM_PAGES; i++) { page_table[i].frame_number = -1; page_table[i].valid = 0; }}
// 分配页框void allocate_page(int page_number, int frame_number) { if (page_number >= 0 && page_number < NUM_PAGES) { page_table[page_number].frame_number = frame_number; page_table[page_number].valid = 1; }}
// 地址转换int translate_address(int logical_address) { int page_number = logical_address / PAGE_SIZE; int offset = logical_address % PAGE_SIZE;
if (page_number >= 0 && page_number < NUM_PAGES && page_table[page_number].valid) { int frame_number = page_table[page_number].frame_number; return frame_number * PAGE_SIZE + offset; } else { printf("页面错误: 逻辑地址 %d 对应的页面无效\n", logical_address); return -1; }}
int main() { // 初始化页表 init_page_table();
// 分配页框 allocate_page(0, 2); allocate_page(1, 5); allocate_page(2, 7);
// 逻辑地址示例 int logical_address = 4500; int physical_address = translate_address(logical_address);
if (physical_address != -1) { printf("逻辑地址 %d 转换后的物理地址是 %d\n", logical_address, physical_address); }
return 0;}