《计算机操作系统》第八章 - 磁盘存储器的管理
本文详细讲解了《计算机操作系统》第八章磁盘存储器管理的核心内容,包括外存组织方式、文件存储空间管理、磁盘I/O优化、可靠性技术和数据一致性控制五大模块。通过C++98代码实现了连续分配与链接分配模拟、位示图管理、磁盘调度算法(FCFS/SSTF/SCAN)、RAID5奇偶校验和WAL日志恢复等关键技术。所有代码严格遵循C++98标准,可直接编译运行,帮助读者深入理解磁盘管理的核心原理和实践应用。文
前言
大家好!今天给大家详解《计算机操作系统》第八章 —— 磁盘存储器的管理,这一章是操作系统外存管理的核心内容,不管是考研、面试还是实际开发,都是高频考点。本文会用通俗易懂的语言拆解每个知识点,搭配完整可运行的 C++98 代码(无任何语法糖,纯 C++98 标准)、 架构图 / 流程图,还有每个知识点的实战案例,方便大家动手实操。

8.1 外存的组织方式
外存(磁盘为主)的组织方式决定了数据如何在磁盘上存储和读取,核心是把物理磁盘的扇区、磁道抽象成操作系统可管理的逻辑结构。
核心概念
磁盘的物理结构:磁道(同心圆)→ 扇区(磁道分割的最小存储单元)→ 柱面(不同盘面的同号磁道);常见外存组织方式:
- 连续分配:文件占用连续的磁盘块,优点是读取快,缺点是易产生碎片、扩展困难;
- 链接分配:文件块通过指针链接,分隐式链接(指针存在块末尾)和显式链接(FAT 表存指针),优点是无外部碎片,缺点是随机访问慢;
- 索引分配:为文件建索引块(存所有数据块地址),优点是随机访问快,缺点是索引块占用额外空间。
架构图

综合案例:模拟连续分配与链接分配
以下是完整的 C++98 代码,模拟两种分配方式的磁盘块管理,代码可直接编译运行(g++ -std=c++98 文件名.cpp):
#include <iostream>
#include <vector>
#include <string>
// 禁用C++11及以上特性,严格遵循C++98
#if __cplusplus >= 201103L
#error "请使用C++98标准编译!"
#endif
using namespace std;
// 磁盘块大小(模拟,单位:字节)
const int BLOCK_SIZE = 512;
// 总磁盘块数
const int TOTAL_BLOCKS = 100;
// 磁盘块结构体(C++98不支持类内初始化,需在构造函数初始化)
struct DiskBlock {
int block_id; // 块编号
bool is_used; // 是否被占用
int next_block; // 链接分配的下一块编号(-1表示无)
string data; // 块内数据
// C++98构造函数
DiskBlock() : block_id(0), is_used(false), next_block(-1), data("") {}
DiskBlock(int id) : block_id(id), is_used(false), next_block(-1), data("") {}
};
// 磁盘管理器类
class DiskManager {
private:
vector<DiskBlock> disk; // 模拟磁盘(C++98的vector支持)
public:
// 初始化磁盘
DiskManager() {
// 初始化100个磁盘块
for (int i = 0; i < TOTAL_BLOCKS; ++i) {
disk.push_back(DiskBlock(i));
}
}
// 8.1.1 连续分配:为文件分配连续的n个块
// 返回起始块号,-1表示分配失败
int continuous_allocate(int n) {
int count = 0;
int start = -1;
// 遍历磁盘找连续空闲块
for (int i = 0; i < TOTAL_BLOCKS; ++i) {
if (!disk[i].is_used) {
if (count == 0) {
start = i; // 记录连续块起始位置
}
count++;
// 找到足够的连续块
if (count == n) {
// 标记为已使用
for (int j = start; j < start + n; ++j) {
disk[j].is_used = true;
}
return start;
}
} else {
// 中断连续,重置计数
count = 0;
start = -1;
}
}
return -1; // 无足够连续块
}
// 8.1.2 链接分配:为文件分配一个空闲块,并链接到前一块
// prev_block:前一块编号(-1表示新文件的第一个块)
// 返回新分配的块号,-1表示分配失败
int linked_allocate(int prev_block) {
// 找第一个空闲块
for (int i = 0; i < TOTAL_BLOCKS; ++i) {
if (!disk[i].is_used) {
disk[i].is_used = true;
// 如果有前一块,更新前一块的next指针
if (prev_block != -1 && prev_block >= 0 && prev_block < TOTAL_BLOCKS) {
disk[prev_block].next_block = i;
}
return i;
}
}
return -1;
}
// 释放连续分配的块
void continuous_free(int start, int n) {
if (start < 0 || start + n > TOTAL_BLOCKS) {
cout << "释放失败:起始块或块数非法!" << endl;
return;
}
for (int i = start; i < start + n; ++i) {
disk[i].is_used = false;
disk[i].next_block = -1; // 重置链接
disk[i].data.clear();
}
}
// 释放链接分配的块(从起始块开始)
void linked_free(int start) {
if (start < 0 || start >= TOTAL_BLOCKS || !disk[start].is_used) {
cout << "释放失败:起始块非法或未使用!" << endl;
return;
}
int current = start;
// 遍历链表释放所有块
while (current != -1) {
int next = disk[current].next_block;
disk[current].is_used = false;
disk[current].next_block = -1;
disk[current].data.clear();
current = next;
}
}
// 打印磁盘块使用状态(前20块,方便查看)
void print_disk_status() {
cout << "\n磁盘块使用状态(前20块):" << endl;
for (int i = 0; i < 20; ++i) {
cout << "块" << i << ":" << (disk[i].is_used ? "已占用" : "空闲")
<< " | 下一块:" << disk[i].next_block << endl;
}
}
};
// 测试函数
void test_allocation() {
DiskManager dm;
cout << "===== 测试连续分配 =====" << endl;
// 分配5个连续块
int start = dm.continuous_allocate(5);
if (start != -1) {
cout << "成功分配连续块,起始块号:" << start << endl;
} else {
cout << "连续分配失败!" << endl;
}
dm.print_disk_status();
cout << "\n===== 测试链接分配 =====" << endl;
// 分配第一个块
int block1 = dm.linked_allocate(-1);
// 分配第二个块,链接到第一个块
int block2 = dm.linked_allocate(block1);
cout << "链接分配的块1:" << block1 << ",块2:" << block2 << endl;
dm.print_disk_status();
cout << "\n===== 测试释放 =====" << endl;
dm.continuous_free(start, 5);
dm.linked_free(block1);
dm.print_disk_status();
}
int main() {
// 测试外存组织方式的分配与释放
test_allocation();
return 0;
}
代码说明
- 严格遵循 C++98 标准:禁用 C++11 及以上特性(如类内初始化、auto 关键字等),使用 C++98 支持的 vector、构造函数初始化列表;
- 核心功能:模拟磁盘块的连续分配(找连续空闲块)、链接分配(通过 next 指针串联块),以及对应的释放逻辑;
- 测试函数:覆盖分配、释放、状态打印,运行后可直观看到磁盘块的使用变化。
8.2 文件存储空间的管理
文件存储空间管理的核心是高效跟踪磁盘空闲块,避免浪费、快速分配 / 释放,常见方法有:位示图法、空闲表法、空闲链表法、成组链接法。
核心概念
- 位示图法:用二进制位表示磁盘块状态(0 = 空闲,1 = 占用),优点是占用空间小、查找快,适合大容量磁盘;
- 空闲表法:用表格记录空闲块的起始地址和块数(类似内存的空闲分区表),适合连续分配;
- 空闲链表法:把所有空闲块用链表串联,分块链表(按块链接)和簇链表(按簇链接);
- 成组链接法:结合空闲表和链表的优点,UNIX 系统采用,把空闲块分组链接,减少链表遍历开销。
流程图

