一、前言
本文档记录的内容既适用于x86也适用于x64,只是对于后者有一些环境要求。
之前开发使用的方法是:自己的引导代码+虚拟软盘。优点是搭建简单,所有代码都是自己编写,可控性强。最近想试试使用grub的引导功能,于是花了些时间琢磨。搜出来的相关资料有不少,但是要么是grub1的,要么太过零散,要么描述太过简略。总之,没有一篇文章详细的讲述整个配置过程。所以我就在搭建的过程中顺手整理了这么一篇完整的、完全从零开始的方法,其中每一步都有较丰富的说明。
另外,本文档介绍的方法适用于osx和linux,实际上整个过程中大部分必须使用到linux。也就是说如果要按照本文档来搭建开发环境,linuxer只需要使用自己的linux系统就行,而osxer还得备一套linux系统(比如虚拟机)。使用linux的主要原因是我选择了ext2作为文件系统,而osx上貌似只有读写ext2的fuse-ext2,没有用于创建ext2分区的fdisk等工具(如果同好有osx的ext2创建工具推荐,劳烦分享给我(ns.boxcounter[at]gmail.com)吧,不胜感激)。如果改用fat32就没有这个烦恼,整个过程都可以在osx下完成,因为osx的fdisk就可以创建fat32分区。
我使用的系统、软件情况:
- x86 ubuntu 12.04.2/x64 ubuntu 13.10、osx 10.8.4/osx 10.9
- nasm 2.10.09
- bochs 2.6.1
- grub2 2.0.0
如果同好使用的环境不一样,可能需要根据情况自行调整一些细节。
另外,本文提供的命令在显示时候可能会自动折行,所以复制到剪贴板中之后(在折行处)可能会有多余的空格,请同好自行删减。
二、创建虚拟磁盘,并分区
首先说明:
这里的目标磁盘的属性是:16 headers, 63 sectors per track, 512 bytes per sector。意味着每一个cylinder的大小是516096bytes(16 * 63 * 512)。
“#cylinders”表示柱面数,主要关系到磁盘大小。如果是10MB的磁盘,#cylinders=20。
需要在linux系统中进行,使用的工具是kpartx,系统默认没有自带,需要下载。
好了,开始了。
-
dd if=/dev/zero of=antos.img bs=516096 count=#cylinders
创建虚拟磁盘。也可以使用bochs附带的bximage工具来完成。 -
ps aux | grep loop
默认是搜索不到名为“[loopX]”进程的。如果有发现,那记住输出中的“[loopX]”进程。 -
kpartx -av ./antos.img
挂载虚拟磁盘,可能没有输出。 -
ps aux | grep loop
正常情况下,这里会发现一个名为“[loop0]”的进程。说明antos.img被挂载到了“/dev/loop0”设备上。如果前面搜索结果中已经有了“[loopX]”进程,那新增加的那个进程就是挂载的设备名。 -
fdisk -u -C#cylinders -S63 -H16 /dev/loop0
为磁盘分区。以#cylinders=20、单个分区为例:root@ubuntu:~# fdisk -u -C20 -S63 -H16 /dev/loop0 Device contains neither a valid DOS partition table, nor Sun, SGI or OSF disklabel Building a new DOS disklabel with disk identifier 0x136d49ee. Changes will remain in memory only, until you decide to write them. After that, of course, the previous content won't be recoverable. Warning: invalid flag 0x0000 of partition table 4 will be corrected by w(rite) Command (m for help): o <<<=== Create a new empty DOS partition table Building a new DOS disklabel with disk identifier 0x5bd665d5. Changes will remain in memory only, until you decide to write them. After that, of course, the previous content won't be recoverable. Warning: invalid flag 0x0000 of partition table 4 will be corrected by w(rite) Command (m for help): n <<<=== Create a new partition Partition type: p primary (0 primary, 0 extended, 4 free) e extended Select (default p): <<<=== 回车 Partition number (1-4, default 1): <<<=== 回车 First sector (2048-20159, default 2048): <<<=== 回车 Using default value 2048 Last sector, +sectors or +size{K,M,G} (2048-20159, default 20159): <<<=== 回车 Using default value 20159 Command (m for help): a <<<=== Toggle the bootable flag (Optional) Partition number (1-4): 1 <<<=== 分区1 Command (m for help): p <<<=== Print the partition table. Disk /dev/loop0: 10 MB, 10321920 bytes 16 heads, 63 sectors/track, 20 cylinders, total 20160 sectors Units = sectors of 1 * 512 = 512 bytes Sector size (logical/physical): 512 bytes / 512 bytes I/O size (minimum/optimal): 512 bytes / 512 bytes Disk identifier: 0x5bd665d5 Device Boot Start End Blocks Id System /dev/loop0p1 * 2048 20159 9056 83 Linux <<<=== 如果使用附录2记录的方法,需要记录Start和Blocks的值,本例子中分别是2048和9056。 Command (m for help): w <<<=== Write partition table to our 'disk' and exit The partition table has been altered! Calling ioctl() to re-read partition table. WARNING: Re-reading the partition table failed with error 22: Invalid argument. The kernel still uses the old table. The new table will be used at the next reboot or after you run partprobe(8) or kpartx(8) Syncing disks. <<<=== Ignore any errors about rereading the partition table. Since it's not a physical device we really don't care.
-
kpartx -dv ./antos.img
卸载磁盘,应该输出“loop deleted : /dev/loop0”。 -
kpartx -av ./antos.img
挂载分区磁盘,这次新创建的分区也会自动挂载。
正常会输出“add map loop0p1 (252:0): 0 18112 linear /dev/loop0 2048”,表示分区挂载到了“/dev/mapper/loop0p1”设备上。 -
mke2fs -b1024 /dev/mapper/loop0p1
格式化分区,"-b1024"表示使用1KB的block。 -
mkdir /tmp/antos
创建挂载目录。 -
mount -text2 /dev/mapper/loop0p1 /tmp/antos
挂载分区到目录。 -
ls /tmp/antos/
如果前面的步骤都成功,会看到名为“lost+found”的目录,说明磁盘和分区都正确的创建了。
三、安装grub2到虚拟磁盘
假设磁盘挂载到设备“/dev/loop0”上,分区挂载到“/tmp/antos”目录下。
grub-install --no-floppy --modules="biosdisk part_msdos ext2 configfile normal multiboot" --root-directory=/tmp/antos /dev/loop0
安装过程中可能会报警告,只要最后输出“Installation finished. No error reported.”就表示安装成功了。
如果使用的系统是x64架构的,需要使用新一些的系统,比如ubuntu 13.10。具体原因请参考附录一。
(在fedora等redhat系中使用的名称是“grub2-install”)
四、在bochs中使用虚拟磁盘
-
确认虚拟磁盘的属性。
先挂载虚拟磁盘,然后执行“disk -u -l /dev/loop0”,正常会有如下输出1 Disk /dev/loop0: 10 MB, 10321920 bytes 2 16 heads, 63 sectors/track, 20 cylinders, total 20160 sectors 3 Units = sectors of 1 * 512 = 512 bytes 4 Sector size (logical/physical): 512 bytes / 512 bytes 5 I/O size (minimum/optimal): 512 bytes / 512 bytes 6 Disk identifier: 0x6418cb2e 7 8 Device Boot Start End Blocks Id System 9 /dev/loop0p1 * 2048 20159 9056 83 Linux
第2行说明了虚拟磁盘的属性。(前面使用fdisk为磁盘分区的时候也有输出同样的内容,如果记下来了,就可以不需要这一步)
-
创建bochs虚拟机配置文件
不带参数运行bochs,应该会有这样的输出:1. Restore factory default configuration 2. Read options from... 3. Edit options 4. Save options to... 5. Restore the Bochs state from... 6. Begin simulation 7. Quit now Please choose one: [2]
选择4,然后输入配置文件名,比如“antos.bxrc”,提示保存成功后退出bochs。这样就有了一份默认配置的bochs虚拟机配置文件。
-
修改bochs虚拟机配置文件,以适应我们的需要
-
添加虚拟磁盘。
将这一行ata0-master: type=none
改为
ata0-master: type=disk, path="./antos.img", cylinders=#cylinders,heads=#heads,spt=#sec-per-track
对应之前获取到的磁盘属性,这一行应该是:
ata0-master: type=disk, path="./antos.img", cylinders=20,heads=16,spt=63
-
修改启动项为磁盘。
将这一行boot: floppy
改为
boot: disk
-
开启bochs的magic breakpoint。
将这一行magic_break: enabled=0
改为
magic_break: enabled=1
-
到这里,就可以在bochs中运行了(命令是“bochs -f antos.bxrc”),并且看到grub2的命令提示符,如图:

