在上一篇文章中我们简单介绍了UEFI的基本概念在本章中我们介绍uefi-rs
库的内存管理和文件系统使用
文章目录
基本结构
uefi-rs
中基本的结构已经画成脑图的形式
uefi-rs
中主要分为以下内容
- 信息类: 固件的信息,UEFI信息,uefi配置表
- 服务类: 在
uefi-rs
中主要包含运行时服务,启动服务,退出启动服务等 - Protocol(协议): 在
uefi-rs
中支持以下协议,所有的Protocol需要使用BootServer.local_protocol::<ProtocolName>();来获取(ProtocolName
表示Protocol名称,例如BootServer.local_protocol::<Input>())(以上都是伪代码)- 标准输入
Input
- 标准输出
Output
- 图形输出协议
GOP
(Grahpics Output Protocol) - 串行IO设备访问
Serial
- 调试服务
DebugSupport
- 镜像加载
LoadImage
- 文件系统
SimpleFileSystem
- 多处理器服务
MPService
- 标准输入
- 其他服务: 内存分配等
UEFI的HelloWorld!
我们创建好项目后再Cargo.toml
中添加如下内容
[dependencies.uefi]
version="0.4.0"
features = ['exts']
[dependencies.uefi-services]
version = "0.2.0"
features = ["qemu"]
[dependencies.log]
version = "0.4.8"
然后我们在main.rs
中添加几个feature
// main.rs
#![no_std]
#![no_main]
#![feature(asm)]
#![feature(abi_efiapi)] // uefi-rs使用的是efi调用约定
#![feature(never_type)]
extern crate uefi;
然后定义UEFI的入口函数
#[entry]
fn efi_main(image: Handle, st: SystemTable<Boot>) -> Status{}
其中#[entry]
在uefi-marcos
中定义主要功能是将我们定义的函数转化为pub extern "efiapi" fn eif_main
这样的形式
程序进入main函数后的第一件事情就是uefi服务进入DXE
(Driver Execution Environment)阶段
#[entry]
fn efi_main(image: Handle, st: SystemTable<Boot>) -> Status{
if let Err(e) = uefi_services::init(&st).log_warning(){
info!("{:?}",e);
// 如果服务初始化失败后则不能继续执行
return e.status();
}
info!("Hello World!");
Status::SUCCESS
}
我们编写完毕代码后再项目的根目录(与src
目录同级)创建.cargo
文件夹,随后创建config
文件并填写一下内容(rust已经支持了x86_64-unknown-uefi
编译目标)
# build settings
[build]
target = "x86_64-unknown-uefi"
这样我们在运行时不需要指定--target
参数,否则每次运行时需要添加,例如
cargo xbuild --target x86_64-unknown-uefi
Cargo代理
如果连接到cargo.io
比较慢可以添加国内代理
//in .cargo/config
[source.crates-io]
registry = "https://github.com/rust-lang/crates.io-index"
replace-with = 'ustc'
[source.ustc]
registry = "http://mirrors.ustc.edu.cn/crates.io-index"
这样当我们运行cargo xbuild
后会在target/debug
目录中找到xxx.efi
文件(xxx表示起的项目名称)
使用QEMU启动
当编译完毕后我们需要创建一个目录esp/EFI/Boot
然后将我们编译的xxx.efi
命名成BootX64.efi
目录结构如下
project
├── .cargo
├── src
├── target
├── Cargo.toml
├── Cargo.lock
└──esp
└── EFI
└── Boot
└──BootX64.efi
esp
目录是我们需要挂载的文件目录
因为我们使用的是虚拟机,我们需要使用OVMF
(开放虚拟机固件Open Virtual Machine Firmware)OVMF的制作请参考使用Rust开发操作系统(UEFI基本介绍)的OVMF制作章节,然后为QEMU指定OVMF_CODE.fd
路径,OVMF_VARS.fd
的路径,以及我们创建的esp
文件夹路径
例如(为了方便阅读对命令进行了换行)
qemu-system-x86_64
-drive if=pflash,format=raw,file=<OVMF_CODE.fd的路径>,readonly=on
-drive if=pflash,format=raw,file=<OVMF_VARS.fd的路径>,readonly=on
-drive format=raw,file=fat:rw:<esp文件路径>
以下是参考命令(windows)
qemu-system-x86_64
-drive if=pflash,format=raw,file=C:\Users\VenmoSnake\Desktop\ReboxOS\OVMF_CODE.fd,readonly=on
-drive if=pflash,format=raw,file=C:\Users\VenmoSnake\Desktop\OS\OVMF_VARS.fd,readonly=on
-drive format=raw,file=fat:rw:C:\Users\VenmoSnake\Desktop\OS\esp
这样我们可以在QEMU上运行我们编写的efi
基本的数据结构
Result
pub type Result<Output = (), ErrData = ()> = core::result::Result<Completion<Output>, Error<ErrData>>;
在uefi-rs
中Result的定义有些许不同而且使用方式跟通常用法也不同,因此我们要具体说明一下
我们可以看到一个名叫Completion
的数据结构其定义如下
#[must_use]
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Completion<T> {
status: Status,
result: T,
}
Completion
用于表示UEFI服务操作完成后的结果,但在此过程中可能会遇到一些非致命问题,其中的Status
表示执行的结果状态,定义如下
newtype_enum! {
#[must_use]
pub enum Status: usize => {
/// 操作执行成果
SUCCESS = 0,
/// 该字符串包含无法呈显示的字符,并被跳过。 UEFI使用的是UCS-2编码
WARN_UNKNOWN_GLYPH = 1,
/// handle已关闭,但未删除文件。
WARN_DELETE_FAILURE = 2,
/// handle已关闭,但文件内容没有正确刷新。
WARN_WRITE_FAILURE = 3,
/// 指定的缓冲区太小,数据被截断。
WARN_BUFFER_TOO_SMALL = 4,
/// The data has not been updated within the timeframe set by local policy.
/// 数据尚没有在指定的时间范围内更新。(时间范围在本地策略中设置)
WARN_STALE_DATA = 5,
/// 结果缓冲区包含符合UEFI的文件系统。
WARN_FILE_SYSTEM = 6,
/// 系统重置后将处理该操作。
WARN_RESET_REQUIRED = 7,
/// 镜像读取失败
LOAD_ERROR = ERROR_BIT | 1,
/// 参数不正确
INVALID_PARAMETER = ERROR_BIT | 2,
/// 不支持的操作
UNSUPPORTED = ERROR_BIT | 3,
/// 缓冲区的大小不符合要求。
BAD_BUFFER_SIZE = ERROR_BIT | 4,
/// 缓冲区的大小不足以容纳请求的数据。或者表示在参数中应该返回所需的缓冲区大小。
BUFFER_TOO_SMALL = ERROR_BIT | 5,
/// 没有数据返回。
NOT_READY = ERROR_BIT | 6,
/// 物理设备在尝试操作时