综合案例:位示图法实现磁盘空间管理(C++98 代码)
#include <iostream>
#include <vector>
#include <cstring>
// 严格C++98
#if __cplusplus >= 201103L
#error "请使用C++98标准编译!"
#endif
using namespace std;
// 位示图管理器(C++98实现)
class BitmapManager {
private:
vector<char> bitmap; // 位示图:每个char存8个块的状态(1字节=8位)
int total_blocks; // 总磁盘块数
// 计算位示图的索引和位偏移
void get_bit_pos(int block_id, int& index, int& offset) {
index = block_id / 8; // 对应char的索引
offset = block_id % 8; // 对应char内的位偏移
}
public:
// 初始化位示图:total_blocks个磁盘块,初始全空闲(0)
BitmapManager(int total) : total_blocks(total) {
// 计算需要的char数:向上取整
int bitmap_size = (total_blocks + 7) / 8;
bitmap.resize(bitmap_size, 0); // 初始化为0(所有位为0)
}
// 检查块是否空闲
bool is_free(int block_id) {
if (block_id < 0 || block_id >= total_blocks) return false;
int index, offset;
get_bit_pos(block_id, index, offset);
// 按位与判断:对应位为0则空闲
return (bitmap[index] & (1 << offset)) == 0;
}
// 分配单个块:返回块号,-1失败
int allocate_block() {
for (int i = 0; i < total_blocks; ++i) {
if (is_free(i)) {
int index, offset;
get_bit_pos(i, index, offset);
bitmap[index] |= (1 << offset); // 标记为占用(置1)
return i;
}
}
return -1;
}
// 分配n个连续块:返回起始块号,-1失败
int allocate_continuous(int n) {
int count = 0;
int start = -1;
for (int i = 0; i < total_blocks; ++i) {
if (is_free(i)) {
if (count == 0) start = i;
count++;
if (count == n) {
// 标记所有块为占用
for (int j = start; j < start + n; ++j) {
int index, offset;
get_bit_pos(j, index, offset);
bitmap[index] |= (1 << offset);
}
return start;
}
} else {
count = 0;
start = -1;
}
}
return -1;
}
// 释放块
void free_block(int block_id) {
if (block_id < 0 || block_id >= total_blocks) {
cout << "块号非法!" << endl;
return;
}
int index, offset;
get_bit_pos(block_id, index, offset);
bitmap[index] &= ~(1 << offset); // 置0
}
// 打印位示图(前10字节,方便查看)
void print_bitmap() {
cout << "\n位示图状态(前10字节,二进制):" << endl;
int print_size = min(10, (int)bitmap.size());
for (int i = 0; i < print_size; ++i) {
cout << "字节" << i << ":";
// 逐位打印(从高位到低位)
for (int j = 7; j >= 0; --j) {
cout << ((bitmap[i] >> j) & 1);
}
cout << endl;
}
}
};
// 测试位示图
void test_bitmap() {
BitmapManager bm(100); // 100个磁盘块,位示图占13字节(100/8=12.5)
cout << "===== 测试位示图分配 =====" << endl;
// 分配单个块
int block = bm.allocate_block();
cout << "分配单个块:" << block << endl;
// 分配5个连续块
int start = bm.allocate_continuous(5);
cout << "分配5个连续块,起始块:" << start << endl;
bm.print_bitmap();
cout << "\n===== 测试释放 =====" << endl;
bm.free_block(block);
bm.print_bitmap();
}
int main() {
test_bitmap();
return 0;
}
代码说明
- 位示图核心:用 char 数组存储位状态(1 个 char=8 位,对应 8 个磁盘块),通过位运算快速标记 / 查询块状态;
- 关键函数:
get_bit_pos计算块号对应的位位置,allocate_continuous实现连续块分配,free_block释放块; - 兼容性:所有语法均为 C++98 支持,无 C++11 特性。
8.3 提高磁盘 I/O 速度的途径
磁盘 I/O 是系统性能瓶颈,核心优化思路是减少寻道时间、旋转延迟、传输时间,常见途径如下:
核心方法
- 磁盘调度算法:减少寻道时间(核心),如 FCFS、SSTF、SCAN、C-SCAN、LOOK;
- 提前读(预读):操作系统预判用户可能读取的块,提前读入内存缓冲区;
- 延迟写:数据先写入缓冲区,待合适时机(如缓冲区满、系统空闲)再刷到磁盘;
- 磁盘高速缓存:内存中开辟一块区域缓存磁盘数据,减少物理 I/O;
- 优化物理布局:文件数据按磁盘物理顺序存储,减少旋转延迟。
思维导图

