操作系统原理实验(3):操作系统的基石中断与异常

一、实验目的

中断、异常和陷阱指令(合称类中断)是操作系统的基石,现代操作系统就是由(类)中断驱动的。本实验的目的在于深刻理解(类)中断的原理和机制,掌握 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 =>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值