直接读写磁盘设备文件需严格遵循权限、缓存、对齐与校验规范:Linux/macOS/Windows各有权限与缓存要求;GPT主备头必须同步更新并重算CRC32;分区字段须合法GUID、不重叠、字节序正确且结构体紧凑对齐。

直接读写磁盘设备文件会触发权限和缓存问题
Linux/macOS 下不能用 fopen("/dev/sda", "r+") 随意读写 GPT 分区表——系统内核可能已将该设备挂载,或启用 write-back 缓存,导致你改了内存里的缓冲区却没刷到物理扇区;Windows 下更严格,CreateFile 打开 \\.\PhysicalDrive0 必须以 GENERIC_READ | GENERIC_WRITE 权限 + FILE_SHARE_READ | FILE_SHARE_WRITE 共享模式,且进程需管理员权限(UAC 提权),否则返回 ERROR_ACCESS_DENIED。
- Linux:先
sudo blockdev --flushbufs /dev/sda清缓存,再用O_DIRECT | O_SYNC标志打开设备(绕过页缓存) - macOS:需禁用 SIP 后才能写入
/dev/disk0,否则write()直接返回EPERM - Windows:必须调用
SetFilePointer定位到 LBA 1(GPT 头位置),再用WriteFile写入 512 字节,且不能跨扇区对齐写
GPT 头在 LBA 1,但备份头在最后一个 LBA,别只改一个
GPT 规范要求主头(LBA 1)和备份头(最后一个可用 LBA)内容镜像一致。若只修改主头不更新备份头,某些固件或工具(如 gdisk)会检测到校验和不匹配,自动拒绝加载或强制恢复备份头——你的修改就白做了。
- 读取时:先读 LBA 1 的
struct gpt_header,验证signature(应为"EFI PART")、header_crc32和partition_entries_crc32 - 修改后:必须重新计算两个 CRC32(用
~0初始化、跳过自身字段),再把整个结构体 memcpy 到备份头位置 - 分区数组起始 LBA 存在
partition_entry_lba字段,典型值是 LBA 2;数组长度由num_partition_entries×sizeof(struct gpt_entry)决定(通常 128 × 128 = 16KB)
修改分区条目前必须检查 GUID 和类型 GUID 是否合法
GPT 分区条目(struct gpt_entry)里有两个关键 GUID:partition_type_guid(决定系统是否识别该分区)和 unique_partition_guid(唯一标识)。填错 partition_type_guid 可能导致 Windows 拒绝挂载、Linux blkid 无法识别文件系统类型;而 unique_partition_guid 若与其他分区重复,某些工具(如 macOS Disk Utility)会报 “duplicate UUID” 并拒绝操作。
- 常用类型 GUID 示例:
0fc63daf-8483-4772-8e79-3d69d8477de4(Linux filesystem)、c12a7328-f81f-11d2-ba4b-00a0c93ec93b(EFI System Partition) - 生成新
unique_partition_guid时,不能用零值或全 F 值;建议用uuid_generate_random()(Linux)或CoCreateGuid()(Windows) - 修改
starting_lba/ending_lba后,务必确保不与其它分区重叠,且落在设备总容量范围内(可通过ioctl(fd, BLKGETSIZE64, &size)获取)
用 C++ 操作需手动处理字节序和对齐,别依赖 struct 默认布局
x86_64 是小端,GPT 头中所有整数字段(如 my_lba、first_usable_lba)都按小端存储,但编译器可能因结构体对齐插入 padding,直接 memcpy 读写会导致字段错位或 CRC 校验失败。
立即学习“C++免费学习笔记(深入)”;
- 必须用
#pragma pack(1)禁用结构体填充,例如:#pragma pack(1) struct gpt_header { uint64_t signature; uint32_t revision; uint32_t header_size; uint32_t header_crc32; uint32_t reserved; uint64_t my_lba; uint64_t alternate_lba; uint64_t first_usable_lba; uint64_t last_usable_lba; uint8_t disk_guid[16]; uint64_t partition_entry_lba; uint32_t num_partition_entries; uint32_t sizeof_partition_entry; uint32_t partition_entries_crc32; }; #pragma pack() - 读取后,所有
uint64_t/uint32_t字段需用le64toh()/le32toh()转为主机序;写入前再用htole64()转回小端 - 不要用
std::vector<uint8_t></uint8_t>直接 reinterpret_cast 成结构体指针——未保证对齐,UB 风险高



















