使用Rust开发操作系统(UEFI抽象文件系统)

释放双眼,带上耳机,听听看~!

在上一篇文章中我们介绍了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个步骤

  1. 使用GlobalAllocator申请新的内存空间
  2. 将对新申请的内存空间进行初始化(全部置0)

其次我们需要记录每次读取的字节数,以便准确表达缓冲区大小,在扩容时我们只会关注与缓冲区的容量而非大小,但是在用户使用时只关注缓冲区的大小(即从文件中读取的字节数)
因此我们需要一个新的数据结构来完成这个操作,它将完成2个操作

  1. 保存读取的内容并记录每次读取的字节数
  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文件时会更加方便

给TA打赏
共{{data.count}}人
人已打赏
安全技术

c++ list, vector, map, set 区别与用法比较

2022-1-11 12:36:11

气候事件

台风洛坦即将登陆 民航出行暂不影响

2011-7-29 12:57:33

个人中心
购物车
优惠劵
今日签到
有新私信 私信列表
搜索