Rust-oslab-02: 中断和异常处理
一、实验完成情况
完成实验任务:
对于QEMU部分,,在FrameBuffer中对于串口输出的处理、命令解释器help
, clear
, reboot
, exit
内部指令,对于外部指令目前输出unexpected command
。支持退格指令。
测试效果:
对于串口输出部分,实现了串口中断处理函数,实现time
, who r u
内部指令,支持退格指令。
测试效果:
二、主要代码介绍
串口中断处理部分
此部分的代码是仿照已有的时钟中断和键盘中断处理流程,增加串口中断。
首先是CPU, 中断处理器(PIC),设备三者均打开中断:
CPU,中断处理器(PIC):
在kernel/src/driver/pic.rs
文件的init
函数中打开,我们参照pic8259的手册通过PICS.lock().write_masks()均设置为0,打开PIC的中断:
pub fn init() {
unsafe { PICS.lock().initialize() };
// PIC开启中断
unsafe {
let mask1 = 0b0000_0000;
let mask2 = 0b0000_0000;
PICS.lock().write_masks(mask1, mask2);
}
// CPU端开启中断
x86_64::instructions::interrupts::enable();
}
设备打开中断:
在kernel/src/driver/idt.rs
中的IDT中添加idt[InterruptIndex::Com1 as usize].set_handler_fn(com1_interrupt_handler)
;
在kernel/src/driver/idt.rs
中添加函数extern "x86-interrupt" fn com1_interrupt_handler
:
【为什么要设置extern "x86-interrupt"
?:】
【中断处理函数的执行时间是不确定的,所以我们在运行任意一条指令时均有可能产生中断从而导致寄存器的状态是不可预测的,我们需要保存当时寄存器的状态。因为无论是caller还是callee,我们都需要保存一部分寄存器,所以我们在无法判断是caller还是callee的时候,我们会选择都进行保存寄存器的状态,方便之后进行恢复状态。】
其中receive()函数是参照了kernel/src/driver/serial.rs
的实现,需要引入use crate::driver::serial::receive
。
extern "x86-interrupt" fn com1_interrupt_handler(_stack_frame: InterruptStackFrame) {
let chasr = receive();
serial_shell_input(chasr as char);
notify_eoi(InterruptIndex::Com1 as u8);
()
}
QEMU命令解释器
此处首先将原有的键盘输出的部分转到命令解释器函数中,shell_input(character)
。
接下来是函数的编写:
实现内部指令并返回相应的结果,对于clear的操作我们调用了fb.rs
文件中的clear()函数,我们调用的全局变量FRAME_BUFFER是加上锁了的,所以我们需要在调用的时候加上get()
和and_then()
用来调用clear()函数,实现了拿到锁之后的变量安全。
pub fn shell_input(_chr: char) {
// 增加字符解析功能,实现以下功能
let _HELP: [char; BUFFER_SIZE] = ['h', 'e', 'l', 'p', '\n', '\0', '\0', '\0'];
let _CLEAR: [char; BUFFER_SIZE] = ['c', 'l', 'e', 'a', 'r', '\n', '\0', '\0'];
let _EXIT: [char; BUFFER_SIZE] = ['e', 'x', 'i', 't', '\n', '\0', '\0', '\0'];
let _REBOOT: [char; BUFFER_SIZE] = ['r', 'e', 'b', 'o', 'o', 't', '\n', '\0'];
let mut Flag: usize = 0;
unsafe {
if (_chr as u8 == 8) {
if (EBK1 == 0) {
EBK1 = 0;
} else {
EBK1 = EBK1 - 1;
}
fb_print!("{}", _chr);
FRIENDLY1[EBK1 + 1] = '\0';
FRIENDLY1[EBK1 + 2] = '\0';
} else {
fb_print!("{}", _chr);
FRIENDLY1[EBK1] = _chr;
EBK1 = EBK1 + 1;
}
}
unsafe {
if (_chr == '\n' || EBK1 >= 8) {
Flag = 0;
EBK1 = 0;
for _x in 0..EBK1 + 1 {
if (FRIENDLY1[_x] == _HELP[_x]) {
if (_x == EBK1) {
Flag = 1;
fb_println!("Try commands:");
fb_println!(" help");
fb_println!(" clear");
fb_println!(" reboot");
fb_print!(" exit (qemu only)");
}
} else {
break;
}
}
for _x in 0..EBK1 + 1 {
if (FRIENDLY1[_x] == _CLEAR[_x]) {
if (_x == EBK1) {
Flag = 1;
// 调用FrameBufferDriver中的clear()
crate::driver::fb::FRAME_BUFFER
.get()
.and_then(|fb| Some(fb.lock().clear()))
.expect("Fail clear");
}
} else {
break;
}
}
for _x in 0..EBK1 + 1 {
if (FRIENDLY1[_x] == _EXIT[_x]) {
if (_x == EBK1) {
Flag = 1;
exit_qemu(QemuExitCode::Success);
}
}
}
for _x in 0..EBK1 + 1 {
if (FRIENDLY1[_x] == _REBOOT[_x]) {
if (_x == EBK1) {
Flag = 1;
core::arch::asm!(
"cli
mov al, 0xfe
out 0x64, al"
);
}
} else {
break;
}
}
if (Flag == 0) {
fb_print!("Error: unexpected command.");
}
fb_print!("\n[FrameBuffer Shell]: >>> ");
}
}
}
串口部分命令解释器
对于串口命令解释器部分,我们也是仿照以上的命令解释器进行编写,实现的命令有time
,who r u
指令,对于time
我们是输出当前的时钟中断数,对于who r u
指令我们要输出当前目录的用户。
具体实现如下:
pub fn serial_shell_input(_chr: char) {
// 增加字符解析功能,实现以下功能
let _TIME: [char; BUFFER_SIZE] = ['t', 'i', 'm', 'e', '\n', '\0', '\0', '\0'];
let _WHORU: [char; BUFFER_SIZE] = ['w', 'h', 'o', ' ', 'r', ' ', 'u', '\n'];
let mut Flag1: usize = 0;
unsafe {
if (_chr as u8 == 8) {
if (EBK2 == 0) {
EBK2 = 0;
serial_print!(" ");
} else {
EBK2 = EBK2 - 1;
}
serial_print!("{}", _chr);
FRIENDLY2[EBK2 + 1] = '\0';
FRIENDLY2[EBK2 + 2] = '\0';
} else {
serial_print!("{}", _chr);
FRIENDLY2[EBK2] = _chr;
EBK2 = EBK2 + 1;
}
}
unsafe {
if (_chr as usize == 13 || EBK2 >= 8) {
serial_println!();
Flag1 = 0;
EBK2 = 0;
for _x in 0..EBK2 + 1 {
if (FRIENDLY2[_x] == _TIME[_x]) {
if (_x == EBK2) {
Flag1 = 1;
serial_print!(" 当前时钟中断数: {}", crate::driver::idt::CNT);
}
} else {
break;
}
}
for _x in 0..EBK2 + 1 {
if (FRIENDLY2[_x] == _WHORU[_x]) {
if (_x == EBK2) {
Flag1 = 1;
serial_print!("🥰SArB!🥰");
}
} else {
break;
}
}
if (Flag1 == 0) {
serial_print!("Error: unexpected command.");
}
serial_print!("\n[😻 Shell]: >>> ");
}
}
}
删格操作
对于删格的操作,除了在以上命令解释器中的操作以外,我们还对fb.rs
中读入到删格字符的时候进行处理,通过对于FrameBuffer中的buffer数组修改原有的位置清空。
fn write_char(&mut self, c: char) {
match c {
'\n' => self.newline(),
'\t' => self.write_str(" "),
c => {
// 分别判断x、y坐标是否越界
let new_xpos = self.cur_x_pos + LETTER_WIDTH;
if new_xpos >= self.width {
self.newline();
}
let new_ypos = self.cur_y_pos + LETTER_HEIGHT.val() + BORDER_PADDING;
if new_ypos >= self.height {
self.clear();
}
// 渲染字符
if (c as u8 == 8) {
self.cur_x_pos -= LETTER_WIDTH;
for x in 0..9 {
for y in 0..16 {
self.write_pixel(self.cur_x_pos + x, (self.cur_y_pos + y), 0);
}
}
} else {
self.render_char(c);
}
}
}
}
附录(源码等)
src/driver/shell.rs
//! 格式化输出至framebuffer,部分参考bootloader实现
use bootloader_api::info::FrameBuffer;
use core::fmt;
use core::fmt::Write;
use noto_sans_mono_bitmap::{get_raster, get_raster_width, FontWeight, RasterHeight};
use spin::{Mutex, Once};
use x86_64::instructions::interrupts;
/// FrameBuffer驱动全局变量
pub static FRAME_BUFFER: Once<Mutex<FrameBufferDriver>> = Once::new();
/// 初始化FrameBuffer驱动
pub fn init<'a>(frame_buffer: &'a mut FrameBuffer) {
serial_println!("[Kernel] {:#x?}", frame_buffer);
// 根据BootInfo传入信息创建FrameBuffer驱动
FRAME_BUFFER.call_once(|| Mutex::new(FrameBufferDriver::new(frame_buffer)));
}
/// 辅助打印结构,主要实现Write trait
struct Printer;
impl Write for Printer {
fn write_str(&mut self, s: &str) -> fmt::Result {
interrupts::without_interrupts(|| {
FRAME_BUFFER
.get()
.and_then(|fb| Some(fb.lock().write_str(s)))
.expect("Uninit frame buffer");
});
Ok(())
}
}
/// 打印至FrameBuffer
pub fn print(args: fmt::Arguments) {
// write_fmt函数在Write trait中定义,因此需要实现Write trait
Printer.write_fmt(args).unwrap();
}
/// 格式化打印至FrameBuffer(无换行)
#[macro_export]
macro_rules! fb_print {
($($arg:tt)*) => {
$crate::driver::fb::print(format_args!($($arg)*))
};
}
/// 格式化打印至FrameBuffer(有换行)
#[macro_export]
macro_rules! fb_println {
() => ($crate::fb_print!("\n"));
($fmt:expr) => ($crate::fb_print!(concat!($fmt, "\n")));
($fmt:expr, $($arg:tt)*) => ($crate::fb_print!(
concat!($fmt, "\n"), $($arg)*));
}
// 以下代码参考bootloader实现
/// 行间距
const LINE_SPACING: usize = 2;
/// 字间距
const LETTER_SPACING: usize = 0;
/// 页边距
const BORDER_PADDING: usize = 1;
/// 字高
const LETTER_HEIGHT: RasterHeight = RasterHeight::Size16;
/// 字宽
const LETTER_WIDTH: usize = get_raster_width(FontWeight::Regular, LETTER_HEIGHT);
/// 字体
const FONT_WEIGHT: FontWeight = FontWeight::Regular;
/// FrameBuffer驱动
pub struct FrameBufferDriver {
buffer: &'static mut [u8],
bytes_per_pixel: usize,
height: usize,
width: usize,
cur_x_pos: usize,
cur_y_pos: usize,
}
impl FrameBufferDriver {
/// 创建FrameBuffer驱动
pub fn new<'a>(framebuffer: &'a mut FrameBuffer) -> Self {
let info = framebuffer.info();
let buffer_start = framebuffer.buffer().as_ptr() as usize;
let buffer_len = info.byte_len;
let mut fb = Self {
buffer: unsafe { core::slice::from_raw_parts_mut(buffer_start as *mut u8, buffer_len) },
bytes_per_pixel: info.bytes_per_pixel,
height: info.height,
width: info.width,
cur_x_pos: 0,
cur_y_pos: 0,
};
fb.clear();
fb
}
// 换行
fn newline(&mut self) {
self.cur_x_pos = BORDER_PADDING;
self.cur_y_pos += LETTER_HEIGHT.val() + LINE_SPACING;
}
/// 清屏
pub fn clear(&mut self) {
self.cur_x_pos = BORDER_PADDING;
self.cur_y_pos = BORDER_PADDING;
self.buffer.fill(0);
}
/// 写字符串
fn write_str(&mut self, s: &str) {
for byte in s.chars() {
self.write_char(byte);
}
}
/// 写字符
fn write_char(&mut self, c: char) {
match c {
'\n' => self.newline(),
'\t' => self.write_str(" "),
c => {
// 分别判断x、y坐标是否越界
let new_xpos = self.cur_x_pos + LETTER_WIDTH;
if new_xpos >= self.width {
self.newline();
}
let new_ypos = self.cur_y_pos + LETTER_HEIGHT.val() + BORDER_PADDING;
if new_ypos >= self.height {
self.clear();
}
// 渲染字符
if (c as u8 == 8) {
self.cur_x_pos -= LETTER_WIDTH;
for x in 0..9 {
for y in 0..16 {
self.write_pixel(self.cur_x_pos + x, (self.cur_y_pos + y), 0);
}
}
} else {
self.render_char(c);
}
}
}
}
/// 退格
pub fn delete(&mut self) {
self.cur_x_pos -= LETTER_WIDTH;
}
/// 渲染字符
fn render_char(&mut self, c: char) {
let rendered_char =
get_raster(c, FONT_WEIGHT, LETTER_HEIGHT).expect("Failed to render a char");
for (y, row) in rendered_char.raster().iter().enumerate() {
for (x, byte) in row.iter().enumerate() {
self.write_pixel(self.cur_x_pos + x, self.cur_y_pos + y, *byte);
}
}
self.cur_x_pos += rendered_char.width() + LETTER_SPACING;
}
/// 写像素
fn write_pixel(&mut self, x: usize, y: usize, intensity: u8) {
let pixel_offset = y * self.width + x;
let color = [intensity / 2, intensity, intensity, 0];
let bytes_per_pixel = self.bytes_per_pixel;
let byte_offset = pixel_offset * bytes_per_pixel;
self.buffer[byte_offset..(byte_offset + bytes_per_pixel)]
.copy_from_slice(&color[..bytes_per_pixel]);
}
}
kernel/src/driver/shell.rs
use crate::test::{exit_qemu, QemuExitCode};
const BUFFER_SIZE: usize = 8;
static mut FRIENDLY1: [char; BUFFER_SIZE] = ['\0'; BUFFER_SIZE];
static mut FRIENDLY2: [char; BUFFER_SIZE] = ['\0'; BUFFER_SIZE];
static mut EBK1: usize = 0;
static mut EBK2: usize = 0;
/// 处理键盘输入
pub fn shell_input(_chr: char) {
// TODO: 增加字符解析功能,实现以下功能
/*
1. 输入help+回车,显示帮助信息
2. 输入clear+回车,清空屏幕显示
3. 输入exit+回车,退出qemu终端
4. 输入reboot+回车,重启终端
5. 输入其他未定义命令+回车,弹出错误提示信息
6. 其他功能:每行命令输出前有提示前缀;可以回退删除打错的字符
*/
let _HELP: [char; BUFFER_SIZE] = ['h', 'e', 'l', 'p', '\n', '\0', '\0', '\0'];
let _CLEAR: [char; BUFFER_SIZE] = ['c', 'l', 'e', 'a', 'r', '\n', '\0', '\0'];
let _EXIT: [char; BUFFER_SIZE] = ['e', 'x', 'i', 't', '\n', '\0', '\0', '\0'];
let _REBOOT: [char; BUFFER_SIZE] = ['r', 'e', 'b', 'o', 'o', 't', '\n', '\0'];
let mut Flag: usize = 0;
unsafe {
if (_chr as u8 == 8) {
if (EBK1 == 0) {
EBK1 = 0;
} else {
EBK1 = EBK1 - 1;
}
fb_print!("{}", _chr);
FRIENDLY1[EBK1 + 1] = '\0';
FRIENDLY1[EBK1 + 2] = '\0';
} else {
fb_print!("{}", _chr);
FRIENDLY1[EBK1] = _chr;
EBK1 = EBK1 + 1;
}
}
unsafe {
if (_chr == '\n' || EBK1 >= 8) {
Flag = 0;
EBK1 = 0;
for _x in 0..EBK1 + 1 {
if (FRIENDLY1[_x] == _HELP[_x]) {
if (_x == EBK1) {
Flag = 1;
fb_println!("Try commands:");
fb_println!(" help");
fb_println!(" clear");
fb_println!(" reboot");
fb_print!(" exit (qemu only)");
}
} else {
break;
}
}
for _x in 0..EBK1 + 1 {
if (FRIENDLY1[_x] == _CLEAR[_x]) {
if (_x == EBK1) {
Flag = 1;
// 调用FrameBufferDriver中的clear()
crate::driver::fb::FRAME_BUFFER
.get()
.and_then(|fb| Some(fb.lock().clear()))
.expect("Fail Clear");
}
} else {
break;
}
}
for _x in 0..EBK1 + 1 {
if (FRIENDLY1[_x] == _EXIT[_x]) {
if (_x == EBK1) {
Flag = 1;
exit_qemu(QemuExitCode::Success);
}
}
}
for _x in 0..EBK1 + 1 {
if (FRIENDLY1[_x] == _REBOOT[_x]) {
if (_x == EBK1) {
Flag = 1;
core::arch::asm!(
"cli
mov al, 0xfe
out 0x64, al"
);
}
} else {
break;
}
}
if (Flag == 0) {
fb_print!("Error: unexpected command.");
}
fb_print!("\n[FrameBuffer Shell]: >>> ");
}
}
}
/// 解析串口输入
pub fn serial_shell_input(_chr: char) {
// TODO: 增加字符解析功能,实现以下功能
/*
1. 输入time+回车,显示系统目前时钟中断次数
2. 输入其他未定义命令+回车,弹出错误提示信息
3. 其他功能:每行命令输出前有提示前缀;可以回退删除打错的字符
*/
let _TIME: [char; BUFFER_SIZE] = ['t', 'i', 'm', 'e', '\n', '\0', '\0', '\0'];
let _WHORU: [char; BUFFER_SIZE] = ['w', 'h', 'o', ' ', 'r', ' ', 'u', '\n'];
let mut Flag1: usize = 0;
unsafe {
if (_chr as u8 == 8) {
if (EBK2 == 0) {
EBK2 = 0;
serial_print!(" ");
} else {
EBK2 = EBK2 - 1;
}
serial_print!("{}", _chr);
FRIENDLY2[EBK2 + 1] = '\0';
FRIENDLY2[EBK2 + 2] = '\0';
} else {
serial_print!("{}", _chr);
FRIENDLY2[EBK2] = _chr;
EBK2 = EBK2 + 1;
}
}
unsafe {
if (_chr as usize == 13 || EBK2 >= 8) {
serial_println!();
Flag1 = 0;
EBK2 = 0;
for _x in 0..EBK2 + 1 {
if (FRIENDLY2[_x] == _TIME[_x]) {
if (_x == EBK2) {
Flag1 = 1;
serial_print!(" 当前时钟中断数: {}", crate::driver::idt::CNT);
}
} else {
break;
}
}
for _x in 0..EBK2 + 1 {
if (FRIENDLY2[_x] == _WHORU[_x]) {
if (_x == EBK2) {
Flag1 = 1;
serial_print!("🥰SArB!🥰");
}
} else {
break;
}
}
if (Flag1 == 0) {
serial_print!("Error: unexpected command.");
}
serial_print!("\n[😻 Shell]: >>> ");
}
}
}
kernel/src/driver/idt.rs
//! 中断描述符表模块
use crate::driver::pic::{notify_eoi, InterruptIndex};
use crate::driver::serial::receive;
use crate::driver::shell::{serial_shell_input, shell_input};
use pc_keyboard::{layouts, DecodedKey, HandleControl, Keyboard, ScancodeSet1};
use spin::Lazy;
//use spin::{Mutex, Once};
use x86_64::structures::idt::{InterruptDescriptorTable, InterruptStackFrame};
//pub static CNT: Once<Mutex<usize>> = Once::new();
pub static mut CNT: usize = 0;
static IDT: Lazy<InterruptDescriptorTable> = Lazy::new(|| {
let mut idt = InterruptDescriptorTable::new();
idt.breakpoint.set_handler_fn(breakpoint_handler);
idt.double_fault.set_handler_fn(double_fault_handler);
idt[InterruptIndex::Timer as usize].set_handler_fn(timer_interrupt_handler);
idt[InterruptIndex::Keyboard as usize].set_handler_fn(keyboard_interrupt_handler);
idt[InterruptIndex::Com1 as usize].set_handler_fn(com1_interrupt_handler);
idt
});
/// 初始化中断描述符表
pub fn init() {
IDT.load();
}
extern "x86-interrupt" fn breakpoint_handler(stack_frame: InterruptStackFrame) {
fb_println!("EXCEPTION: BREAKPOINT\n{:#?}", stack_frame);
}
extern "x86-interrupt" fn double_fault_handler(
stack_frame: InterruptStackFrame,
_error_code: u64,
) -> ! {
panic!("EXCEPTION: DOUBLE FAULT\n{:#?}", stack_frame);
}
extern "x86-interrupt" fn timer_interrupt_handler(_stack_frame: InterruptStackFrame) {
unsafe {
CNT = CNT + 1;
}
notify_eoi(InterruptIndex::Timer as u8);
}
extern "x86-interrupt" fn keyboard_interrupt_handler(_stack_frame: InterruptStackFrame) {
use x86_64::instructions::port::Port;
let mut port = Port::new(0x60);
let scancode: u8 = unsafe { port.read() };
let mut keyboard = Keyboard::new(
ScancodeSet1::new(),
layouts::Us104Key,
HandleControl::Ignore,
);
if let Ok(Some(key_event)) = keyboard.add_byte(scancode) {
if let Some(key) = keyboard.process_keyevent(key_event) {
match key {
DecodedKey::Unicode(character) => {
shell_input(character);
}
DecodedKey::RawKey(_key) => {
fb_print!("{:?}", key);
}
}
}
}
notify_eoi(InterruptIndex::Keyboard as u8);
}
extern "x86-interrupt" fn com1_interrupt_handler(_stack_frame: InterruptStackFrame) {
let chasr = receive();
serial_shell_input(chasr as char);
notify_eoi(InterruptIndex::Com1 as u8);
()
}
#[test_case]
fn test_breakpoint() {
x86_64::instructions::interrupts::int3();
}