一、实验目的
中断、异常和陷阱指令(合称类中断)是操作系统的基石,现代操作系统就是由(类)中断驱动的。本实验的目的在于深刻理解(类)中断的原理和机制,掌握 CPU 访问设备控制器的方法,掌握 x86 体系结构的(类)中断机制和规范,实现时钟中断服务和部分异常处理等。
二、实验过程&错误
内容(一):实现 Breakpoint 异常的处理
步骤1:新建一个os并复制文件main.rs和vga_buffer.rs
步骤2:新建lib.rs文件并输入pub mod interrupts;
步骤3:新建interrupts.rs文件并输入如下代码
我们将首先在src/interrupts.rs中创建一个新的中断模块,该模块首先创建一个init_idtfunction,该函数创建一个新的InterruptDescriptorTable。
步骤4:创建简单的断点处理函数。在interrupts.rs文件中覆盖为如下代码
现在我们可以添加处理程序函数了。我们首先为断点异常添加一个处理程序。断点异常是测试异常处理的完美异常。它的唯一目的是在执行断点指令int 3时暂停程序。
断点异常通常用于调试器:当用户设置断点时,调试器用int 3指令覆盖相应的指令,以便CPU在到达该行时抛出断点异常。当用户想要继续该程序时,调试器再次用原始指令替换int 3指令,并继续该程序。
对于我们的用例,我们不需要覆盖任何指令。相反,我们只希望在执行断点指令时打印一条消息,然后继续该程序。
步骤5:加载IDT,将如下代码写入到interrupts.rs文件中原本init_idt函数的位置
为了CPU使用我们的新中断描述符表,我们需要使用lidT指令加载它。x86_64的中断描述结构为该结构提供了加载方法功能。
因此,加载方法需要一个“静态”,这是一个对程序的完整运行时有效的引用。原因是CPU将在每次中断上访问此表,直到我们加载不同的IDT。因此,使用比“静态”更短的生存期会导致使用后的错误。
事实上,这正是在这里发生的事。我们的IDT是在堆栈上创建的,因此它仅在init函数内部有效。然后,栈存储器用于其它功能,因此CPU将随机堆栈存储器解释为IDT。
静态MUTS很容易出现数据争用,因此我们需要每个访问上的一个不安全的块。
步骤6:缓慢的静态处理,将interrupts文件修改如下
步骤7:运行,将lib.rs文件和main.rs文件修改如下
问题7-1:编译出错,一直有一个错误说找不到std
但问题是我已经在main中禁用了std标准库,为什么没有用呢?经过加上–verbose查看后发现详细原因如下:
但问题在哪里一直没有解决,后来我决定,
解决方法7-1:直接将原来的第二次试验的基础上增加新的文件,而不是新建文件。
但还是不行
由此考虑不是在于我们的coml文件或者main文件的问题,而是在我们的lib文件和interrupts文件中有对std标准库函数的引用导致这种问题。发现了我们在interrupts文件中使用了println!函数,而这时std标准库中的函数,我们想要使用我们自己写的println函数,
解决方法7-2:将interrupts文件修改如下:
可以看到,我一方面是禁用了std标准库,另一方面引用了我自己写的vga_buffer库。但发现还是不行:
显示是没有办法找到vga_buffer库
解决方法7-3:尝试更新rust,输入rustup update
还是没有办法引用std标准库
真的不知道为什么。。。
解决方法7-4:将coml文件修改如下:
问题7-2:std库的错误解决了,但出现了一个新的问题:
在interrupts文件里,没有办法使用println宏
解决方法7-5:在主函数中加入这么一句,就可以使用文件夹中我们所写的宏
注意,不能直接pub mod vga_buffer,这样会引起很神奇的错误。
问题7-3:找不到我们定义的宏
解决方法7-6:这是因为我们在所有的文件中都没有引入vga_buffer这个我们自己写的库,所以没有办法调用我们写的println宏,这里需要将lib文件修改如下:
问题7-4:我们在调用x86-interrupt的abi的时候可能会发生奇怪的变化
解决方法7-7:这个与x86的中断机制有关系,这里我还没有找到原因,我们需要在lib文件里加上这么一条语句
此时,我们就解决了这个问题,但很正常的,我们又出现了三个问题。。。
问题7-5:似乎是我们需要写一个处理异常的代码
解决方法7-8:那我们就写一个
现象7-1:编译成功!yeah!
有一个警告,但一般来讲不用管他,我们开始cargo bootimage
现象7-2:成功!我们成功地解决了std库的问题并建立了我们的操作系统!
现象7-3:现在,我们开始运行
啊,太完美了,我都要哭出来了,实验内容一的基础部分成功了!
步骤8:进行测试,首先将lib文件进行修改,增加如下代码
记住,这个_start函数将会在运行cargo xtest的时候使用,因为Rust测试lib.rs完全独立于main.rs。在运行测试之前,我们需要在这里调用init来设置IDT。现在,我们可以创建一个test_interpoint_Exception测试:
步骤9:创建一个测试,将interrupts文件增加如下代码:
除了通过串口打印状态消息外,测试还调用int 3函数来触发断点异常。通过检查之后是否继续执行,我们验证我们的断点处理程序是否正常工作。我们可以通过运行Cargo xtest(所有测试)或Cargo xtest-lib(仅测试lib.rs及其模块)来尝试这个新测试。应该在输出中看到test_interpoint_Exception.[ok]。
步骤10,:使用cargo xtest进行全测试,在命令行中输入cargo xtest
问题10-1:说我们的test_case是在一个不稳定库里面调用的,我们的自定义测试框架不稳定。
他建议我们增加一条语句#![feature(custom_test_frameworks)],那我们就试一下。
解决方法10-1:在main文件和lib文件里增加#![feature(custom_test_frameworks)]语句
问题10-2:问题解决了,但我们又多了一个问题,我们找不到test
这是因为我们上一节没有进行,上一节中写了一个serial文件,包含了今天用到的serial_print和serial_println两个宏,我们需要学习上一节的知识,并将serial文件补全
解决方法10-2:编写serial文件并调用
但并没有完全解决,因为我们也要对vga_buffer文件进行修改,同时需要增加很多东西,包括一个名叫test文件夹和里面两个basic_boot.rs和should_panic.rs的文件。
最终的最终,得到的文件是这样的:
main文件:
#![no_std]
#![no_main]
#![feature(custom_test_frameworks)]
#![test_runner(junmo4_os::test_runner)]
#![reexport_test_harness_main = "test_main"]
use junmo4_os::println;
use core::panic::PanicInfo;
#[no_mangle]
pub extern "C" fn _start() -> ! {
println!("Hello World{}", "!");
junmo4_os::init();
// invoke a breakpoint exception
x86_64::instructions::interrupts::int3();
#[cfg(test)]
test_main();
println!("It did not crash!");
loop {
}
}
/// This function is called on panic.
#[cfg(not(test))]
#[panic_handler]
fn panic(info: &PanicInfo) -> ! {
println!("{}", info);
loop {
}
}
#[cfg(test)]
#[panic_handler]
fn panic(info: &PanicInfo) -> ! {
junmo4_os::test_panic_handler(info)
}
lib文件:
#![no_std]
#![cfg_attr(test, no_main)]
#![feature(custom_test_frameworks)]
#![feature(abi_x86_interrupt)]
#![test_runner(crate::test_runner)]
#![reexport_test_harness_main = "test_main"]
use core::panic::PanicInfo;
pub mod interrupts;
pub mod vga_buffer;
pub mod serial;
pub fn init() {
interrupts::init_idt();
}
pub fn test_runner(tests: &[&dyn Fn()]) {
serial_println!("Running {} tests", tests.len());
for test in tests {
test();
}
exit_qemu(QemuExitCode::Success);
}
pub fn test_panic_handler(info: &PanicInfo) -> ! {
serial_println!("[failed]\n");
serial_println!("Error: {}\n", info);
exit_qemu(QemuExitCode::Failed);
loop {
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u32)]
pub enum QemuExitCode {
Success = 0x10,
Failed = 0x11,
}
pub fn exit_qemu(exit_code: QemuExitCode) {
use x86_64::instructions::port::Port;
unsafe {
let mut port = Port::new(0xf4);
port.write(exit_code as u32);
}
}
/// Entry point for `cargo xtest`
#[cfg(test)]
#[no_mangle]
pub extern "C" fn _start() -> ! {
init();
test_main();
loop {
}
}
#[cfg(test)]
#[panic_handler]
fn panic(info: &PanicInfo) -> ! {
test_panic_handler(info)
}
interrupts文件:
#![cfg(not(windows))]
use crate::println;
use lazy_static::lazy_static;
use x86_64::structures::idt::{
InterruptDescriptorTable, InterruptStackFrame};
lazy_static! {
static ref IDT: InterruptDescriptorTable = {
let mut idt = InterruptDescriptorTable::new();
idt.breakpoint.set_handler_fn(breakpoint_handler);
idt
};
}
pub fn init_idt() {
IDT.load();
}
extern "x86-interrupt" fn breakpoint_handler(stack_frame: &mut InterruptStackFrame) {
println!("EXCEPTION: BREAKPOINT\n{:#?}", stack_frame);
}
#[cfg(test)]
use crate::{
serial_print, serial_println};
#[test_case]
fn test_breakpoint_exception() {
serial_print!("test_breakpoint_exception...");
// invoke a breakpoint exception
x86_64::instructions::interrupts::int3();
serial_println!("[ok]");
}
toml文件:
[package]
name = "junmo4_os"
version = "0.1.0"
authors = ["junmo"]
edition = "2018"
[profile.dev]
panic = "abort"
[profile.release]
panic = "abort"
[dependencies]
bootloader = "0.6.0"
volatile = "0.2.3"
spin = "0.4.9"
x86_64 = "0.7.5"#注意注意!!
uart_16550 = "0.2.0"#注意注意!!
[dependencies.lazy_static]
version = "1.0"
features = ["spin_no_std"]
vga_buffer文件:
use core::fmt;
use lazy_static::lazy_static;
use spin::Mutex;
use volatile::Volatile;
#[cfg(test)]
use crate::{
serial_print, serial_println};
lazy_static! {
pub static ref WRITER: Mutex<Writer> = Mutex::new(Writer {
column_position: 0,
color_code: ColorCode::new(Color::Yellow, Color::Black),
buffer: unsafe {
&mut *(0xb8000 as *mut Buffer) },
});
}
#[allow(dead_code)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u8)]
pub enum Color {
Black = 0,
Blue = 1,
Green = 2,
Cyan = 3,
Red = 4,
Magenta = 5,
Brown = 6,
LightGray = 7,
DarkGray = 8,
LightBlue = 9,
LightGreen = 10,
LightCyan = 11,
LightRed = 12,
Pink = 13,
Yellow = 14,
White = 15,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(transparent)]
struct ColorCode(u8);
impl ColorCode {
fn new(foreground: Color, background: Color) -> ColorCode {
ColorCode((background as u8) << 4 | (foreground as u8))
}
}
/// A screen character in the VGA text buffer, consisting of an ASCII character and a `ColorCode`.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(C)]
struct ScreenChar {
ascii_character: u8,
color_code: ColorCode,
}
/// The height of the text buffer (normally 25 lines).
const BUFFER_HEIGHT: usize = 25;
/// The width of the text buffer (normally 80 columns).
const BUFFER_WIDTH: usize = 80;
/// A structure representing the VGA text buffer.
#[repr(transparent)]
struct Buffer {
chars: [[Volatile<ScreenChar>; BUFFER_WIDTH]; BUFFER_HEIGHT],
}
/// A writer type that allows writing ASCII bytes and strings to an underlying `Buffer`.
///
/// Wraps lines at `BUFFER_WIDTH`. Supports newline characters and implements the
/// `core::fmt::Write` trait.
pub struct Writer {
column_position: usize,
color_code: ColorCode,
buffer: &'static mut Buffer,
}
impl Writer {
/// Writes an ASCII byte to the buffer.
///
/// Wraps lines at `BUFFER_WIDTH`. Supports the `\n` newline character.
pub fn write_byte(&mut self, byte: u8) {
match byte {
b'\n' => self.new_line(),
byte =>