> 近段时间阅读《操作系统-真象还原》(郑钢 著)来学习操作系统。这本书环境是在`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或给本文个评论哈哈~ 如需转载请评论区说明即可
版权属于:Hurray's InfoShare
本文链接:https://hurray0.com/menu/164/
如果没有特别声明,则为本博原创。转载时须注明出处及本声明!