光盘启动与软盘启动的方式有很大不同,主要在于光盘容量要远远大于软盘的容量。一张74分钟的标准CD包含333000个扇区。每个扇区为2352字节,模式1型的CD-ROM包含2048字节计算机数据,模式2型的CD-ROM包含2336字节PSX/VCD数据,音频包含2352字节信息。扇区容量的差异来自头信息和纠错编码,计算机数据对精度要求最高,这类信息最多,VCD对精度要求不高,这类信息稍小,音频CD没有这类信息。如果用RAW格式从盘片中读取数据,无论是什么数据类型,每个扇区都能够读出2352字节信息。在一张74分钟的CD上,能够用RAW方式刻录更多的信息,这样做容量可以达到333000 * 2352 = 783216000 字节(大约747MB)。这是74分钟650MB的CD的容量的上限。容量增加了14.8%,但是,这样做将失去纠错数据。
首先,我们来来看一下一个可引导光盘的文件结构。这种光盘遵从EL TORITO和ISO 9660规范。每个扇区大小为2048个字节,1至16个扇区为保留扇区,从第17个扇区开始正式的数据存储。我们使用二进制查看工具打开一个可引导的iso文件,来查看它的二进制内容,如下:
vim -b boot.iso :%!xxd使用vim命令并采用二进制方式打开boot.iso这个文件,打开之后在vim中执行“:%!xxd”来进入二进制显示模式,iso光盘镜像文件的第17个扇区的位置为2048 * 17 = 34816(0x8800),我们这个iso文件的内容如下:
0008800: 0043 4430 3031 0145 4c20 544f 5249 544f .CD001.EL TORITO 0008810: 2053 5045 4349 4649 4341 5449 4f4e 0000 SPECIFICATION.. 0008820: 0000 0000 0000 0000 0000 0000 0000 0000 ................ 0008830: 0000 0000 0000 0000 0000 0000 0000 0000 ................ 0008840: 0000 0000 0000 0013 0000 0000 0000 0000 ................ 0008850: 0000 0000 0000 0000 0000 0000 0000 0000 ................我们来看一下这个扇区的每一个字节所代表的意思:
偏移地址 |
数据类型 |
说明 |
值 |
0x0 |
Byte |
启动标识,必须是0 |
0 |
0x1 - 0x5 |
Byte |
ISO-9600定义,必须是CD001 |
CD001 |
0x6 |
Byte |
版本描述符,必须是1 |
1 |
0x7 - 0x26 |
Byte |
引导系统标识符,必须是EL TORITO SPECIFICATION并以0填充未使用的字节 |
EL TORITO SPECIFICATION |
0x27 - 0x46 |
Byte |
保留,必须是0 |
0 |
0x47 - 0x4A |
Dword |
引导目录的第一个扇区号 |
0x00000013 |
0x4A - 0x7FF |
Byte |
保留,必须是0 |
0 |
值得注意的是,这个扇区的内容只是标识出这张光盘是可引导的,它的开始引导目录的第一个扇区号是0x00000013。所以,我们还要看一下0x00000013这个扇区的内容(位置在0x13 * 0x800 = 0x9800):
0009800: 0100 0000 0000 0000 0000 0000 0000 0000 ................ 0009810: 0000 0000 0000 0000 0000 0000 aa55 55aa .............UU. 0009820: 8800 0000 0000 0402 2501 0000 0000 0000 ........%....... 0009830: 0000 0000 0000 0000 0000 0000 0000 0000 ................ 0009840: 0000 0000 0000 0000 0000 0000 0000 0000 ................
我们再来看一下这个扇区的每一个字节所代表的意思:
偏移地址 |
数据类型 |
说明 |
值 |
0x0 |
Byte |
引导目录标识,必须是01 |
01 |
0x1 |
Byte |
平台ID |
0 (80x86 platform id) 1 (Power PC platform id) 2 (Mac platform id) |
0x2 - 0x3 |
Byte |
保留,必须是0 |
0 |
0x4 - 0x1B |
Byte |
生产厂商 |
0 |
0x1C - 0x1D |
Byte |
校验码,0 |
0 |
0x1E |
Byte |
结束标识55 |
55 |
0x1F |
Byte |
结束标识AA |
AA |
0x20 |
Byte |
启动标识符 |
0 (not bootable) 88 (bootable) |
0x21 |
Byte |
启动媒体类型 |
0 (no emulation) 1 (1.2mb disk) 2 (1.44mb disk) 3 (2.88mb disk) 4 (hard drive) |
0x22 - 0x23 |
Word |
将引导扇区数据载入内存的段地址,如果 是0则代表载入到默认段地址为0x7c0处 |
0 |
0x24 |
Byte |
系统分区类型,必须是0 |
0 |
0x25 |
Byte |
保留,必须是0 |
0 |
0x26 - 0x27 |
Word |
载入扇区数 |
0x0400 |
0x28 – 0x2B |
Dword |
正式载入引导程序的开始扇区号 |
0x00000125 |
0x2C - 0x3F |
Byte |
保留,必须是0 |
0 |
了解了光盘的头格式,下一步我们就可以制作一张可引导的光盘镜像,并将前面使用汇编编写的引导程序烧录到我们的光盘镜像中。首先创建一个名为mkiso的Makefile新工程,并创建一个mkiso.c的C语言源代码文件,并在里面编写下面的代码。我们先要定义一些常用的类型:
typedef unsigned char u8; typedef unsigned short u16; typedef unsigned int u32; typedef unsigned long long u64;
无符号单字节、无符号双字节、无符号4字节和无符号8字节类型。下面是第17扇区头内容,按字节大小和类型进行相关定义:
typedef struct iso_struct { u8 boot_record_indicator; char iso_9660_identifier[5]; u8 version_of_this_descriptor; char boot_system_identifier[23]; u8 pad0; u8 pad1; u8 pad2[32]; u8 pad3[7]; u8 first_sector0; u8 first_sector1; u8 first_sector2; u8 first_sector3; u8 pad4[1973]; } iso_struct;
再下面是第19扇区的头格式内容:
typedef struct iso_boot_catalog { u8 boot_indicator; u8 platform_id; u8 reserved0; u8 reserved1; u8 pad0[26]; u8 end_55; u8 end_AA; u8 bootable; u8 boot_media_type; u8 load_segment_for_image0; u8 load_segment_for_image1; u8 system_type; u8 pad1; u8 sector_count0; u8 sector_count1; u32 start_address_of_the_virtual_disk; u8 pad2[20]; u8 pad3[1984]; } iso_boot_catalog;
再定义两个类型,也就是上面两个结构体类型,这样定义是为了使用方便:
typedef iso_struct iso; typedef iso_boot_catalog iso_boot;
这样定义的好处是以后想要定义一个struct iso_struct类型和struct iso_boot_catalog 的变量只需要这样定义:
iso i; iso_boot b;
而不再需要再使用结构体名称来定义,比如这样:
struct iso_struct i; struct iso_boot_catalog b;这样就大大简化了代码,也提高了编程的效率。
再下面就是定义两个光盘头格式的变量,并将其内容写如bin文件中:
iso iso_m; //为iso_n赋值,设置17扇区内容 ... iso_boot iso_b; //为iso_b赋值,设置19扇区内容 ...创建一个bin文件,这个文件的路径来自于args[1],也就是说我们编写的mkiso程序在执行时需要给它传入参数,参数的内容即是要生成的bin文件目录及文件名。并写入iso_m扇区内容:
FILE *p = fopen(args[1], "wb"); fwrite((char*) &iso_m, sizeof(char), sizeof(iso_m), p); fclose(p);创建一个bin文件,并写入iso_b扇区内容:
FILE *p2 = fopen(args[2], "wb"); fwrite((char*) &iso_b, sizeof(char), sizeof(iso_b), p2); fclose(p2);再为mkiso工程编写一个Makefile文件,加入以下内容:
BUILD = build all: mkdir -p $(BUILD) #编译mkiso.c文件,生成mkiso可执行文件。 gcc -m32 mkiso.c -o $(BUILD)/mkiso #执行mkiso程序,生成第17扇区的iso_header.bin文件和第19扇区的iso_bootable.bin文件。 $(BUILD)/mkiso $(BUILD)/iso_header.bin $(BUILD)/iso_bootable.bin clean: rm -rvf $(BUILD)编译并运行,可以看到在mkiso工程下生成了build目录,里面有mkiso、iso_header.bin和iso_bootable.bin三个文件,如下图:
看到程序执行的正确结果之后,我们就可以将这个工具工程的源代码加入到lidqos主工程的Makefile文件中,并执行dd命令将iso_header.bin、iso_bootable.bin和boot.bin烧录到我们的iso光盘镜像文件中。在lidqos的Makefile文件中修改代码为:
BUILD = build all: mkdir -p $(BUILD) gcc boot.S -c -m32 -std=c99 -o $(BUILD)/boot.o ld -m elf_i386 -Ttext 0x0 -e _start $(BUILD)/boot.o -o $(BUILD)/boot objcopy -O binary $(BUILD)/boot $(BUILD)/boot.bin gcc -m32 ../tool/mkiso/mkiso.c -o $(BUILD)/mkiso #编译并执行mkiso,生成iso_header.bin、iso_bootable.bin文件。 $(BUILD)/mkiso $(BUILD)/iso_header.bin $(BUILD)/iso_bootable.bin #将iso_header.bin烧录到第17扇区。 dd bs=2048 seek=17 count=1 conv=notrunc if=$(BUILD)/iso_header.bin of=$(BUILD)/boot.iso #将iso_bootable.bin烧录到第19扇区。 dd bs=2048 seek=19 count=1 conv=notrunc if=$(BUILD)/iso_bootable.bin of=$(BUILD)/boot.iso #将boot.iso引导程序烧录到293(0x0125)扇区。 dd bs=2048 seek=293 count=1 conv=notrunc if=$(BUILD)/boot.bin of=$(BUILD)/boot.iso #补齐扇区。 dd bs=2048 seek=4096 count=1 conv=notrunc if=/dev/zero of=$(BUILD)/boot.iso clean: rm -rvf $(BUILD)
执行make all编译,就会在lidqos/build目录下生成相关文件,这里我们关心的是boot.iso光盘镜像文件,如下图:
成功生成了boot.iso文件之后,就可以在VirtualBox中使用这个文件来引导我们的系统了,在VirtualBox中修改lidqos虚拟机的设置,删除软驱控制器,并在CD/DVD Drive中选中我们上面生成的boot.iso文件。如下图:
点击OK按钮,并启动虚拟机,运行成功:
源代码的下载地址为:
https https://github.com/magicworldos/lidqos.git git git@github.com:magicworldos/lidqos.git subverion https://github.com/magicworldos/lidqos branch v0.2
Copyright © 2015-2023 问渠网 辽ICP备15013245号