综合案例:磁盘调度算法实现(C++98 代码)
#include <iostream>
#include <vector>
#include <algorithm>
#include <cmath>
// 严格C++98
#if __cplusplus >= 201103L
#error "请使用C++98标准编译!"
#endif
using namespace std;
// 磁盘调度算法类
class DiskScheduling {
private:
int current_pos; // 当前磁头位置
public:
DiskScheduling(int pos) : current_pos(pos) {}
// 8.3.1 FCFS:先来先服务
int fcfs(vector<int>& requests) {
int total_seek = 0;
cout << "\nFCFS调度过程:" << endl;
cout << "初始磁头位置:" << current_pos << endl;
for (size_t i = 0; i < requests.size(); ++i) {
int seek = abs(requests[i] - current_pos);
total_seek += seek;
cout << "从" << current_pos << "到" << requests[i] << ",寻道距离:" << seek << endl;
current_pos = requests[i];
}
return total_seek;
}
// 8.3.2 SSTF:最短寻道时间优先
int sstf(vector<int> requests) {
int total_seek = 0;
vector<bool> processed(requests.size(), false); // 标记是否处理过
cout << "\nSSTF调度过程:" << endl;
cout << "初始磁头位置:" << current_pos << endl;
int processed_count = 0;
while (processed_count < (int)requests.size()) {
int min_seek = -1;
int target = -1;
int target_idx = -1;
// 找当前磁头最近的未处理请求
for (size_t i = 0; i < requests.size(); ++i) {
if (!processed[i]) {
int seek = abs(requests[i] - current_pos);
if (min_seek == -1 || seek < min_seek) {
min_seek = seek;
target = requests[i];
target_idx = i;
}
}
}
// 处理该请求
total_seek += min_seek;
cout << "从" << current_pos << "到" << target << ",寻道距离:" << min_seek << endl;
current_pos = target;
processed[target_idx] = true;
processed_count++;
}
return total_seek;
}
// 8.3.3 SCAN:电梯算法(向一个方向走到头,再反向)
int scan(vector<int> requests, int max_cylinder, bool direction_up) {
int total_seek = 0;
vector<int> left, right;
// 分离磁头左侧和右侧的请求
for (size_t i = 0; i < requests.size(); ++i) {
if (requests[i] < current_pos) {
left.push_back(requests[i]);
} else {
right.push_back(requests[i]);
}
}
// 排序
sort(left.begin(), left.end());
sort(right.begin(), right.end());
cout << "\nSCAN调度过程(" << (direction_up ? "向上" : "向下") << "):" << endl;
cout << "初始磁头位置:" << current_pos << endl;
// 向上走
if (direction_up) {
// 处理右侧请求
for (size_t i = 0; i < right.size(); ++i) {
int seek = abs(right[i] - current_pos);
total_seek += seek;
cout << "从" << current_pos << "到" << right[i] << ",寻道距离:" << seek << endl;
current_pos = right[i];
}
// 走到最外侧柱面
if (!left.empty()) {
int seek = abs(max_cylinder - current_pos);
total_seek += seek;
cout << "从" << current_pos << "到" << max_cylinder << ",寻道距离:" << seek << endl;
current_pos = max_cylinder;
// 反向处理左侧请求
for (int i = left.size() - 1; i >= 0; --i) {
int seek = abs(left[i] - current_pos);
total_seek += seek;
cout << "从" << current_pos << "到" << left[i] << ",寻道距离:" << seek << endl;
current_pos = left[i];
}
}
} else {
// 向下走:处理左侧请求
for (int i = left.size() - 1; i >= 0; --i) {
int seek = abs(left[i] - current_pos);
total_seek += seek;
cout << "从" << current_pos << "到" << left[i] << ",寻道距离:" << seek << endl;
current_pos = left[i];
}
// 走到最内侧柱面
if (!right.empty()) {
int seek = abs(current_pos - 0);
total_seek += seek;
cout << "从" << current_pos << "到0,寻道距离:" << seek << endl;
current_pos = 0;
// 反向处理右侧请求
for (size_t i = 0; i < right.size(); ++i) {
int seek = abs(right[i] - current_pos);
total_seek += seek;
cout << "从" << current_pos << "到" << right[i] << ",寻道距离:" << seek << endl;
current_pos = right[i];
}
}
}
return total_seek;
}
};
// 测试磁盘调度算法
void test_scheduling() {
// 磁道请求序列
vector<int> requests;
requests.push_back(98);
requests.push_back(183);
requests.push_back(37);
requests.push_back(122);
requests.push_back(14);
requests.push_back(124);
requests.push_back(65);
requests.push_back(67);
// 初始磁头位置:53,最大柱面数:199
DiskScheduling ds(53);
// FCFS
int fcfs_seek = ds.fcfs(requests);
cout << "FCFS总寻道距离:" << fcfs_seek << endl;
// 重置磁头位置
DiskScheduling ds2(53);
// SSTF
int sstf_seek = ds2.sstf(requests);
cout << "SSTF总寻道距离:" << sstf_seek << endl;
// 重置磁头位置
DiskScheduling ds3(53);
// SCAN(向上)
int scan_seek = ds3.scan(requests, 199, true);
cout << "SCAN总寻道距离:" << scan_seek << endl;
}
int main() {
test_scheduling();
return 0;
}
代码说明
- 核心算法:实现 FCFS(简单遍历)、SSTF(找最近请求)、SCAN(电梯算法),计算总寻道距离;
- 关键逻辑:SSTF 通过遍历找最短寻道请求,SCAN 按方向处理请求后反向;
- 测试用例:采用经典的磁道请求序列(53→98→183→37→122→14→124→65→67),可直观对比不同算法的寻道效率。
8.4 提高磁盘可靠性的技术
磁盘是易损硬件,核心目标是防止数据丢失、提升容错能力,常见技术如下:
核心技术
- 磁盘镜像(RAID 1):将数据同时写入两个磁盘(主盘 + 镜像盘),一个损坏另一个可用,缺点是磁盘利用率 50%;
- 磁盘条带化(RAID 0):将数据分块存储到多个磁盘,并行 I/O 提升速度,但无容错(一块损坏则数据全丢);
- RAID 5:结合条带化和奇偶校验,至少 3 块磁盘,奇偶校验信息分布在不同磁盘,容错且利用率高(n-1/n);
- 热备份(热插拔):磁盘故障时,备用磁盘自动替换故障盘,无需停机;
- 数据备份与恢复:定期备份数据到离线介质(如磁带、云存储)。
综合案例:模拟 RAID 5 奇偶校验计算(C++98 代码)
#include <iostream>
#include <vector>
#include <string>
// 严格C++98
#if __cplusplus >= 201103L
#error "请使用C++98标准编译!"
#endif
using namespace std;
// RAID 5奇偶校验管理器
class RAID5Manager {
private:
int disk_count; // 磁盘数量(至少3)
// 修复:C++98中嵌套模板的>>必须拆分为> >
vector<vector<unsigned char> > disk_data;
// 异或校验计算(RAID 5核心:奇偶校验位=所有数据位异或)
unsigned char xor_check(const vector<unsigned char>& data) {
unsigned char check = 0;
for (size_t i = 0; i < data.size(); ++i) {
check ^= data[i];
}
return check;
}
public:
// 初始化:disk_count块磁盘,每个磁盘有block_count个块
RAID5Manager(int disks, int block_count) : disk_count(disks) {
if (disks < 3) {
cout << "RAID 5至少需要3块磁盘!自动设置为3块。" << endl;
disk_count = 3;
}
// 初始化磁盘数据
disk_data.resize(disk_count);
for (int i = 0; i < disk_count; ++i) {
disk_data[i].resize(block_count, 0);
}
}
// 写入数据块,并计算奇偶校验
// data:要写入的数据块(数量=disk_count-1)
// block_idx:写入的块索引
void write_block(const vector<unsigned char>& data, int block_idx) {
if (block_idx < 0 || block_idx >= (int)disk_data[0].size()) {
cout << "块索引非法!" << endl;
return;
}
if ((int)data.size() != disk_count - 1) {
cout << "数据块数量必须为" << disk_count - 1 << "!" << endl;
return;
}
// 确定校验块的位置(轮询分布:block_idx % disk_count)
int check_disk = block_idx % disk_count;
cout << "\n写入块" << block_idx << ",校验块位置:磁盘" << check_disk << endl;
// 写入数据块
int data_idx = 0;
for (int i = 0; i < disk_count; ++i) {
if (i == check_disk) {
continue; // 校验盘先不写
}
disk_data[i][block_idx] = data[data_idx++];
}
// 收集数据块计算校验值
vector<unsigned char> temp_data;
for (int i = 0; i < disk_count; ++i) {
if (i != check_disk) {
temp_data.push_back(disk_data[i][block_idx]);
}
}
unsigned char check_val = xor_check(temp_data);
// 写入校验值
disk_data[check_disk][block_idx] = check_val;
cout << "数据写入完成,校验值:" << (int)check_val << endl;
}
// 模拟磁盘故障,恢复数据
// failed_disk:故障磁盘编号
// block_idx:要恢复的块索引
void recover_disk(int failed_disk, int block_idx) {
if (failed_disk < 0 || failed_disk >= disk_count) {
cout << "故障磁盘编号非法!" << endl;
return;
}
if (block_idx < 0 || block_idx >= (int)disk_data[0].size()) {
cout << "块索引非法!" << endl;
return;
}
cout << "\n恢复故障磁盘" << failed_disk << "的块" << block_idx << endl;
// 收集其他磁盘的块数据(包括校验块)
vector<unsigned char> temp_data;
for (int i = 0; i < disk_count; ++i) {
if (i != failed_disk) {
temp_data.push_back(disk_data[i][block_idx]);
}
}
// 异或计算恢复故障块数据
unsigned char recovered_data = xor_check(temp_data);
disk_data[failed_disk][block_idx] = recovered_data;
cout << "恢复完成,故障块数据:" << (int)recovered_data << endl;
}
// 打印磁盘数据
void print_disk_data() {
cout << "\nRAID 5磁盘数据:" << endl;
for (int i = 0; i < disk_count; ++i) {
cout << "磁盘" << i << ":";
for (size_t j = 0; j < disk_data[i].size(); ++j) {
cout << (int)disk_data[i][j] << " ";
}
cout << endl;
}
}
};
// 测试RAID 5
void test_raid5() {
// 初始化:4块磁盘,每个磁盘5个块
RAID5Manager raid5(4, 5);
// 写入第一组数据(3个数据块)
vector<unsigned char> data1;
data1.push_back(0x12); // 18
data1.push_back(0x34); // 52
data1.push_back(0x56); // 86
raid5.write_block(data1, 0);
// 写入第二组数据
vector<unsigned char> data2;
data2.push_back(0x78); // 120
data2.push_back(0x9A); // 154
data2.push_back(0xBC); // 188
raid5.write_block(data2, 1);
raid5.print_disk_data();
// 模拟磁盘1故障,恢复块0
raid5.recover_disk(1, 0);
raid5.print_disk_data();
}
int main() {
test_raid5();
return 0;
}

