所有的代码都可以从我的GitHub repo获得
该用UEFI写操作系统教程了
有很多操作系统教程和 YouTube 视频。然而,它们中的大多数都是为 BIOS 模式启动编写的。
然而,现在已经2023年了,离Intel开源UEFI的实现已经整整19年了。同时,Intel正在抛弃传统的x86_64
架构,转向只支持64位的x86_64s
. 当然,它只支持UEFI(UEFI CSM将被删除)。
UEFI是一个更安全、更强大的引导系统。几乎所有现代操作系统都是通过UEFI引导的。似乎没有理由继续为BIOS编写操作系统教程。我们不再需要关心如何切换到长模式,或者启用分页之类的烦人细节。
我在Google上搜索后发现,关于在UEFI中编写操作系统的信息非常有限。这些信息分散在不同的地方,很难找到完整的教程。因此,我决定写一系列关于在UEFI中编写操作系统的教程。
配置测试环境
大多数操作系统教程都使用QEMU作为测试环境,因为它有以下优点:
- 只需要普通用户权限就可以执行(QEMU可以在模拟器模式下运行,这意味着它不需要访问任何硬件虚拟化基础架构,例如KVM)。
- 它非常灵活,可以配置不同的硬件。
- 它支持各种客户端CPU架构。此外,模拟器可以在与客户端CPU不同的主机CPU架构上运行。
- 它支持模拟BIOS或UEFI作为固件接口。
- 它支持调试器,可以轻松调试操作系统内核(通常在裸机上使用日志记录来调试内核)。
因此,在本教程中,我们也使用QEMU作为测试环境。好吧,我使用的是MacBook Pro,所以教程的命令行是针对macOS的,但是很容易转换为Linux版本。
- 安装QEMU
我们通常使用homebrew来安装qemu:
brew install qemu
- 为项目创建工作空间
mkdir tony-os && cd tony-os
git init .
mkdir -p src build buildenv {target,dist}/x86_64
- 为QEMU准备UEFI固件
Intel的开源UEFI固件称为TianoCore项目。代码也托管在GitHub.
在这个项目中,他们开发了一个支持QEMU的固件,提供了一个UEFI环境。
我们可以从他们的持续集成服务器上下载它,找到一个以"edk2.git-ovmf.x64"开头的文件,解压它,然后将usr/share/edk2.git/ovmf-x64/OVMF-pure-efi.fd
复制到我们的工作空间的target/x86_64/bios.bin
。
- 准备脚本来测试我们的UEFI
在我们的工作空间中写入run.sh
脚本:
#! /bin/bash
qemu-system-x86_64 \
-L target/x86_64 \
-bios target/x86_64/bios.bin \
-hda fat:rw:dist/x86_64
然后,使其可执行:chmod +x run.sh
。
如果你直接运行脚本的话,应该会启动并显示TianoCore的图像。
此事,你的工作空间应该是这样的:
build
buildenv
dist
|- x86_64
src
target
|- x86_64
|- bios.bin
run.sh
准备构建环境
Docker是一个用来固化你的构建环境的完美平台。因此,我们可以在buildenv
文件夹下准备一个Dockerfile
,来构建一个构建环境的镜像:
FROM rust:1.73-bullseye
RUN rustup target add x86_64-unknown-uefi
VOLUME /root/env
WORKDIR /root/env
Rust对UEFI目标有第二级支持,这意味着它不是官方支持的。但是,它对我们来说足够好了,可以写一个玩具操作系统。
感谢David Rheinsberg(@dvdhrm) 和 Nicholas Bishop(@nicholasbishop)维护了Rust的UEFI目标
构建镜像
docker build --platform linux/x86_64 buildenv -t tonyos-buildenv
准备一个最简单的内核
David Rheinsberg,UEFI目标的维护者,还维护了一个叫r-efi的crate,提供了UEFI的接口。我们可以使用这个crate来快速开始。
- 创建在构建容器中运行的构建脚本:buildscript.sh
#! /bin/bash
# build the kernel
cargo build --target x86_64-unknown-uefi || exit 1
# copy the built kernel to the dist directory
mkdir -p dist/x86_64/EFI/BOOT || exit 1
cp target/x86_64-unknown-uefi/debug/tonyos.efi \
dist/x86_64/EFI/BOOT/BOOTX64.EFI || exit 1
- 创建调用容器的构建脚本:build.sh
#! /bin/bash
docker run -it \
--platform linux/x86_64 \
--rm \
-v $(pwd):/root/env \
tonyos-buildenv \
./buildscript.sh
- 准备内核入口文件
src/main.rs
#![no_main]
#![no_std]
use uefi::prelude::*;
use uefi_services::*;
const HELLO_STR: &str = "Hello, world. Press any key to return to UEFI firmware.";
#[entry]
fn main(_handle: Handle, mut system_table: SystemTable<Boot>) -> Status {
uefi_services::init(&mut system_table).unwrap();
println!("{}", HELLO_STR);
let mut events = [ system_table.stdin().wait_for_key_event().unwrap() ];
system_table.boot_services().wait_for_event(&mut events).discard_errdata().unwrap();
Status::SUCCESS
}
- 准备
Cargo.toml
文件
[package]
name = "tonyos"
version = "0.1.0"
authors = ["Tony Huang <tony@tonyhuang.dev>"]
edition = "2021"
[build]
build-stage = 1
target = ["x86_64-unknown-uefi"]
[dependencies]
uefi = { version = "0.25", features = ["alloc"] }
uefi-services = "0.22"
# the profile used for `cargo build`
[profile.dev]
panic = "abort" # disable stack unwinding on panic
# the profile used for `cargo build --release`
[profile.release]
panic = "abort" # disable stack unwinding on panic
构建和运行你的第一个UEFI内核
现在,我们可以构建和运行我们的第一个UEFI内核了:
./build.sh && ./run.sh
延伸阅读
下一篇:获取关键硬件信息
修改历史
- 2023-10-24: 迁移到
uefi
crate