- 实验要求
- 实验过程
- 关键代码
- 实验结果
- 实验总结
- 思考题&加分项
- 了解现代操作系统(Windows)的启动过程,UEFI 和 Legacy(BIOS)的区别是什么?
- 尝试解释 Makefile 中的命令做了哪些事情?
- 利用 cargo 的包管理和 docs.rs 的文档,我们可以很方便的使用第三方库。这些库的源代码在哪里?它们是什么时候被编译的?
- 为什么我们需要使用
#entry
而不是直接使用main
函数作为程序的入口? - 基于控制行颜色的 Rust 编程题目,参考
log crate
的文档,为不同的日志级别输出不同的颜色效果,并进行测试输出。 - 基于第一个 Rust 编程题目,实现一个简单的 shell 程序
- 尝试使用线程模型,基于 UniqueId 的任务
实验要求
-
配置好实验环境,熟悉实验环境的使用方法。
-
尝试使用rust进行编程,熟悉rust的基本语法。
-
运行UEFI Shell,熟悉UEFI Shell的基本命令。
-
启动YSOS
实验过程
实验环境配置
我选择使用虚拟机进行实验,虚拟机的系统为Ubuntu22.04,与推荐实验环境一致。我在虚拟机中更新了apt源并进行软件升级,然后安装了qemu,rustup,vscode等实验所需的环境,同时完成了相关的验证工作。
Rust编程
-
实现了一系列函数: count_down, read_and_print, file_size, 并且调用它们进行了测试,得到了预期的输出。
-
实现了一个进行字节数转换的函数, 并通过了测试。
-
使用现有的crate在终端中输出彩色的文字。
-
利用enum类型实现了一个名为Shape的枚举类型,其中包含了Circle和Rectangle两种类型, 并且实现了一个计算面积的函数,其通过了测试。
-
实现了UniqueID类型, 并且通过了测试。
UEFI Shell
我使用git clone命令下载了所需的仓库,并且借助其初始化了自己的仓库,其中文件完整。接着在简单了解UEFI Shell的功能后,使用命令启动UEFI Shell,其输出与预期一致。
YSOS
根据仓库提供的.toml文件指定的Rust工具链,在项目根目录下运行make run命令,得到输出与预期一致,成功启动了YSOS。
关键代码
Rust编程
编程任务1:
// 创建一个函数进行倒计时
fn count_down(seconds: u64) {
for i in (1..=seconds).rev() {
println!("Remaining seconds: {}", i);
std::thread::sleep(std::time::Duration::from_secs(1));// 这行代码的作用是让当前线程休眠一秒钟。
}
println!("Countdown finished!");
}
// 创建一个函数尝试读取并输出文件的内容
fn read_and_print(file_path: &str) {
match File::open(file_path) {
Ok(mut file) => {
let mut contents = String::new();
file.read_to_string(&mut contents).expect("Failed to read file");
println!("{}", contents);
}
Err(_) => {
panic!("File not found!");
}
}
}
// 创建一个函数尝试获取文件大小,并处理可能的错误
fn file_size(file_path: &str) -> Result<u64, &str> {
match std::fs::metadata(file_path) {
Ok(metadata) => Ok(metadata.len()),
Err(_) => Err("File not found!"),
}
}
编程任务2:
fn humanized_size(size: u64) -> (f64, &'static str) {
const KIB: f64 = 1024.0;
const MIB: f64 = KIB * KIB;
const GIB: f64 = KIB * MIB;
if size < KIB as u64 {
(size as f64, "B")
} else if size < MIB as u64 {
(size as f64 / KIB, "KiB")
} else if size < GIB as u64 {
(size as f64 / MIB, "MiB")
} else {
(size as f64 / GIB, "GiB")
}
}
编程任务3:
use colored::Colorize;
fn main() {
// 输出绿色的 INFO
println!("{} Hello, world!", "INFO:".green());
// 输出黄色、加粗、下划线的 WARNING
println!("{}", "WARNING: I'm a teapot!".yellow().bold().underline());
// 输出红色、加粗的 ERROR,并尝试在控制台窗口居中
let error_message = "KERNEL PANIC!!!";
let padding = (80 - error_message.len()) / 2;
println!("{:-^80}", ""); // 打印横线,用于居中
println!("{:width$}{}", "", error_message.red().bold(), width = padding);
println!("{:-^80}", ""); // 打印横线,用于居中
}
编程任务4:
use std::f64::consts::PI;
enum Shape {
Rectangle { width: f64, height: f64 },
Circle { radius: f64 },
}
impl Shape {
pub fn area(&self) -> f64 {
match self {
Shape::Rectangle { width, height } => width * height,
Shape::Circle { radius } => PI * radius * radius,
}
}
}
编程任务5:
struct UniqueId(u16);
impl UniqueId {
fn new() -> UniqueId {
static mut NEXT_ID: u16 = 0;
unsafe {
let id = NEXT_ID;
NEXT_ID += 1;
UniqueId(id)
}
}
}
实验结果
实验环境配置
版本与实验推荐一致
Rust编程
编程任务1:
编程任务2:
编程任务3:
编程任务4:
编程任务5:
UEFI Shell
校验文件完整性:
使用QEMU启动UEFI Shell:
YSOS
YSOS启动:
实验总结
在这个实验中,我首先配置了实验环境,确保所需的工具和库已经安装,并且进行了相关的验证工作。随后,我使用Rust进行编程,实现了一系列任务,包括倒计时、文件操作、字节数转换、彩色输出、枚举类型实现面积计算、以及唯一标识生成。在编程过程中,我熟悉了Rust 的基本语法和一些常见的文件和系统操作。
在实现彩色输出时,我使用了 colored crate来实现不同颜色的文字输出,通过这个过程我了解了如何在终端中添加颜色和样式。
在 UEFI Shell 的部分,我克隆了仓库,通过简单的命令了解了 UEFI Shell的基本功能,并启动了 YSOS 系统,验证了环境配置的正确性。
思考题&加分项
了解现代操作系统(Windows)的启动过程,UEFI 和 Legacy(BIOS)的区别是什么?
答:UEFI 是一种新型的固件接口,它是 BIOS 的替代品。UEFI 与传统的 BIOS相比,具有启动速度快、支持大容量硬盘、支持多分区启动、支持网络启动、支持图形界面等优点。UEFI启动过程中,会加载 UEFI Shell,然后通过 UEFI Shell加载操作系统内核。而传统的 BIOS 启动过程中,会加载 MBR,然后通过 MBR 加载操作系统内核。
尝试解释 Makefile 中的命令做了哪些事情?
答:
-
run: 构建项目并启动 QEMU 虚拟机运行UEFI程序。
-
launch:启动 QEMU 虚拟机运行UEFI程序。
-
intdbg:启动 QEMU 虚拟机并启用 GDB 调试。
-
debug:启动 QEMU 虚拟机并启用 GDB 调试。
-
clean:清理项目构建产物。
-
build:构建项目。
利用 cargo 的包管理和 docs.rs 的文档,我们可以很方便的使用第三方库。这些库的源代码在哪里?它们是什么时候被编译的?
答:这些库的源代码通常托管在版本控制系统。而它们是在我们使用 cargo build 或 cargo run 命令时被编译的。
为什么我们需要使用 #entry
而不是直接使用 main
函数作为程序的入口?
答:使用 #entry
而不是 main 是为了适应裸机和嵌入式环境的特殊需求,让程序入口更加灵活。
基于控制行颜色的 Rust 编程题目,参考 log crate
的文档,为不同的日志级别输出不同的颜色效果,并进行测试输出。
答:日志包括 Fatal, Error, Warn, Info, Debug, Trace 等级别,我选择其中的 Info,Warn,Error 三个级别进行测试。
use colored::*;
use log::{info, warn, error};// 已在toml里添加log依赖
fn main() {
env_logger::init();
// 记录不同级别的日志并应用颜色效果
info!("{} This is an info message.", "INFO:".green());
warn!("{} This is a warning message.", "WARNING:".yellow().bold().underline());
error!("{} This is an error message.", "ERROR:".red().bold());
}
输出展示:
基于第一个 Rust 编程题目,实现一个简单的 shell 程序
答: 关键代码:
fn cd_command(target: &str) {
let current_dir = env::current_dir().expect("Failed to get current directory");
let mut new_path = current_dir;
for component in target.split('/') {
if component == ".." {
new_path.pop();
} else {
new_path.push(component);
}
}
env::set_current_dir(&new_path).expect("Failed to change directory");
}
fn ls_command() {
let current_dir = env::current_dir().expect("Failed to get current directory");
let entries = current_dir.read_dir().expect("Failed to read directory entries");
for entry in entries {
if let Ok(entry) = entry {
let path = entry.path();
let metadata = entry.metadata().unwrap();
println!(
"{}\t{:>10} bytes\tCreated: {:?}",
path.display(),
metadata.len(),
metadata.created().unwrap()
);
}
}
}
fn cat_command(file_path: &str) {
if let Ok(file) = File::open(file_path) {
let reader = BufReader::new(file);
for line in reader.lines() {
if let Ok(line) = line {
println!("{}", line);
}
}
} else {
eprintln!("Error: Failed to open file '{}'", file_path);
}
}
输出展示:
尝试使用线程模型,基于 UniqueId 的任务
在 Rust 中,使用 static mut 变量在多线程环境下是不安全的,因为多个线程可能同时访问和修改这个变量,导致数据竞争。
而AtomicU16 是一个用于原子操作的无符号 16 位整数类型。在多线程编程中,原子类型是为了避免数据竞争而设计的,提供了一组原子性的操作,确保在多线程环境中进行安全的共享数据修改。
unsafe:它的诞生主要是因为Rust的静态检查太强了,也有一部分是因为计算机底层的一些硬件本身就是不安全的,比如说指针操作,内存操作等等。unsafe的作用就是告诉编译器,这里的代码我知道是不安全的,但是我保证这里的代码是安全的,你不用检查了。
当然,它本身就如它的名字一样是不安全的,所以在使用的时候一定要小心,不要滥用。