代码说明
- RAID 5 核心:通过异或校验实现容错,校验块轮询分布在不同磁盘,避免单块校验盘瓶颈;
- 关键函数:
xor_check计算异或校验值,write_block写入数据并生成校验块,recover_disk模拟故障恢复; - 恢复逻辑:利用异或的可逆性(A^B^C=P → A=B^C^P),通过剩余磁盘数据恢复故障盘数据。
8.5 数据一致性控制
多进程 / 线程读写磁盘数据时,易出现数据不一致(如写一半断电),核心目标是保证数据的原子性、一致性、持久性。
核心技术
- 事务(Transaction):将一组操作封装为原子单元,要么全执行,要么全不执行;
- 日志(Journaling):先写操作日志到磁盘,再执行实际操作,故障时通过日志恢复;
- 检查点(Checkpoint):定期将内存中的修改刷到磁盘,减少故障恢复时间;
- 写前日志(WAL, Write-Ahead Logging):修改数据前先写日志,确保日志落盘后再改数据。
流程图

综合案例:模拟 WAL 写前日志(C++98 代码)
#include <iostream>
#include <vector>
#include <string>
#include <fstream>
// 严格C++98
#if __cplusplus >= 201103L
#error "请使用C++98标准编译!"
#endif
using namespace std;
// 日志操作类型
enum LogType {
INSERT, // 插入
UPDATE, // 更新
DELETE // 删除
};
// 日志条目
struct LogEntry {
LogType type; // 操作类型
int record_id; // 记录ID
string old_data; // 旧数据(更新/删除时用)
string new_data; // 新数据(插入/更新时用)
bool is_committed; // 是否已提交
// C++98构造函数
LogEntry() : type(INSERT), record_id(0), is_committed(false) {}
LogEntry(LogType t, int id, const string& old_d, const string& new_d)
: type(t), record_id(id), old_data(old_d), new_data(new_d), is_committed(false) {}
};
// WAL管理器
class WALManager {
private:
vector<LogEntry> log; // 内存日志
vector<string> data_store; // 模拟数据存储
string log_file_path; // 日志文件路径
// 将日志写入文件(模拟落盘)
void write_log_to_disk() {
ofstream log_file(log_file_path.c_str(), ios::app); // C++98需转const char*
if (!log_file.is_open()) {
cout << "日志文件打开失败!" << endl;
return;
}
// 写入未提交的日志
for (size_t i = 0; i < log.size(); ++i) {
if (!log[i].is_committed) {
log_file << log[i].type << " " << log[i].record_id << " "
<< log[i].old_data << " " << log[i].new_data << endl;
log[i].is_committed = true; // 标记为已提交
}
}
log_file.close();
}
public:
// 初始化
WALManager(const string& log_path) : log_file_path(log_path) {
// 初始化数据存储(10个空记录)
data_store.resize(10, "");
}
// 插入数据(WAL流程)
bool insert(int record_id, const string& data) {
if (record_id < 0 || record_id >= (int)data_store.size()) {
cout << "记录ID非法!" << endl;
return false;
}
// 1. 创建日志条目
LogEntry entry(INSERT, record_id, "", data);
log.push_back(entry);
// 2. 写日志到磁盘
write_log_to_disk();
// 3. 修改内存数据
data_store[record_id] = data;
cout << "插入记录" << record_id << "成功,数据:" << data << endl;
return true;
}
// 更新数据(WAL流程)
bool update(int record_id, const string& new_data) {
if (record_id < 0 || record_id >= (int)data_store.size()) {
cout << "记录ID非法!" << endl;
return false;
}
// 1. 保存旧数据
string old_data = data_store[record_id];
// 2. 创建日志条目
LogEntry entry(UPDATE, record_id, old_data, new_data);
log.push_back(entry);
// 3. 写日志到磁盘
write_log_to_disk();
// 4. 修改内存数据
data_store[record_id] = new_data;
cout << "更新记录" << record_id << "成功,新数据:" << new_data << endl;
return true;
}
// 故障恢复:从日志恢复数据
void recover() {
cout << "\n===== 开始故障恢复 =====" << endl;
ifstream log_file(log_file_path.c_str()); // C++98需转const char*
if (!log_file.is_open()) {
cout << "日志文件不存在,无需恢复!" << endl;
return;
}
// 读取日志并恢复
int type, record_id;
string old_data, new_data;
while (log_file >> type >> record_id >> old_data >> new_data) {
LogType log_type = (LogType)type;
switch (log_type) {
case INSERT:
data_store[record_id] = new_data;
cout << "恢复插入:记录" << record_id << ",数据:" << new_data << endl;
break;
case UPDATE:
data_store[record_id] = new_data;
cout << "恢复更新:记录" << record_id << ",新数据:" << new_data << endl;
break;
case DELETE:
data_store[record_id] = "";
cout << "恢复删除:记录" << record_id << endl;
break;
default:
cout << "无效日志类型!" << endl;
break;
}
}
log_file.close();
cout << "恢复完成!" << endl;
}
// 打印数据存储
void print_data() {
cout << "\n当前数据存储:" << endl;
for (size_t i = 0; i < data_store.size(); ++i) {
cout << "记录" << i << ":" << data_store[i] << endl;
}
}
};
// 测试WAL
void test_wal() {
// 日志文件路径(当前目录)
WALManager wal("wal_log.txt");
// 插入数据
wal.insert(1, "user1:张三");
wal.insert(2, "user2:李四");
// 更新数据
wal.update(1, "user1:张三丰");
wal.print_data();
// 模拟故障,恢复数据
wal.recover();
wal.print_data();
}
int main() {
test_wal();
return 0;
}
代码说明
- WAL 核心流程:先写日志→日志落盘→修改内存数据→异步刷盘,保证故障时可通过日志恢复;
- 关键函数:
write_log_to_disk模拟日志落盘,recover从日志文件恢复数据; - 兼容性:C++98 的文件操作需用
const char*(c_str()),无 C++11 的 string 直接传参。
习题
- 编程题:基于 8.2 的位示图代码,实现空闲链表法的磁盘空间管理;
- 简答题:对比 SCAN 和 C-SCAN 算法的优缺点,为什么 C-SCAN 更适合 SSD?
- 实操题:修改 8.4 的 RAID 5 代码,模拟两块磁盘故障的场景(说明为什么 RAID 5 不支持两块盘故障);
- 分析题:为什么写前日志(WAL)能保证数据一致性?结合代码说明核心流程。
总结
- 外存组织方式核心是连续、链接、索引分配,各有优劣,代码可模拟块的分配与释放;
- 磁盘 I/O 优化的核心是减少寻道时间(调度算法)、利用缓存 / 预读,可靠性依赖 RAID 等容错技术;
- 数据一致性控制的关键是原子性(事务)、持久性(日志),WAL 是最常用的实现方式;
- 所有代码均严格遵循 C++98 标准,可直接编译运行,覆盖每个知识点的核心应用场景。
运行说明
- 编译命令:
g++ -std=c++98 文件名.cpp -o 可执行文件名; - 运行:
./可执行文件名(Linux/Mac)或可执行文件名.exe(Windows); - 注意:WAL 案例会生成日志文件,运行前确保当前目录有写权限。
希望本文能帮助大家吃透磁盘存储器管理的核心知识点,代码可直接动手实操,有问题欢迎评论区交流!
更多推荐









所有评论(0)