《真象还原》手写操作系统运行在MacOS+bochs/qemu/virtualbox

    Hurray 337次浏览 4条评论 8282字

#操作系统 #MacOS #虚拟机 #qemu #virtualbox

> 近段时间阅读《操作系统-真象还原》(郑钢 著)来学习操作系统。这本书环境是在`CentOS`上使用的虚拟机`bochs`。我这边除了`CentOS`之外还有`MacOS`,因此对本书代码的兼容性有更多考虑。 经过我的不断调研和尝试,最终在`CentOS`/`MacOS`上跑通了`bochs`、`qemu`和`virtualbox`。由于`CentOS`是本书默认支持的环境,因此这里不再阐述,接下来将介绍`MacOS`上跑通`bochs`/`qemu`/`virtualbox`的方法。 环境/虚拟机相关makefile:[https://github.com/Hurray0/elephant-os/blob/main/include.mk]() 本文github代码:[https://github.com/Hurray0/elephant-os]() ### 1. MacOS上运行虚拟机bochs 对于`bochs`来说,`MacOS`与`CentOS`差异比较小。具体有以下内容需要改动: * __编译器改动__:我这边mac是m1(arm) CPU,使用的gcc和x86的不同,因此需要改动编译器 * __虚拟机配置改动__:mac不支持默认的`X Window System`,因此display配置需要改动为`sdl2` ### 1.1 编译器改动 __改动原因__:在`MacOS`上`gcc`与linux指令不一致,生成的汇编指令不同,即一个是arm,一个是x86。因此编译出来的kernel(书第五章(保护模式)第三节(加载内核)之后)在无法使用在x86虚拟机上。 在mac的arm CPU机器上,如何编译出x86的二进制文件呢?答案就是`x86_64-elf-gcc`。具体是通过`brew install x86_64-elf-gcc`安装的。 具体编译改动如下: ```makefile UNAME_S := $(shell uname -s) DEBUG_FLAG = -g ifeq ($(UNAME_S),Darwin) OS = mac GCC = x86_64-elf-gcc $(DEBUG_FLAG) LD = x86_64-elf-ld GDB = x86_64-elf-gdb else OS = linux GCC = gcc $(DEBUG_FLAG) LD = ld GDB = gdb endif ``` ### 1.2 虚拟机配置改动 __改动原因__:bochs默认display系统为_X Window System_,在MacOS上虽然可以安装 _XQuartz_ 来提供 X11 支持,但这种方式并不是最理想的。因此我们在Mac上推荐使用 `SDL2` (Simple DirectMedia Layer 2) 作为显示库,具有更好的用户体验。 具体是改动`bochs`的配置文件,增加一行: ``` display_library: sdl2 ``` 我这边名称是`bochsrc.disk`,makefile命令如下: ```makefile # 修改bochsrc.disk文件,适配不同的操作系统 env: @if [ "$(OS)" = "mac" ]; then \ sed -i '' 's/^#display_library: sdl2/display_library: sdl2/' bochsrc.disk; \ elif [ "$(OS)" = "linux" ]; then \ sed -i 's/^display_library: sdl2/#display_library: sdl2/' bochsrc.disk; \ fi ``` ## 2. MacOS上运行虚拟机qemu MacOS可以直接使用`brew install qemu`安装`qemu`虚拟机。这个虚拟机和virtualbox一样是成熟的商用虚拟机,比bochs运行速度要快得多,也因此会引发一些bochs上不会遇到的问题(原书没考虑到的bug),在进入实模式之后原书的代码就运行不了了。具体改动如下: * __启动命令改动__: qemu上依旧可以用bochs上使用的硬盘文件`hd60M.img`和`hd80M.img`(硬盘驱动之后的章节),但是启动命令是不同的。 * __硬盘读写相关asm代码改动__: 由于qemu/virtualbox的速度比qemu快得多,原书对于硬盘读写的代码没有考虑扇区未就位问题,会导致进入不了实模式。 * __硬盘读写相关cpp代码改动__: 书中第14章的文件系统实现,`ide_write`/`ide_read`不能一次读写多个扇区,猜测原因也是扇区未就位问题。 ### 2.1 qemu启动命令改动 将`bochs -f bochsrc.disk`命令改为`qemu`的命令。为启动gdb调试功能,需要指定`-s -S`,然后通过`gdb`连接远程端口(默认为1234)来运行。这里设置了断点为`0x7c00`,即MBR入口地址。进入程序后执行`c`就能继续运行了,也可以使用gdb的`b`命令添加新的断点。 具体如下: ``` run_qemu: all # 如果存在hd80M.img,则挂载两个盘。否则只挂载hd60M.img # -s -S: 启动gdb调试,等待连接。 @if [ -f $(BUILD_DIR)/hd80M.img ]; then \ nohup $(QEMU) -s -S -hda $(BUILD_DIR)/hd60M.img -hdb $(BUILD_DIR)/hd80M.img; \ else \ nohup $(QEMU) -s -S -hda $(BUILD_DIR)/hd60M.img; \ fi > /dev/null 2>&1 & # 使用gdb连接远程1234端口 $(GDB) -q -ex "file $(BUILD_DIR)/kernel.bin" -ex "target remote :1234" -ex "b *0x7c00" -ex "c" ``` ### 2.2 硬盘读写相关asm代码改动 __改动原因__: 由于qemu/virtualbox的速度比qemu快得多,原书对于硬盘读写的代码没有考虑扇区未就位问题,会导致进入不了实模式。 具体需要改动的就是`mbr`和`loader`的汇编代码,即`rd_disk_m_16`和`rd_disk_m_32`的实现。 由于文件比较长,这里直接附上代码链接: 参考代码: [https://github.com/Hurray0/elephant-os/tree/main/10.interrupt/boot]() 对应代码改动的commit: [https://github.com/Hurray0/elephant-os/commit/27ce0e1233f3876f458beab4e3255828837aa24b]() ### 2.3 硬盘读写相关cpp代码改动 __改动原因__:书中第14章的文件系统实现,`ide_write`/`ide_read`不能一次读写多个扇区,猜测原因也是扇区未就位问题。 改动内容,将所有调用`ide_write`和`ide_read`的地方都改为一次读写一个扇区即可,然后通过for循环来读写多个扇区。例如: ``` // 从硬盘上读入块位图到分区的block_bitmap.bits ide_read(hd, block_bitmap_lba, block_bitmap.bits, block_bitmap_sects); ``` 改为: ``` for (uint32_t i = 0; i < block_bitmap_sects; i++) { ide_read(hd, block_bitmap_lba + i, block_bitmap.bits + i * 512, 1); } ``` 参考代码:[https://github.com/Hurray0/elephant-os/tree/main/23.file_system]() 对应commit: [https://github.com/Hurray0/elephant-os/commit/fdbe2f316de557b614895a78383d267c46c5cab2]() ## 3. MacOS上运行虚拟机VirtualBox virtualbox使用需要先手动建一个虚拟机,这里我使用的名称为`MyOS2`。 具体改动有: * __磁盘生成__:VB不能使用之前的`hd60M.img`/`hd80M.img`,需要使用`VBoxManage`命令来创建VB的硬盘文件。 * __启动命令__:VB可以手动软件点击启动,也可以命令行启动。这里命令行启动也是`VBoxManage`命令实现的。 * __磁盘读写相关asm/cpp代码改动__:与`qemu`改动相同,这里不再叙述。也是因为运行速度太快导致的问题。 ### 磁盘生成 ```makefile # 创建virtualbox虚拟机构建的硬盘 # 如果存在hd80M.img,则创建80M的硬盘。 vb-vdi: all @rm -f $(BUILD_DIR)/hd60M.vdi @VBoxManage convertfromraw --format VDI $(BUILD_DIR)/hd60M.img $(BUILD_DIR)/hd60M.vdi @if [ -f $(BUILD_DIR)/hd80M.img ]; then \ rm -f $(BUILD_DIR)/hd80M.vdi; \ VBoxManage convertfromraw --format VDI $(BUILD_DIR)/hd80M.img $(BUILD_DIR)/hd80M.vdi; \ fi ``` ### 启动命令 具体是重建硬盘文件vdi并挂载,之后启动。 ```makefile # 虚拟机名称 VBOX_OS_NAME = "MyOS2" run_vb: all # 删除虚拟机已存在的hd60M.vdi @if [ -n "`VBoxManage showvminfo ${VBOX_OS_NAME} | grep 'Port 0, Unit 0'`" ]; then \ VBoxManage storageattach $$(VBoxManage list vms | grep '${VBOX_OS_NAME}' | awk '{print $$2}') --storagectl "IDE" --port 0 --device 0 --type hdd --medium none; \ VBoxManage closemedium disk $$(VBoxManage list hdds | grep 'hd60M.vdi' | awk '{print $$2}') --delete; \ fi # 删除虚拟机已存在的hd80M.vdi @if [ -n "`VBoxManage showvminfo ${VBOX_OS_NAME} | grep 'Port 0, Unit 1'`" ]; then \ VBoxManage storageattach $$(VBoxManage list vms | grep '${VBOX_OS_NAME}' | awk '{print $$2}') --storagectl "IDE" --port 0 --device 1 --type hdd --medium none; \ VBoxManage closemedium disk $$(VBoxManage list hdds | grep 'hd80M.vdi' | awk '{print $$2}') --delete; \ fi # 创建新的hd60M.vdi(和hd80M.vdi如需要) $(MAKE) vb-vdi @VBoxManage storageattach $$(VBoxManage list vms | grep '${VBOX_OS_NAME}' | awk '{print $$2}') --storagectl "IDE" --port 0 --device 0 --type hdd --medium build/hd60M.vdi @if [ -f $(BUILD_DIR)/hd80M.vdi ]; then \ VBoxManage storageattach $$(VBoxManage list vms | grep '${VBOX_OS_NAME}' | awk '{print $$2}') --storagectl "IDE" --port 0 --device 1 --type hdd --medium build/hd80M.vdi; \ fi @VBoxManage startvm $$(VBoxManage list vms | grep '${VBOX_OS_NAME}' | awk '{print $$2}') ``` ## 4. 总结 大概使用了三周的时间利用业余时间完成了本书的学习和代码撰写,也踩了这么多坑,因此写本文方便后续读者学习。 本文代码和脑图都放在github开源了,具体为:[https://github.com/Hurray0/elephant-os]() 如果帮助到大家了可以点个star或给本文个评论哈哈~ 如需转载请评论区说明即可

最后修改: