起因

在写裸机系统的时候,需要用到的从U盘启动到系统的情况,这时候
写一个U盘写入小工具很有必要,下面是将引导程序写入U盘的简单代码

假如已经有一个boot.bin的引导程序,需要写入U盘。

技术实现原理

MBR:第一个扇区里装了什么

BIOS 时代的磁盘以“启动盘的第一个扇区”(512 字节)为引导单位,这个扇区叫 MBR,布局是固定的:

  • 0~445 字节:引导代码,boot.bin 的内容就放这里;
  • 446~509 字节:分区表(4 个分区项,每项 16 字节);
  • 510~511 字节:签名 0x55AA

BIOS 为什么肯从这块U盘启动

上电自检后,BIOS 把启动设备的第一个扇区原样读进内存 0x7C00 处,然后检查扇区末尾两个字节能不能拼出 0xAA55:对得上才认为这是合法引导扇区,跳过去执行;对不上就报找不到启动设备。这就是代码里 buffer[510]=0x55; buffer[511]=0xaa; 的来历——不管 boot.bin 有没有带签名,工具强制补上,保证 BIOS 认账。

为什么直接写 /dev/sda 就算“装好了”

Linux 里块设备也是文件:对 /dev/sda 做 write,数据落到的是磁盘的第 0 号扇区,完全绕过文件系统。所以这个工具不需要挂载、不需要格式化,写完第 0 扇区,引导代码就算装上了。代价也很直白:原来的分区表就在第 0 扇区里,会被一并覆盖,U盘上的旧数据自然全部“丢失”。

write-mbr.c

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <fcntl.h>
#include <stdio.h>
int main(int argc,char*argv[]){
    int dev_flag,file_flag;
    unsigned char buffer[512];
    file_flag = open("./boot.bin",O_RDONLY);
    if(file_flag == -1){
        perror("failed to read file boot");
    return -1;
}
    read(file_flag,buffer,510);
    close(file_flag);
    buffer[510] =0x55;
    buffer[511] =0xaa;
    dev_flag = open("/dev/sda",O_RDWR);
    if(dev_flag == -1){
        perror("failed to open device /dev/sda");
        return -1;
    }
    write(dev_flag,buffer,512);
    close(dev_flag);
    puts("successful.");
return 0;
}

执行命令

1
gcc -o write-mbr write-mbr.c

操作步骤

在操作之前,备份好U盘里面的数据,写完后,U盘里面的数据就丢失了。
将U盘接入电脑,挂载到linux上
首先用命令 sudo fdisk -l 可以通过容量找到对应U盘的设备名称。
然后用命令 sudo mount /dev/sda /mnt/flash将U盘挂载到flash,注意该命令需要flash目录存在,不存在则要创建:

1
cd /mnt && sudo mkdir flash

最后执行 ./write-mbr ,shell返回successful,说明写入成功。

然后,卸载设备

将U盘拔出,插入到另外一台测试电脑,并通过BIOS将启动项改为U盘优先。

代码说明

  • read(file_flag,buffer,510):只读 boot.bin 的前 510 字节,签名那两字节由程序自己补;
  • open("/dev/sda",O_RDWR):以裸设备方式打开,write 直接落到扇区 0(原理见上文);
  • write(dev_flag,buffer,512):恰好一个扇区,不会多抹后面的数据。

踩坑记录与注意事项

  • /dev/sda 不一定是U盘:设备名按检测顺序分配,sda 完全可能是系统盘。写错目标 = 系统盘第 0 扇区被覆盖、分区表没了,机器直接起不来——写之前必须 fdisk -l 按容量核对设备名,拿不准就拔插U盘前后对比两次列表的变化;
  • boot.bin 超过 510 字节会被无声截断:代码只读 510 字节,引导程序装不下时被截掉尾巴,引导到一半跑飞。用 nasm 写引导时以 times 510-($-$$) db 0 把镜像补齐到 510 字节,签名交给工具补;
  • 先备份原第一扇区sudo dd if=/dev/sdb of=mbr.bak bs=512 count=1,写坏了还能把分区表恢复回来;
  • 写完先卸载再拔盘:umount 后再拔,保证设备状态干净;
  • BIOS 启动模式要选对:这写的是传统 BIOS/MBR 引导,测试机如果只开 UEFI,不会认这个扇区,要在 BIOS 里打开 Legacy/CSM 启动。