声明:此文章为本人在知乎首发的原创文章
目录
一、概述
这可能是中文互联网最奇怪的内核开发教程了:
- 这个教程不会教你如何写bootloader——它本不属于内核。
- 这个教程不会教你x86架构的启动过程——16位实模式到32位保护模式到开启ia32e模式——这应该是bootloader需要考虑的。
- 这个教程不会直接告诉你内核的各个模块应该怎么写——譬如内存管理、进程调度、中断机制——当需要实现一个个实实在在的功能时,它们自然会出现:你总是知道你为什么要写这段代码。
- 这个教程不会教你如何构建内核编译系统——这本应是各位读者自己完成的——自己发挥的空间非常大。
某些不太需要熟练掌握的知识,会在需要的地方,插入其它的教程链接以便读者学习;而一些需要熟练掌握的知识,则不会插入链接。
二、前置知识
- C语言是必须会的,本教程大多数代码都使用C语言。
- 少许rust,这是我的个人偏好,用来取代c++;有些时候rust比c++要方便一些;rust代码的数量会很少,不会给读者太大的负担。
- 汇编语言,负责管理cpu状态的指令必须直接使用。
- 一种构建脚本(
make
、cmake
、xmake
等) - 合适的开发工具,功能过于复杂的IDE和功能过于简单的文本编辑器都不适合开发内核,推荐使用vscode。
- 基础的Linux终端命令。
nano
编辑器,编辑一些需要root权限的文件,你也可以选择使用vim
或emacs
,不过nano最简单,用法都在界面中罗列出来。
不熟悉Linux的读者进:初学者必学的20个Linux基础命令
三、开发环境
我们需要一个Linux环境,Linux上有很多自带的开发工具,而使用Windows需要自己寻找能够替代的软件。
对于Windows用户来说,现在搭建一个Linux环境非常简单,我们不再需要花费大量和精力时间安装和配置一个虚拟机,docker和wsl都是很好的选择。
- 安装wsl
超详细windows安装配置WSL2(ubuntu20.04)步骤 - 可能有用的操作
windows如何访问wsl系统下的文件 - 愿意折腾的读者可以试着在wsl中安装图形界面,这样整个开发的过程就都只需在wsl中完成了
WSL2 GUI 图形界面配置教程(deprecated)
以下是需要在Linux中额外安装的软件:
gcc
make/cmake/xmake
grub - 在docker和wsl环境下可能不会自带bootloader
qemu - 用于测试内核的虚拟机
git
rustup - 安装rust工具链
openssh - 运行在Windows上的vscode可以通过ssh连接打开项目
nbd - 挂载具有特定格式的虚拟磁盘镜像
gnome-boxes - 提供UEFI镜像
vscode可以通过插件Remote Development连接ssh、docker容器等,让我们能够像在本机上一样开发项目,远程连接时打开的终端也是远程环境中的终端:WSL+VSCode食用指南
使用Windows的读者需要在本机安装qemu而不是在docker或者wsl中安装qemu。
另外评论区有读者反映wsl和docker不太好用,这可能在不同的CPU、不同的windows版本上都有不同的效果,如果wsl和docker在你的环境中不好用,可以安装一个linux发行版的虚拟机:(贴出manjaro仅仅因为我比较喜欢arch系,和deb系相比仅仅是网络上的资料少一些,使用体验绝对不差)
Manjaro安装与配置指南
VMware虚拟机 安装Ubuntu 22.04系统教程(图文步骤,贼详细)
(夹带私货)
三、一些准备工作
1. 我们需要UEFI镜像,使得qemu能够以UEFI模式启动
gnome-boxes
自带一系列UEFI镜像,安装后可以使用find命令找到文件OVMF_CODE.fd的路径,并将其复制到项目中来。
例如:
cd /
sudo find . -name "OVFM_CODE.fd"
搜索结果中会有路径包含edk2/x64的,这个文件就是以64位启动的UEFI镜像。
使用Linux发行版的读者可以直接用图形界面工具来完成这个过程。
2. 我们需要准备一个可以UEFI启动的虚拟磁盘文件
创建并格式化磁盘
创建一个qcow2格式虚拟磁盘
qemu-img create -f qcow2 镜像文件名 512M
然后挂载为nbd设备
sudo modprobe nbd
sudo qemu-nbd --connect=/dev/nbd0 镜像文件名
这时/dev/nbd0就可以作为正常的磁盘使用了。
接下来需要为这个磁盘分区,分区建议如下:
分区号 | 分区类型 | 分区大小 | 分区设备名 |
---|---|---|---|
1 | EFI系统 | 100MB | /dev/nbd0p1 |
2 | Linux root | 剩余空间 | /dev/nbd0p2 |
例如使用fdisk命令进行分区:sudo fdisk /dev/nbd0
然后sudo partprobe /dev/nbd0
确保新分的分区都被检测到。
给两个分区都写入fat32文件系统:
sudo mkfs.fat -F 32 /dev/nbd0p1
sudo mkfs.fat -F 32 /dev/nbd0p2
安装uefi启动的grub
首先在项目的目录下将两个分区分别挂载:
sudo mount -m /dev/nbd0p2 ./mnt # 在当前目录创建mnt目录并将设备挂载
sudo mount -m /dev/nbd0p1 ./mnt/boot
写入grub
sudo grub-install --target=x86_64-efi --efi-directory=./mnt/boot/EFI --bootloader-id=GRUB --recheck --boot-directory=./mnt/boot /dev/nbd0
不过安装好的需要经过一些配置才能正常启动,因此我准备了一个repo 指向BIOS的野指针/well-confugured-grub,将它clone下来直接将内容复制到mnt/boot/grub/中即可:
git clone https://gitee.com/pointer-to-bios/well-confugured-grub.git
sudo cp -r well-configured-grub/* ./mnt/boot/grub/
在grub.cfg中,可以找到以下内容:
### BEGIN ###
# 在此编写menuentry
### END ###
只需要自己编写一个menuentry即可:
menuentry "你的系统名" {
multiboot2 /内核文件
}
启动时,你的系统名将会显示在grub启动界面上。
本教程使用multiboot2标准作为内核的引导标准,因此使用multiboot2命令。
只要将构建好的内核文件放到mnt/boot/下就可以引导内核了,不过暂时不把内核放进去这个启动项也会显示。
配置好后将两个分区卸载:
sudo umount /dev/nbd0p1
sudo umount /dev/nbd0p2
最后将设备/dev/nbd0卸载:
sudo qemu-nbd --disconnect /dev/nbd0
这样,一个可引导的虚拟磁盘镜像就准备好了,wsl中没安装图形界面或使用docker的读者需要先将虚拟磁盘镜像和UEFI镜像从wsl或docker中移动到宿主机中,用qemu启动它:
qemu-system-x86_64 镜像文件 -m 1G -bios OVMF_CODE.fd
这个命令创建了一个UEFI引导的具有1GB内存的qemu虚拟机,运行这个命令可以看到grub启动界面:
一个挂载、卸载、启动可引导虚拟磁盘的make脚本
分区和写入grub的过程只需要执行一次,但是虚拟磁盘的挂载和卸载以及把内核放进虚拟磁盘的操作会在开发过程中频繁使用,可以编写一个构建脚本简化这些过程。我准备了一个make脚本供读者参考(这个make脚本没有写入grub的功能):
TARGET = ../src/metaverse.elf
.PHONY: run debug load mount unmount
BIOS = bios/OVMF_CODE.fd
# 运行
run:
@sudo modprobe nbd
@make load
@qemu-system-x86_64 -m 4G metaverse.img -enable-kvm -bios ${BIOS}
# gdb调试
debug:
@sudo modprobe nbd
@make load
@qemu-system-x86_64 -m 4G metaverse.img -enable-kvm -bios ${BIOS} -s -S
# 创一个镜像
create:
@qemu-img create -f qcow2 metaverse.img 512M
@sudo modprobe nbd
@sudo qemu-nbd --connect=/dev/nbd0 metaverse.img
@sudo partprobe /dev/nbd0
@echo "你需要为这个虚拟磁盘写入一个GPT分区表并分出efi分区和根分区。"
@sudo fdisk /dev/nbd0
@sudo mkfs.fat -F 32 /dev/nbd0p1
@sudo mkfs.fat -F 32 /dev/nbd0p2
@sudo mount /dev/nbd0p2 mnt/
@sudo mount -m /dev/nbd0p1 mnt/boot/
@sudo mkdir mnt/boot/EFI/
@make unmount
# 将内核文件放到镜像中
load:
@echo loading kernel ...
@make mount
@sudo cp ${TARGET} mnt/boot/
@make unmount
# 挂载镜像
mount:
@sudo modprobe nbd
@sudo qemu-nbd --connect=/dev/nbd0 metaverse.img
@sudo partprobe /dev/nbd0
@sudo mount /dev/nbd0p2 mnt/
@sudo mount /dev/nbd0p1 mnt/boot/
# 卸载镜像
unmount:
@sync
@sudo umount /dev/nbd0p1
@sudo umount /dev/nbd0p2
@sudo qemu-nbd --disconnect /dev/nbd0
至此准备工作完成。