前言:前几天因为一直插在软路由上的u盘坏了,进系统直报IO ERROR,又重新研究了如何把我的系统装到软路由内置的nvme固态硬盘中。早在一年以前我就做过了一些尝试,但一直没有定位到镜像烧录后无法启动的问题所在,结果把系统安装到了u盘中。最近重新翻出来看这个问题,发现了其中的大坑,硬盘的物理扇区大小512Byte与4096Byte。
TLDR:直接在这下载4k硬盘用的烧录镜像:TODO
先来补习一点背景知识:
什么是扇区?
扇区、柱面、磁道是磁盘时代的产物
- 将磁盘切出一块扇形区域,叫磁道
- 将磁盘切分一个个更小的弧形,叫扇区
- 如果有多张磁盘垂直排列,垂直位置相同的一整圈扇区叫一个柱面
但在ssd时代,这几个参数还有什么实际意义?
- 底层访问磁盘的最小单位依然是扇区,但操作系统内都不在使用磁头磁道了,而是直接使用逻辑块了
- 使用磁头磁道扇区读写的模式叫做CHS(Cylinder Head Sector),使用逻辑块的读写的模式LBA(Logic Block Address)
- 物理扇区是磁盘的最小读写操作单位,物理扇区可以模拟比自己大的逻辑扇区
这里引入了物理扇区和逻辑扇区两个概念,磁盘内部实际操作的最小单元被称为物理扇区,磁盘对外提供操作接口最小单元为逻辑扇区。4K对齐就是使用较大的逻辑扇区来减少操作系统在读写文件时重复和磁盘IO的次数。
使用512物理扇区硬盘,逻辑扇区支持512以上所有模式 | 使用4096物理扇区的硬盘,逻辑扇区支持4096以上模式 |
---|---|
什么是分区、分区表?
存储在磁盘数据最开头的就是分区表,一般分为MBR、GUID(GPT)两种分区格式,对应着磁盘上不同的分区表的数据格式。
MBR分区表
MBR是传统分区表格式,意译为Master Boot Record,MBR存储在硬盘的第一个扇区里,存储结构如下
从结构上看,MBR的结构最多支持四个分区,windows上会从第四个分区开始自动使用逻辑分区
其中每一个Parition table entry的结构又如下,包含分区是否bootable、分区的起始CHS、分区的起始LBA等信息:
这个16字节结构体代码定义如下:
typedef struct {
UINT8 BootIndicator;
UINT8 StartHead;
UINT8 StartSector;
UINT8 StartTrack;
UINT8 OSIndicator;
UINT8 EndHead;
UINT8 EndSector;
UINT8 EndTrack;
UINT8 StartingLBA[4];
UINT8 SizeInLBA[4];
} MBR_PARTITION_RECORD;
GPT分区表
GPT分区表是EFI新协议的一部分,它的第一个扇区仍然是MBR分区表,从第二个扇区开始,是GPT的专用Header,之后第三个扇区开始是分区数据。
GPT分区整体结构 | GPT分区头定义 | GPT分区表定义 |
---|---|---|
MBR与GPT的区别
下图清楚展示了MBR分区表结构和GPT分区表结构的区别
MBR | GPT |
---|---|
从这个区别可看出来,GPT是在MBR基础上扩充了定义,如果无法无法正常读取到第二个GPT Header扇区,是不会将硬盘识别成GPT格式的,只会识别成MBR格式。
磁盘扇区大小对分区表的影响
先来看一下这次出问题的元凶:东芝BG3硬盘
淘宝不到50元就有128G,非常适合做小主机的系统盘。其实用它来安装windows、安装pve也都是不会有任何问题的,主流操作系统早就支持4k分区,但偏偏以烧录硬盘镜像的方式来安装openwrt,会导致问题。
让我们再看这次烧录用的openwrt镜像:
这次烧录的镜像,是从supes.top下载的一个普通x86架构的openwrt软路由镜像。镜像里有两个分区,其中kernel分区的起始扇区是2048,CHS对应0 32 33,在512byte磁盘上,2048扇区的偏移地址是0x100000。镜像使用MBR分区表,所以以下的部分将会以MBR分区表的形式介绍。
512扇区和4096扇区的硬盘在分区表上的区别 ?
先看正常情况下硬盘上的MBR分区表:
512扇区 | 4096扇区 |
---|---|
512扇区的磁盘,MBR分区表正好占用一个扇区 | 4096扇区的盘,MBR分区表只会占用扇区的前512个字节,后面为空 |
512扇区镜像烧录到4096扇区大小硬盘,实际上发生了什么?
烧录我是用的rufus,这里可以使用别的软件如dd、physicaldiskwrite等等,效果是一样的。(dd的obs参数只是决定一次写入的数据大小,和扇区大小完全无关)
【重点】直接来看写入之后的磁盘0号扇区:
IMG镜像文件 | 512byte磁盘 | 4096byte磁盘 |
---|---|---|
0号扇区里是分区表,长度正好是512byte | 0号扇区里也是分区表,长度正好是512byte | 0号扇区里不仅有分区表,还有了原来两个中属于1号扇区的数据 |
所以烧录磁盘镜像时,烧录程序其实是按偏移量去把镜像文件写入磁盘的。
但是如果将这个img文件烧录到4096byte磁盘中,0号扇区内从0x0位置开始是长512byte的MBR分区记录,但从0x200(512byte)开始,是原来属于扇区1的数据,此处依旧在0号扇区中。
同时,第二个问题是分区表记录的分区的CHS和起始扇区,都是以扇区为单位记录的,在4096byte磁盘中,扇区大小比原来大了8倍,于是真正起始位置在磁盘上的物理偏移量,也成了原来的8倍。
原来的磁盘,分区0起始偏移量是0x100000,数据全0 | 4096byte的磁盘,根据分区表跳转到的分区0起始位置是0x800000,数据乱七八糟 |
---|---|
看0x100000位置的数据,两个盘其实是一样的
原来的磁盘 | 4096byte的磁盘 |
---|---|
如果看到这里还没有明白,可以看这张图:
BIOS根据MBR里记录的分区表找到第一个分区的起始扇区,访问这个扇区时数据却完全不是原来引导内核的数据了。
烧录到4096byte扇区的硬盘上后,为何无法启动?
无法启动的原因就是从分区表读到的分区起始扇区,对应的实际物理位置变了,导致没法正确读取到kernel分区,无法引导。
那么是不是改一下扇区起始地址就ok了?
请看实验:通过修改MBR分区的起始扇区能否使4k硬盘兼容512镜像
如何将openwrt安装到4096byte扇区的硬盘上
刚刚的魔改扇区法看起来还是太hack了,接下来推荐正常的安装方法:
1. 先把磁盘镜像第一个分区挂载到系统中
1048576是起始扇区2048乘以扇区大小512得到的
mkdir op_img
mount -o loop,offset=1048576 openwrt-03.30.2024-x86-64-generic-squashfs-combined.img_ ./op_img
- 在4096byte硬盘上新建两个扇区,一个大小16MB,一个大小可以自己决定(可以把剩下所有空间用完)
- 将磁盘镜像中对应分区里的文件拷贝到硬盘新建的分区中
- 取消挂载之前一个分区,因为loop分区一次只能挂载一个,将磁盘镜像中第二个分区挂载
- 拷贝第二个分区里的数据
- 修改第一个分区里grub文件信息,更新其中的part uuid
- 正常启动
TODO 提供一个4096byte的openwrt iso
512byte物理扇区下,512byte逻辑扇区与4096byte逻辑扇区的区别
todo 在512byte磁盘上分别以512byte扇区大小和4096扇区大小格式化,之后存入一个文件,查看文件在扇区中的表现。