149 / 163 · C11 · 约 8 分钟
数据完整性与保护
先记住这句话
本章探讨在硬件不可靠时如何保证写入存储的数据日后能原样读回。内容涵盖局部磁盘故障(潜在扇区错误与静默损坏)、冗余恢复手段以及校验和检测技术,并强调空间与时间开销的权衡。
现代磁盘的局部故障
早期设计假定磁盘要么完全正常要么整盘报废。现实中驱动器常在看似工作的状态下丢失个别扇区或静默改写内容。潜在扇区错误由磁头划伤或宇宙射线引起,磁盘ECC能发现并报错;静默损坏则来自固件写错位置或总线传输出错,磁盘自身毫无察觉。廉价盘出现这些问题的概率明显高于企业级盘,但即使后者也不能忽略。
处理可检测的扇区错误
潜在扇区错误一旦出现,磁盘会立即返回失败。存储层只需启用已有冗余:镜像盘读另一份副本,奇偶校验组则用其余块重建。真正的风险出现在整盘失效后的重建过程中又碰上第二个错误。此时单奇偶RAID无法完成恢复,因此一些系统增加第二份奇偶信息,用空间换取重建成功率。
用校验和捕捉静默损坏
静默损坏不会产生任何错误码,因此必须在数据旁保存一份短摘要。写入时计算校验和,读回时重新计算并比对;不匹配即判定损坏并转向副本。简单异或或字节累加速度快但漏检某些模式,CRC或加密哈希更强却更耗CPU。没有免费午餐:保护越强,开销越大。
常见误区
- 以为磁盘ECC已足够而完全不做上层校验
- 重建RAID时未预留额外冗余,一旦再遇扇区错误就丢失数据
- 选用过弱的校验函数,无法发现特定翻转模式
运行一个例子
最低标准 C11 · 完整程序 · 下载 .c
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#define BLOCK_SIZE 16
static uint8_t compute_xor_checksum(const uint8_t *block, size_t n) {
uint8_t cs = 0;
for (size_t i = 0; i < n; ++i) {
cs ^= block[i];
}
return cs;
}
int main(void) {
uint8_t block[BLOCK_SIZE];
const char *msg = "Hello OSTEP!";
memset(block, 0, BLOCK_SIZE);
strncpy((char *)block, msg, BLOCK_SIZE - 1);
uint8_t stored_cs = compute_xor_checksum(block, BLOCK_SIZE);
printf("Stored data: %s\n", (char *)block);
printf("Stored checksum: 0x%02X\n", stored_cs);
block[3] ^= 0x01; /* silent corruption */
uint8_t read_cs = compute_xor_checksum(block, BLOCK_SIZE);
printf("Read checksum: 0x%02X\n", read_cs);
if (read_cs != stored_cs) {
printf("Corruption detected! Data integrity violated.\n");
} else {
printf("Checksums match.\n");
}
return 0;
}
在本地编译
gcc -std=c11 -Wall -Wextra -Wpedantic -Werror ostep-45-data-integrity.c -o example && ./example预期结果
Stored data: Hello OSTEP!
Stored checksum: 0x1E
Read checksum: 0x1F
Corruption detected! Data integrity violated.
CHECK YOUR UNDERSTANDING
合上答案,试着解释。
为何在已经使用RAID的情况下仍必须计算并验证校验和?
查看参考答案
RAID能应付整盘故障或磁盘主动报告的错误,却无法发现静默改写,因为被篡改的数据仍可能让奇偶校验通过。校验和在每个块上独立验证内容是否被改动,两者互补。
继续查证
标准草案与官方章节会更新;版本标记只说明示例最低要求。