在上一篇文章中我们介绍了rust在uefi中的基本使用,在本章中我们开始编写UEFI基础设施,主要包括File结构和uefi编译脚本用来简化内核文件读取和qemu启动过程
建立基础异常
在标准库中为我们提供了Result,但是在UEFI开发中将Result分成了2种,UEFI服务执行的Result和用户自定义的Result,在本节中我们仿照标准库的io::Result建立我们自己的Result
设计思路
我们设计的Result除了满足我们程序基本的使用以外还要兼容UEFI服务的Result因此我们可以通过type来重新命令2种Result,其次我们要兼容UEFI服务的错误,但是还是要区分UEFI异常和UEFI应用异常,因此我们需要设计一个枚举Repr来区分两种异常
根据Result我们可以轻松定义出如下结构
1
2
3
4
5
6 1// 表示UEFI服务使用的Result
2pub type UefiResult<T> = uefi::Result<T>;
3// 表示UEFI应用程序使用的Result
4pub type Result<T> = core::result::Result<T, Error>;
5
6
第二个Result的Error为我们自定义的Error,Error分为2种UEFI执行中的错误以及应用程序自定义的错误,因此我们可以使用一个枚举来完成
1
2
3
4
5
6
7
8
9
10 1// 用于表示异常的种类,分为UEFI执行错误和UEFI应用程序错误
2#[derive(Debug)]
3enum Repr {
4 /// UEFI服务错误
5 Uefi(Efi),
6 /// UEFI应用错误
7 Custom(Custom),
8}
9
10
在Repr中包含2个结构体Efi和Custom结构定义如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34 1#[derive(Debug, Clone)]
2struct Custom {
3 /// UEFI应用执行错误类型
4 kind: ErrorKind,
5 /// UEFI应用执行错误说明
6 err_msg: String,
7}
8
9#[derive(Debug, Clone)]
10struct Efi {
11 /// UEFI服务执行结果状态码
12 status: Status,
13 /// UEFI状态对应说明,如果为None使用默认的提示信息,
14 err_msg: Option<String>,
15}
16
17// 用于表示UEFI应用异常类型
18#[derive(Clone, Copy, Debug, PartialOrd, PartialEq)]
19pub enum ErrorKind {
20 /// 文件未找到
21 NotFound,
22 /// 在读取文件中读取到了EOF
23 UnexpectedEof,
24 /// 无效的文件
25 InvalidFile,
26 /// 无效的文件模式
27 InvalidFileMode,
28 /// UEFI错误,包含错误码
29 UefiErrorCode,
30 /// 终止
31 Interrupted,
32}
33
34
Custom和Efi主要的不同在于kind为我们自定义的错误类型,Status表示UEFI服务执行错误的状态码(Status其实是Rust与C的枚举的映射,但是uefi-rs中不是简单地与C枚举映射因为会产生UB(undefined behaviour),而是采取了newtype_enum宏进行处理)
紧接着我们为2种结构提供as_str()方法,ErrorKind的实现比较简单,以下的部分代码
1
2
3
4
5
6
7
8
9
10
11 1impl ErrorKind {
2 pub fn as_str(&self) -> &'static str {
3 match *self {
4 ErrorKind::NotFound => "entity not found",
5 ErrorKind::UnexpectedEof => "unexpected end of file",
6 ....
7 }
8 }
9}
10
11
但是实现Efi结构的as_str是有些不同了,我们不能写出这样的代码
1
2
3
4
5
6
7
8
9
10 1impl Efi {
2 pub fn as_str(&self) -> &'static str {
3 match self.status {
4 Status:: WARN_UNKNOWN_GLYPH => "The string contained characters that could not be rendered and were skipped."
5 .....
6 }
7 }
8}
9
10
在上面我们也提到了,如果简单的看源码Status确实像枚举,但是其实是结构体,因此我们要判断为Status中具体的值
以Status:: WARN_UNKNOWN_GLYPH为例,经过宏扩展后结果为pub struct WARN_UNKNOWN_GLYPH(pub usize),因此我们要以结构体的思路来处理
考虑到完整性列出了所有Status的结果
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53 1const ERROR_BIT: usize = 1 << (core::mem::size_of::<usize>() * 8 - 1);
2
3
4impl Efi {
5 pub fn as_str(&self) -> &'static str {
6 match self.status.0 {
7 0 => "The operation completed successfully.",
8 1 => "The string contained characters that could not be rendered and were skipped.",
9 2 => "The handle was closed, but the file was not deleted.",
10 3 => "The handle was closed, but the data to the file was not flushed properly.",
11 4 => "The resulting buffer was too small, and the data was truncated.",
12 5 => "The data has not been updated within the timeframe set by local policy.",
13 6 => "The resulting buffer contains UEFI-compliant file system.",
14 7 => "The operation will be processed across a system reset.",
15 ERROR_BIT | 1 => "The image failed to load.",
16 ERROR_BIT | 2 => "A parameter was incorrect.",
17 ERROR_BIT | 3 => "The operation is not supported.",
18 ERROR_BIT | 4 => "The buffer was not the proper size for the request.The buffer is not large enough to hold the requested data.",
19 ERROR_BIT | 5 => "The required buffer size is returned in the appropriate parameter.",
20 ERROR_BIT | 6 => "There is no data pending upon return.",
21 ERROR_BIT | 7 => "The physical device reported an error while attempting the operation.",
22 ERROR_BIT | 8 => "The device cannot be written to.",
23 ERROR_BIT | 9 => "A resource has run out.",
24 ERROR_BIT | 10 => "An inconstency was detected on the file system.",
25 ERROR_BIT | 11 => "There is no more space on the file system.",
26 ERROR_BIT | 12 => "The device does not contain any medium to perform the operation.",
27 ERROR_BIT | 13 => "The medium in the device has changed since the last access.",
28 ERROR_BIT | 14 => "The item was not found.",
29 ERROR_BIT | 15 => "Access was denied.",
30 ERROR_BIT | 16 => "The server was not found or did not respond to the request.",
31 ERROR_BIT | 17 => "A mapping to a device does not exist.",
32 ERROR_BIT | 18 => "The timeout time expired.",
33 ERROR_BIT | 19 => "The protocol has not been started.",
34 ERROR_BIT | 20 => "The protocol has already been started.",
35 ERROR_BIT | 21 => "The operation was aborted.",
36 ERROR_BIT | 22 => "An ICMP error occurred during the network operation.",
37 ERROR_BIT | 23 => "A TFTP error occurred during the network operation.",
38 ERROR_BIT | 24 => "A protocol error occurred during the network operation. The function encountered an internal version that was",
39 ERROR_BIT | 25 => "incompatible with a version requested by the caller.",
40 ERROR_BIT | 26 => "The function was not performed due to a security violation.",
41 ERROR_BIT | 27 => "A CRC error was detected.",
42 ERROR_BIT | 28 => "Beginning or end of media was reached",
43 ERROR_BIT | 31 => "The end of the file was reached.",
44 ERROR_BIT | 32 => "The language specified was invalid.The security status of the data is unknown or compromised and",
45 ERROR_BIT | 33 => "the data must be updated or replaced to restore a valid security status.",
46 ERROR_BIT | 34 => "There is an address conflict address allocation",
47 ERROR_BIT | 35 => "A HTTP error occurred during the network operation.",
48 _ => "Unknown status"
49 }
50 }
51}
52
53
ERROR_BIT定义在uefi-rs/src/result/status.rs中该结构并不是pub的,我们因此我们在我们自己的文件中重新定义了该结构(只用作读取不会用作其他用途)
这样我们可以定义出Error的结构
1
2
3
4
5
6 1#[derive(Debug)]
2pub struct Error {
3 repr: Repr,
4}
5
6
最后我们提供Error配套的方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31 1impl Error {
2 /// 根据给定的错误类型创建UEFI应用异常
3 pub fn new(kind: ErrorKind, msg: &str) -> Error {
4 Error { repr: Repr::Custom(Custom { kind, err_msg: msg.to_string() }) }
5 }
6 /// 根据给定的错误类型创建UEFI应用异常 支持String 主要方便使用format!
7 pub fn with_string(kind: ErrorKind, msg: String) -> Error {
8 Error { repr: Repr::Custom(Custom { kind, err_msg: msg }) }
9 }
10 /// 根据传递的状态码创建UEFI服务错误
11 pub fn from_uefi_status(status: Status, msg: Option<&str>) -> Error {
12 Error {
13 repr: Repr::Uefi(Efi {
14 status,
15 err_msg: match msg {
16 Some(msg) => Some(msg.to_string()),
17 None => None,
18 },
19 })
20 }
21 }
22 /// 提供错误类型的判断
23 pub fn kind(&self) -> ErrorKind {
24 match self.repr {
25 Repr::Uefi(ref efi) => ErrorKind::UefiErrorCode,
26 Repr::Custom(ref cu) => cu.kind,
27 }
28 }
29}
30
31
为了发生错误时能够显示错误信息我们要为Error实现Displaytrait
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 1impl fmt::Display for Error {
2 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
3 match self.repr {
4 Repr::Custom(ref cu) => {
5 write!(f, "{}: {}", cu.kind.as_str(), cu.err_msg)
6 }
7 Repr::Uefi(ref efi) => {
8 match efi.err_msg {
9 None => write!(f, "got uefi status `{}` info: {}", efi.status.0, efi.as_str()),
10 Some(ref other) => write!(f, "got uefi status `{}` info: {}", efi.status.0, other),
11 }
12 }
13 }
14 }
15}
16
17
经过修改后就不能使用以前的Ok(T)和Err(T)的(有一种思路就是将UefiResult和Result和并,这样可以使照常使用,未经过测试可行性)
1
2
3
4
5
6
7
8
9 1pub fn ok<T>(t: T) -> UefiResult<Result<T>> {
2 Ok(Completion::from(core::result::Result::Ok(t)))
3}
4
5pub fn err<T>(e: Error) -> UefiResult<Result<T>> {
6 Ok(Completion::from(core::result::Result::Err(e)))
7}
8
9
这样我们我们Result设计就完成了
封装文件操作
文件的操作我们可以参考标准库,将文件的操作抽象成3个trait来实现分别是Read,Write,Seek
read主要提供文件读取操作,write主要提供文件写入操作,Seek提供文件指针移动操作
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21 1pub trait Seek {
2 /// 指定文件/读取写入位置
3 fn seek(&mut self, pos: SeekFrom) -> Result<()>;
4}
5
6pub trait Read {
7 /// 读取尽可能多的文件数据并填充到`but`中,返回读取的字节数
8 /// #Error
9 /// 如果指定的缓冲区过小则返回`BUFFER_TOO_SMALL`并返回所需要的`size`
10 fn read(&mut self, buf: &mut [u8]) -> Result<usize>;
11}
12
13pub trait Write {
14 /// 将`buf`中的数据写入到文件中,返回写入的字节数
15 /// 在写入的过程发生错误会返回已写入的字节数
16 fn write(&mut self, buf: &[u8]) -> usize;
17 /// 将所有写入的数据刷新到设备
18 fn flush(&mut self) -> Result<()>;
19}
20
21
我们定义出了每个trait基本的功能,后续我们会丰富我们的trait功能
在结构方面我们需要将文件操作和文件读写操作分离主要原因是uefi-rs提供的open方法也适用于文件夹操作,因此File主要用于文件夹和文件的操作,FileOperator主要完成文件读写操作,File定义如下
1
2
3
4
5
6
7
8
9 1/// 适用于文件和文件夹
2pub struct File {
3 /// 根目录
4 root: Directory,
5 /// 缓冲区用于存储已打开目录信息
6 buffer: Vec<u8>,
7}
8
9
File的new函数只需要从BootServices中获取SimpleFileSystemProtocol并且读取打开根目录即可
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 1impl File {
2 /// new函数和try_new函数的辅助操作
3 fn _new(bt: &BootServices) -> UefiResult<Self> {
4 // 获取SimpleFileSystemProtocol
5 let f = unsafe { &mut *bt.locate_protocol::<SimpleFileSystem>().log_warning()?.get() };
6 // 打开根目录
7 let mut volume = f.open_volume().log_warning()?;
8 Ok(Completion::from(File {
9 root: volume,
10 buffer: vec![0_u8; DEFAULT_BUFFER_SIZE],
11 }))
12 }
13 }
14
15
这里的_new函数只是一个辅助操作,因为我们要提供new和try_new2个函数定义如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 1impl File {
2 /// 根据BootServices创建File实例,
3 /// # Panic
4 /// 当尝试读取根目录失败后,文件系统出错,设备驱动等错误均会导致panic
5 pub fn new(bt: &BootServices) -> Self {
6 match File::try_new(bt) {
7 Ok(f) => f,
8 Err(e) => {
9 panic!("occur an error during open root folder! {}", e);
10 }
11 }
12 }
13 /// new的方法
14 pub fn try_new(bt: &BootServices) -> Result<Self> {
15 match Self::_new(bt).log_warning() {
16 Ok(f) => Ok(f),
17 Err(e) => Err(Error::from_uefi_status(e.status(), None)),
18 }
19 }
20}
21
22
经过try_new函数后我们将底层的uefi服务错误转为uefi应用错误,这样我们不需要处理层层嵌套的Result
当然,我们也可以提供可以指定的缓冲区大小的函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21 1impl File {
2 /// with_buffer_capacity的辅助函数
3 fn capacity(bt: &BootServices, size: usize) -> UefiResult<Self> {
4 let f = unsafe { &mut *bt.locate_protocol::<SimpleFileSystem>().log_warning()?.get() };
5 let volume = f.open_volume().log_warning()?;
6 Ok(Completion::from(File {
7 root: volume,
8 buffer: vec![0_u8; size],
9 }))
10 }
11
12 /// 指定缓冲区容量大小
13 pub fn with_buffer_capacity(bt: &BootServices, size: usize) -> Result<Self> {
14 match Self::capacity(bt, size).log_warning() {
15 Ok(f) => Ok(f),
16 Err(e) => Err(Error::from_uefi_status(e.status(), None))
17 }
18 }
19 }
20
21
打开指定文件
open函数较为复杂,因此需要3个辅助函数来完成,这三个辅助将要完成以下操作
- 读取根目录中的文件信息FileInfo
- 获取文件的属性FileAttribute
- 根据指定的文件模式和文件属性打开指定路径的文件
首先我们完成第一步: 读取根目录中的文件信息FileInfo
1
2
3
4
5
6
7
8
9
10
11
12
13 1impl File{
2 /// 读取根目录信息
3 fn read_entry(&mut self) -> Result<&mut FileInfo> {
4 return match self.root.read_entry(self.buffer.as_mut_slice()).log_warning() {
5 Ok(info) => {
6 if let Some(f) = info { Ok(f) } else { Err(Error::new(ErrorKind::NotFound, "the file info header not found!")) }
7 }
8 Err(e) => Err(Error::from_uefi_status(e.status(), None))
9 };
10 }
11}
12
13
第二步:获取文件的属性FileAttribute
1
2
3
4
5
6
7
8
9
10
11 1impl File{
2 /// 读取根目录属性
3 fn root_attribute(&mut self) -> Result<FileAttribute> {
4 match self.read_entry() {
5 Ok(info) => Ok(info.attribute()),
6 Err(e) => Err(e),
7 }
8 }
9}
10
11
第三步:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27 1impl File{
2 fn _open(&mut self, filename: &str, mode: FileMode, mut attr: FileAttribute) -> UefiResult<Result<FileOperator>> {
3 // 如果指定的模式为写模式需要更改属性值
4 if let FileMode::CreateReadWrite = mode {
5 attr = FileAttribute::VALID_ATTR;
6 }
7
8 return match self.root.open(filename, mode, attr).log_warning() {
9 Ok(handle) => {
10 // 这里只处理文件的情况,如果指定的是文件夹则返回ErrorKind::InvalidFile
11 match handle.into_type().log_warning()? {
12 FileType::Dir(_) => {
13 return err(Error::new(ErrorKind::InvalidFile, "except file found folder, if you want create folder please use `mkdir` method if you want read folder please use `walk` method"));
14 }
15 FileType::Regular(file) => {
16 ok(FileOperator { file, current: 0 })
17 }
18 }
19 }
20 Err(e) => {
21 err(Error::from_uefi_status(e.status(), None))
22 }
23 };
24 }
25}
26
27
最后我们将open函数定义出来
1
2
3
4
5
6
7
8
9
10
11
12
13 1 pub fn open(&mut self, filename: &str, mode: &str) -> UefiResult<Result<FileOperator>> {
2 // 获取文件属性
3 let attr = self.root_attribute().unwrap();
4 let f_mode = match mode {
5 "r" => FileMode::Read,
6 "w" => FileMode::ReadWrite,
7 "c" => FileMode::CreateReadWrite,
8 other => return err(Error::new(ErrorKind::InvalidFileMode, format!("No Support mode: `{}`", other.clone()).as_str())),
9 };
10 self._open(filename, f_mode, attr)
11 }
12
13
值得注意的是root_attribute,open和_open函数中的内容并不能写在一个函数中,编译器会提示一次可变借用,一次不可变借用,以及FileInfo not implement copy trait等错误
我们注意到open的返回值是FileOperator它的定义如下
1
2
3
4
5
6
7
8 1pub struct FileOperator {
2 /// 已文件实例
3 file: RegularFile,
4 /// 文件指针位置
5 current: u64,
6}
7
8
FileOperator将会实现Read,Write,Seek等trait,实现较为简单,这里不做过多阐述
Seek
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28 1/// 将此文件句柄的游标的位置设置为指定的绝对位置。
2/// 允许使用`End`将游标设置超过文件末尾的位置,它将在下次写入时触发文件增长。
3///
4/// * `SeekFrom::Start(size)` 将游标移至文件起始位置`size`个字节处
5/// * `SeekFrom::End(size)` 将游标移至设置为此对象的大小加上指定的`size`个字节处
6/// * `SeekFrom::Current(size)` 将游标移至设置为当前位置加上指定的`size`个字节处
7#[derive(Copy, Clone, Debug, Eq, PartialEq)]
8pub enum SeekFrom {
9 Start(u64),
10 End(u64),
11 Current(u64),
12}
13
14impl Seek for FileOperator {
15 fn seek(&mut self, pos: SeekFrom) -> Result<()> {
16 let result = match pos {
17 SeekFrom::Start(p) => self.file.set_position(p).log_warning(),
18 SeekFrom::End(p) => self.file.set_position(RegularFile::END_OF_FILE + p).log_warning(),
19 SeekFrom::Current(p) => self.file.set_position(self.current + p).log_warning(),
20 };
21 match result {
22 Ok(_) => Ok(()),
23 Err(e) => Err(Error::from_uefi_status(e.status(), None))
24 }
25 }
26}
27
28
Read
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 1impl Read for FileOperator {
2 fn read(&mut self, buf: &mut [u8]) -> Result<usize> {
3 match self.file.read(buf).log_warning() {
4 Ok(size) => {
5 self.current += size as u64;
6 Ok(size)
7 }
8 Err(e) => {
9 match e.data() {
10 Some(size) => Err(Error::from_uefi_status(e.status(), Some(format!("buffer to small need {}", size).as_str()))),
11 None => Err(Error::from_uefi_status(e.status(), None))
12 }
13 }
14 }
15 }
16}
17
18
Write
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 1impl Write for FileOperator {
2 fn write(&mut self, buf: &[u8]) -> usize {
3 match self.file.write(buf).log_warning() {
4 Ok(_) => buf.len(),
5 Err(size) => *size.data()
6 }
7 }
8
9 fn flush(&mut self) -> Result<()> {
10 match self.file.flush().log_warning() {
11 Ok(()) => Ok(()),
12 Err(e) => Err(Error::from_uefi_status(e.status(), None))
13 }
14 }
15}
16
17
这些主要复用SimpleFileSystem提供的方法,我们主要的关注点是设计并提供便利的API,我们首先来设计读取的api
扩充Read trait
read_exact主要读取指定的字节数并填充到给定的切片中,以下是read_exact流程图
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25 1/// 读取指定字节数
2/// 将读取到的数据填充至`buf`中
3fn read_exact(&mut self, mut buf: &mut [u8]) -> Result<()> {
4 // buf是否填充完毕?
5 while !buf.is_empty() {
6 // 读取并填充数据
7 match self.read(buf){
8 Ok(0) => break,
9 Ok(n) => {
10 let tmp = buf;
11 buf = &mut tmp[n..];
12 }
13 Err(e) => return Err(e),
14 }
15 }
16 // buf是否为空?
17 if !buf.is_empty() {
18 Err(Error::new(ErrorKind::UnexpectedEof, "failed to fill whole buffer"))
19 } else {
20 Ok(())
21 }
22}
23
24
25
read_to_end读取所有字节,直到碰到EOF,将读取到的数据填充至buf中,以下是read_to_end的流程图
read_to_end中的难点便是扩容操作,扩容操作总共执行2个步骤
- 使用GlobalAllocator申请新的内存空间
- 将对新申请的内存空间进行初始化(全部置0)
其次我们需要记录每次读取的字节数,以便准确表达缓冲区大小,在扩容时我们只会关注与缓冲区的容量而非大小,但是在用户使用时只关注缓冲区的大小(即从文件中读取的字节数)
因此我们需要一个新的数据结构来完成这个操作,它将完成2个操作
- 保存读取的内容并记录每次读取的字节数
- 在读取完毕后设置缓冲区大小
我们可以得到这样的结构
1
2
3
4
5
6 1struct Guard<'a> {
2 buf: &'a mut Vec<u8>,
3 len: usize,
4}
5
6
Guard将会完成以上2个操作,设置缓冲区大小的时机应该是Guard生命周期结束的时候,因此在Guard被Drop前需要设置缓冲区大小,因此我们可以重写Drop trait
1
2
3
4
5
6
7
8
9 1impl Drop for Guard<'_> {
2 fn drop(&mut self) {
3 unsafe {
4 self.buf.set_len(self.len);
5 }
6 }
7}
8
9
我们现在还有一个扩容问题需要解决,在alloc库中已经为Vec实现了扩容的函数reserve,在扩容后需要重新设置缓冲区的容量
随后将新申请的内存空间初始化,因此我们还需要一个用于初始化的数据结构Initializer,定义如下
1
2
3
4 1#[derive(Debug)]
2pub struct Initializer(bool);
3
4
其中的bool值表示是否需要初始化操作,然后我们需要提供两个初始化函数,zero和nop,zero函数用于表示对内存进行初始化,nop表示不需要对内存进行初始化,因此对于nop函数而言是unsafe操作
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 1impl Initializer {
2 /// 表明需要对缓冲区进行初始化操作
3 #[inline]
4 pub fn zeroing() -> Initializer {
5 Initializer(true)
6 }
7 /// 表明不会对缓冲区进行初始化操作
8 #[inline]
9 pub unsafe fn nop() -> Initializer {
10 Initializer(false)
11 }
12 /// 表示缓冲区是否应该被初始化
13 #[inline]
14 pub fn should_initialize(&self) -> bool {
15 self.0
16 }
17}
18
19
最后我们实现初始化功能initialize
1
2
3
4
5
6
7
8
9 1 /// 如果需要的话会始化缓冲区(根据缓冲区长度将值设为0)
2 #[inline]
3 pub fn initialize(&self, buf: &mut [u8]) {
4 if self.should_initialize() {
5 unsafe { ptr::write_bytes(buf.as_mut_ptr(), 0, buf.len()) }
6 }
7 }
8
9
初始化功能较为简单,在这里不在赘述
那么我们的扩容操作可以这样编写,以下是伪代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 1let r = Initializer::zeroing();
2let mut g = 初始化 Guard;
3if 缓冲区容量不足 {
4 // 进行扩容
5 g.buf.reserve(需要扩充的字节数);
6 // 获得扩容后的缓冲区最大容量
7 let capacity = g.buf.capacity();
8 unsafe {
9 // 设置缓冲区容量
10 g.buf.set_len(capacity);
11 // 初始化缓冲区新扩容的内存 只需要初始化新增的内存
12 r.initializer().initialize(&mut g.buf[g.len..]);
13 }
14}
15
16
这里缓冲区容量不足的判断条件便是 Guard中缓冲区的长度等于缓冲区的长度
相应的我们每次读取的时候只需要将数据读取套新增的内存中,因此我们读取操作可以写成这样
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21 1let start_len = 初始缓冲区大小
2// 将数据读取至扩容部分
3let r = FileOperator实例
4let ret:Option<usize>;
5// 只需要读取新增的内存
6match r.read(&mut g.buf[g.len..]){
7 // 没有读取到数据
8 Ok(0) => {
9 // 读取的内存大小 = 读取后的大小 - 读取前的大小
10 ret = Ok(g.len - start_len);
11 break;
12 }
13 // 记录每次读取的字节数
14 Ok(n) => g.len += n,
15 Err(e) => {
16 ret = Err(e);
17 break;
18 }
19}
20
21
这样我们可以形成read_to_end_with_reservation函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41 1fn read_to_end_with_reservation<R, F>(r: &mut R, buf: &mut Vec<u8>, mut reservation_size: F) -> Result<usize>
2 where R: Read + ?Sized, F: FnMut(&R) -> usize
3{
4 let start_len = buf.len();
5 let mut g = Guard { len: buf.len(), buf };
6 let ret:Result<usize>;
7 loop {
8 // 缓冲区的长度等于缓冲区的长度时需要进行扩容
9 if g.len == g.buf.len() {
10 // 进行扩容
11 g.buf.reserve(reservation_size(r));
12 // 获得扩容后的缓冲区最大容量
13 let capacity = g.buf.capacity();
14 unsafe {
15 // 设置缓冲区容量
16 g.buf.set_len(capacity);
17 // 初始化缓冲区新扩容的内存 只需要初始化新增的内存
18 r.initializer().initialize(&mut g.buf[g.len..]);
19 }
20 }
21 // 将数据读取至扩容部分
22 match r.read(&mut g.buf[g.len..]){
23 // 没有读取到数据
24 Ok(0) => {
25 // // 读取的内存大小 = 读取后的大小 - 读取前的大小
26 ret = Ok(g.len - start_len);
27 break;
28 }
29 // 记录每次读取的字节数
30 Ok(n) => g.len += n,
31 Err(e) => {
32 ret = Err(e);
33 break;
34 }
35 }
36 }
37
38 ret
39}
40
41
read_to_end_with_reservation是read_to_end的辅助函数其中泛型参数F: FnMut(&R)-> usize是用来指定扩容的大小
相应的我们的Readtrait中需要增加初始化的函数(包含之前编写的read_exact函数)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33 1pub trait Read {
2 /// 读取尽可能多的文件数据并填充到`but`中,返回读取的字节数
3 /// #Error
4 /// 如果指定的缓冲区过小则返回`BUFFER_TOO_SMALL`并返回所需要的`size`
5 fn read(&mut self, buf: &mut [u8]) -> Result<usize>;
6
7 /// 读取指定字节数
8 /// 将读取到的数据填充至`buf`中
9 fn read_exact(&mut self, mut buf: &mut [u8]) -> Result<()> {
10 while !buf.is_empty() {
11 match self.read(buf) {
12 Ok(0) => break,
13 Ok(n) => {
14 let tmp = buf;
15 buf = &mut tmp[n..];
16 }
17 Err(e) => return Err(e),
18 }
19 }
20 if !buf.is_empty() {
21 Err(Error::new(ErrorKind::UnexpectedEof, "failed to fill whole buffer"))
22 } else {
23 Ok(())
24 }
25 }
26
27 #[inline]
28 unsafe fn initializer(&self) -> Initializer {
29 Initializer::zeroing()
30 }
31}
32
33
然后我们可以定义出read_to_end函数了
1
2
3
4
5 1fn read_to_end<R: Read + ?Sized>(r: &mut R, buf: &mut Vec<u8>) -> Result<usize> {
2 read_to_end_with_reservation(r, buf, |_| 32)
3}
4
5
之后再Read trait中添加read_to_end函数
1
2
3
4
5
6
7
8
9 1pub trait Read {
2 ....
3 fn read_to_end(&mut self, buf: &mut Vec<u8>) -> Result<usize> {
4 read_to_end(self, buf)
5 }
6 ...
7}
8
9
扩充 Write trait
Write trait中我们暂时只提供一个write_all函数已经够用了,暂时在uefi环境先不会做过多的复杂操作
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 1pub trait Write {
2 fn write_all(&mut self, mut buf: &[u8]) -> Result<()> {
3 while !buf.is_empty() {
4 match self.write(buf) {
5 0 => {
6 return Err(Error::new(ErrorKind::WriteZero, "failed to write whole buffer"));
7 }
8 n => buf = &buf[n..],
9 }
10 }
11 Ok(())
12 }
13}
14
15
write_all函数的实现比较简单,循环入即可
如果大家读过std::io的代码的话就知道其实这是官方库的修改版本适用于我们当前的uefi环境
简化Qemu启动
随着我们要编译的内容越来越多,我们需要使用更加自动化的方式来完成,基本思路是依靠rust提供的std::use std::process::Command功能,以内核编译举例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 1
2fn main() -> std::io::Result<()> {
3 // 获取当前项目路径
4 let work_dir = Path::new(env!("CARGO_MANIFEST_DIR"));
5 // 1. build kernel
6 check_status(kernel_build_step(work_dir)?);
7}
8// 执行 cargo xbuild --release命令
9pub fn kernel_build_step(path: &Path) -> std::io::Result<ExitStatus> {
10 Command::new("cargo")
11 .current_dir(path.join("kernel"))
12 .arg("xbuild")
13 .arg("--release").status()
14}
15fn check_status(status: ExitStatus) {
16 if !status.success() {
17 println!("status is not succeed: {}", status);
18 exit(1);
19 }
20}
21
22
如法炮制我们可以得到uefi编译的函数
1
2
3
4
5
6
7
8
9
10
11 1// 执行 cargo xbuild --package uefis
2pub fn efi_build_step(path: &Path) -> std::io::Result<ExitStatus> {
3 Command::new("cargo")
4 .current_dir(path.join("uefis"))
5 .arg("xbuild")
6 .args(&["--package", "uefis"])
7 .arg("--release")
8 .status()
9}
10
11
然后我们需要建立模拟esp分区,我们将其放到targetr/debug文件夹中即可,基本的过程如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25 1pub fn copy_file(path: &Path) -> std::io::Result<()> {
2 //构建内核路径 $WORK_DIR/kernel/target/x86-64/debug/kernel
3 let src_kernel_path = path.join(r"kernel\target\x86-64\debug\kernel");
4 //构建efi文件路径 $WORK_DIR/uefis/target/x86_64-unknown-uefi/debug/uefis.efi
5 let uefi_path = path.join(r"uefis\target\x86_64-unknown-uefi\debug\uefis.efi");
6 // 构建uefi启动目录 $WORK_DIR/target/debug/esp/EFI/Boot
7 let dest_path = path.join(r"target\debug\esp\EFI\Boot");
8
9 // 创建esp/EFI/Boot目录
10 std::fs::create_dir_all(&dest_path)?;
11
12 // 复制efi文件
13 let efi_file_path = dest_path.join("BootX64.efi");
14 File::create(&efi_file_path)?;
15 std::fs::copy(uefi_path, efi_file_path)?;
16
17 // 复制内核文件
18 let dest_kernel_path = dest_path.join("kernel");
19 File::create(&dest_kernel_path)?;
20 std::fs::copy(src_kernel_path, dest_kernel_path)?;
21
22 Ok(())
23}
24
25
注意path的join函数会直接添加到路径中听不会根据不相同的系统来做对应的处理,因此在linux系统的路径分隔符需要反过来
最后我们将qemu启动的参数写成脚本的形式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35 1pub fn run_qemu(path: &Path) {
2 // 拼接OVMF_CODE.fd命令
3 let p_code = format!("if=pflash,format=raw,file={},readonly=on", path.join("OVMF_CODE.fd").to_str().unwrap());
4 // 拼接OVMF_VARS.fd命令
5 let p_vars = format!("if=pflash,format=raw,file={},readonly=on", path.join("OVMF_VARS.fd").to_str().unwrap());
6 // 拼接挂在esp分区的命令
7 let p_esp = format!("format=raw,file=fat:rw:{}", path.join("target\\debug\\esp").to_str().unwrap());
8 // 启动qemu
9 let process = Command::new("qemu-system-x86_64.exe").stdout(Stdio::piped())
10 .args(&[
11 "-drive", p_code.as_str(),
12 "-drive", p_vars.as_str(),
13 "-drive", p_esp.as_str(),
14 "-serial", "stdio",
15 "-device", "isa-debug-exit,iobase=0xf4,iosize=0x04",
16 "-debugcon", "file:debug.log",
17 "-s",
18// "-S"
19// "-global", "isa-debugcon.iobase=0x402"
20 ])
21 .spawn().unwrap();
22 // 循环读取stdout输出的数据(qemu将会把模拟环境的输出重定向到本机的stdout中)
23 let mut line = String::new();
24 if let Some(out) = process.stdout {
25 let mut reader = BufReader::new(out);
26 while let Ok(size) = reader.read_line(&mut line) {
27 if size == 0 {
28 break;
29 }
30 println!("{}", line);
31 }
32 }
33}
34
35
最后我们的启动脚本就编写好了
1
2
3
4
5
6
7
8
9
10
11
12
13 1fn main() -> std::io::Result<()> {
2 // 获取当前项目路径
3 let work_dir = Path::new(env!("CARGO_MANIFEST_DIR"));
4 // 1. build kernel
5 check_status(kernel_build_step(work_dir)?);
6 // 2. build efi file
7 check_status(efi_build_step(work_dir)?);
8 // 3. copy file
9 copy_file(work_dir)?;
10 run_qemu(work_dir);
11}
12
13
最后我们的项目结构如下
1
2
3
4
5
6
7
8 1OperatingSystem
2├── kernel
3├── uefi
4├── target
5└── src
6 └── main.rs
7
8
下一步要做什么?
在下一篇文章中我们要认识并解析elf文件,因为我们的内核编译后便是elf文件,有了本章中提供的便捷api,解析elf文件时会更加方便