五、编写最简单的系统内核
// kernel.asm源码
[section .kernel]
[bits 32]
load_base equ 0x100000
;
; multiboot header
;
align 8
multiboot_header:
MBH_magic equ 0xE85250D6
MBH_architecture equ 0 ; 32-bit protected mode
MBH_header_length equ multiboot_header_end - multiboot_header
MBH_checksum equ -(MBH_header_length + MBH_magic + MBH_architecture)
dd MBH_magic
dd MBH_architecture
dd MBH_header_length
dd MBH_checksum
; tags
info_request_tag:
dw 1
dw 0
dd info_request_tag_end - info_request_tag
dd 5
dd 6
info_request_tag_end:
align 8
address_tag:
dw 2
dw 0
dd address_tag_end - address_tag
dd load_base ; header_addr
dd load_base ; load_addr
dd 0 ; load_end_addr
dd 0 ; bss_end_addr
address_tag_end:
align 8
entry_address_tag:
dw 3
dw 0
dd entry_address_tag_end - entry_address_tag
dd load_base + kernel_entry
entry_address_tag_end:
align 8
; end tag
dw 0
dw 0
dd 8
multiboot_header_end:
kernel_entry:
xchg bx, bx ; magic breakpoint
jmp $
编译
nasm kernel.asm -o kernel.bin
编译过程中可能会报警告,无视它。
将虚拟磁盘挂载到某个目录,然后将kernel.bin拷贝到分区的根目录,即和/boot目录同一层目录。
六、使用grub2启动自行编写的操作系统内核
假设分区挂载到“/tmp/antos”目录下,那么创建grub需要的配置文件“/tmp/antos/boot/grub/grub.cfg”,将以下几行文本贴进去:
set default=0
insmod ext2
set root=(hd0,1)
set timeout=10
menuentry "antos 0.0.1" {
insmod ext2
set root=(hd0,1)
multiboot2 (hd0,1)/kernel.bin
boot
}
其中(hd0,1)表示咱之前创建的虚拟磁盘的第一个分区,kernel.bin就是前面编译的系统内核文件。
现在可以启动咱的bochs虚拟机了,执行“bochs -f antos.bxrc”。
再输入c继续执行后,应该就能看到bochs从咱的虚拟磁盘引导,然后可以看见grub的选择界面,最后会中断到咱系统内核的“xchg bx, bx”指令,这是bochs内置的主动中断指令,即magic breakpoint机制。如下:
o 00449811185i[CPU0 ] [449811185] Stopped on MAGIC BREAKPOINT
(0) Magic breakpoint
Next at t=449811185
(0) [0x000000100053] 0010:0000000000100053 (unk. ctxt): jmp .-2 (0x00100053) ; ebfe
00449811185i[XGUI ] Mouse capture off
<bochs:2>
ok,整个配置过程就完毕了,整个过程都是在linux中完成的。使用fat32的osx同好可以使用类似的方法来完成。整个过程每一步的功能都写的很清楚了,看到这里理理思路应该就明白整个流程了。
七、osx中读写ext2文件系统的虚拟磁盘
最后说说osx相关的内容,因为我不想每次做开发的时候都需要开个linux虚拟机。以下是在osx下读写虚拟磁盘的方法,比如更新的kernel.bin等等。linuxer可以无视这一步。
-
安装osxfuse和fuse-ext2
fuse-ext2默认只能以只读方式挂载设备,所以需要进行以下修改使其默认以可读可写方式挂载设备:sudo vi /System/Library/Filesystems/fuse-ext2.fs/fuse-ext2.util
搜索定位到Mount函数,为其名为“OPTIONS”的变量增加额外的“rw+”选项。
比如:原内容为:function Mount () { LogDebug "[Mount] Entering function Mount..." # Setting both defer_auth and defer_permissions. The option was renamed # starting with MacFUSE 1.0.0, and there seems to be no backward # compatibility on the options. OPTIONS="auto_xattr,defer_permissions" ... }
改为:
function Mount () { LogDebug "[Mount] Entering function Mount..." # Setting both defer_auth and defer_permissions. The option was renamed # starting with MacFUSE 1.0.0, and there seems to be no backward # compatibility on the options. OPTIONS="auto_xattr,defer_permissions,rw+" ... }
-
挂载磁盘到设备
hdiutil attach -nomount antos.img
这是会输出如下信息:
/dev/disk1 FDisk_partition_scheme /dev/disk1s1 Linux
说明虚拟磁盘已经挂载到/dev/disk1设备上了,分区已经挂载到/dev/disk1s1。(之所以加上-nomount参数,是因为hdiutil没法正确地挂载ext2分区到目录)
-
挂载分区到目录
/sbin/mount_fuse-ext2 /dev/disk1s1 ./mnt
/dev/disk1s1是步骤3中得到的设备(分区)名。
到这里,就可以对分区内容进行修改了,比如更新kernel.bin等等。 -
从目录卸载分区
umount ./mnt
-
卸载虚拟磁盘
hdiutil detach /dev/disk1
(注意是磁盘设备,不是分区设备disk1s1)。
八、附录一:x64 ubuntu 12.04.2中执行grub-install遇到的问题
我在x64 ubuntu 12.04.2中执行“安装grub2到虚拟磁盘”操作时总是失败,如下:
root@x64:~# grub-install --no-floppy --modules="biosdisk part_msdos ext2 configfile normal multiboot" --root-directory=/tmp/antos /dev/loop0
Path `/tmp/antos/boot/grub' is not readable by GRUB on boot. Installation is impossible. Aborting.
加上–deubg选项后,发现grub-install输出了这么几行:
+ /usr/local/sbin/grub-probe -t fs /tmp/antos/boot/grub
+ return 1
+ gettext_printf Path `%s' is not readable by GRUB on boot. Installation is impossible. Aborting.\n /tmp/antos/boot/grub
手动执行grub-probe,输出如下:
root@ubuntu:~# /usr/local/sbin/grub-probe -t fs /tmp/antos/boot/grub/usr/local/sbin/grub-probe: error: disk `lvm/loop0p1' not found.
我查阅了很多资料,都没有明确的解决方案。经过了约莫20个小时的摸索,最终发现只需要使用新版本的系统自带的grub 2.0.0即可。(注:x64 ubuntu 12.04.2中的grub2是我源码编译的2.0.0版,也尝试过使用trunk源码编译或者使用系统自带的1.99,都会报错。)
九、附录二:创建虚拟磁盘分区的另外一种方法(losetup)
需要说明,这种方法较前面介绍的使用kpartx的方法要繁琐,所以并不推荐(特别是如果要使用多分区)。补充在这里的原因是我最开始搜索到的资料使用的就是losetup工具,摸索成功之后才发现kpartx。
另外,操作过程中有部分步骤和前面讲述的步骤一样,所以省略了那些步骤的说明。
- dd if=/dev/zero of=antos.img bs=516096 count=#cylinders
- losetup /dev/loop0 ./antos.img
这个时候执行“ps aux | grep loop”,会看到一个名为[loop0]的进程。(如果loop0被占用,可以换一个设备) - fdisk -u -C#cylinders -S63 -H16 /dev/loop0
- losetup /dev/loop0 ./antos.img
这个时候执行“ps aux | grep loop”,会看到一个名为[loop0]的进程。(如果loop0被占用,可以换一个设备) - losetup -d /dev/loop0
到这里,虚拟磁盘已经创建完毕了,从设备(“loop0”)上卸载虚拟磁盘,准备格式化。 - losetup -o1048576 /dev/loop0 ./antos.img
再次挂载,与前面挂载不同的是,这次使用了“-o1048576”参数,目的是跳过前1048576字节,来到分区的开始。前面提到要记住Start的值,即分区开始扇区号,这里就需要使用它了,1048576=2048*512。 - mke2fs -b1024 /dev/loop0 9056
对加载到“loop0”设备上的分区(注意是分区,不是整个磁盘了,前面咱跳到了分区开始处)进行格式化,使用的是ext2文件系统。
”-b1024“表示使用1KB的block,9056就是之前的Blocks的值,即整个分区的blocks数。 - mkdir /tmp/antos
- mount -text2 /dev/loop0 /tmp/antos
- umount /dev/loop0
- losetup -d /dev/loop0
卸载目录和设备。
十、主要参考资料
- The Multiboot Specification
- grub2源码
- Mac OS X下读写ext2/ext3文件系统
十一、版本记录
- v1.0 - 2013-09-02,初始发布。
- v1.1 - 2013-11-14,增加x64系统下的说明。