YSOS-rust lab2

YSOS-lab2

1. 实验要求

  1. 了解中断的作用、中断的分类、中断的处理过程。
  2. 启用基于 APIC 的中断,注册 IDT 中断处理程序,实现时钟中断。
  3. 注册内核堆分配器。(不实现内存分配算法,使用现有代码赋予内核堆分配能力)
  4. 实现串口驱动的输入能力,尝试进行基础的 IO 操作和交互。

2. 实验过程

补全 TSS 的中断栈表

参照tss.privilege_stack_table[0]完成即可

代码实现见关键代码部分, [点击跳转](# 实现tss.interrupt_stack_table)

注册中断处理程序

代码实现见关键代码部分, [点击跳转](# 实现注册中断处理程序)

初始化APIC

代码实现见关键代码部分, [点击跳转](# 实现初始化APIC)

时钟中断

代码实现见关键代码部分, [点击跳转](# 实现时钟中断)

为了保证多线程环境下的安全性, 保证COUNTER原子地读写, 代码中采用了AtomicU64实现变量COUNTER

调节时钟中断频率

内核运行结果如下

image-20240321111940730

可以看出, 每打印10次 Hello world 出现一次时钟中断

要实现中断频率减半,只需修改pkg/kernel/src/interrupt/clock.rs

1
2
3
4
5
6
7
8
pub extern "x86-interrupt" fn clock_handler(_sf: InterruptStackFrame) {
x86_64::instructions::interrupts::without_interrupts(|| {
if inc_counter() % 0x10000 == 0 {
info!("Tick! @{}", read_counter());
}
super::ack();
});
}

第三行修改为if inc_counter() % 0x20000 == 0 {

重新编译和运行内核,如下图

image-20240321112535949

可以看出, 每打印20次 Hello world 出现一次时钟中断, 中断频率减半了

串口输入中断

代码实现见关键代码部分, [点击跳转](# 实现串口输入中断)

实现输入驱动input.rs:

  • 使用 crossbeam_queue::ArrayQueue 作为缓冲区的实现, 它是一个无锁的、固定大小的队列, 可以在多线程环境下安全地进行读写操作。
  • 设置输入缓冲区INPUT_BUF的大小为128, 存储数据结构为pc_keyboard::DecodedKey, 这是一种可以容纳Unicode的数据结构, 保证串口输入对中文和emoji的支持

实现中断处理程序serial.rs:

当输入中断发生的时候, 会调用receive函数, 函数大致逻辑如下

  • 首先创建一个Unicode缓冲区,用于处理UTF-8编码字符大小不定的情况, 缓冲区大小4字节

  • 进入一个循环,每次使用get_serial_for_sure().receive()从串口输入获取一个字节的数据, 写入Unicode缓冲区, 使用from_utf8函数判断Unicode缓冲区中的数据是否构成合法字符, 如果是合法字符则放入输入缓冲区,结束循环

用户交互

代码实现见关键代码部分, [点击跳转](# 实现用户交互)

思考题

1.为什么需要在 clock_handler 中使用 without_interrupts 函数?如果不使用它, 可能会发生什么情况?

为了避免clock_handler在执行的过程中被其他中断打断, 如果不使用它, 可能出现的后果如下

  1. 中断嵌套:如果在时钟中断处理函数中不关中断, 而又在该函数中触发了同一类型的中断(例如, 又发生了一个时钟中断), 这会导致中断嵌套, 影响系统的响应性能
  2. 时钟精度下降: 如果时钟中断被另一个耗时很长的中断打断, 会导致时钟不准

2.考虑时钟中断进行进程调度的场景, 时钟中断的频率应该如何设置?太快或太慢的频率会带来什么问题?请分别回答。

太快或太慢的频率会带来的问题如下

  • 过快的时钟中断频率:如果时钟中断频率过快,会导致中断处理程序的开销增加。这会占用CPU时间,使得系统在处理中断时花费更多的时间,从而降低了系统的性能。CPU将花费大部分时间在中断处理上,而不是执行实际的任务。

  • 过慢的时钟中断频率:如果时钟中断频率过慢,会影响进程的调度粒度。进程可能长时间占用CPU,导致响应时间较长。进程可能在等待CPU时间时长时间处于等待状态。

在大多数情况下,100Hz的频率是一个不错的时钟中断频率, 既保证中断开销不至于过大, 保证系统的吞吐率, 又能保证用户能够感受到多个进程同时执行, 降低响应时延


3.在进行 receive 操作的时候, 为什么无法进行日志输出?如果强行输出日志, 会发生什么情况?谈谈你对串口、互斥锁的认识。

准确来说, 并非整个receive函数无法进行日志输出, 请参考下面的代码

1
2
3
4
5
6
7
8
9
10
11
12
fn receive() {
info!("at receive start");//输出1
let mut input_buffer:Vec<u8> = Vec::with_capacity(INPUT_BUFFER_SIZE);
loop{
let mut serial = get_serial_for_sure();//获取锁
info!("at receive middle");//输出2
let rec = serial.receive();
drop(serial);//释放锁
info!("at receive end");//输出3
......
}
}

at receive startat receive end是可以正常输出的, at receive middle无法输出

函数get_serial_for_sure()会使用互斥锁保护串口, drop(serial)会解除对互斥锁的保护

串口设备属于静态的全局对象, 可能会被多个进程同时读写, 造成未定义的行为, 所以需要使用互斥锁保护

在互斥锁保护期间, 日志输出无法取得互斥锁的所有权, 就无法正常输出


4.输入缓冲区在什么情况下会满?如果缓冲区满了, 用户输入的数据会发生什么情况?

我的实现使用了三个缓冲区, 分别是:

  1. Unicode输入缓冲区: 大小为4个字节, 用于将输入的字节流转为UTF-8字符保存到全局缓冲区. 只要输入的字符是UTF-8编码的, 该缓冲区就不会满, 否则, 遇到缓冲区满的情况, 缓冲区中的数据会视为非法数据而被丢弃

  2. 全局缓冲区:ArrayQueue实现的大小限定为128的缓冲区, 用于存储用户输入但还没有被处理程序读取的Unicode字符. 如果用户一直输入而没有处理程序(本次实验是main函数运行的get_line())读取, 该缓冲区可能会满. 如果该缓冲区满了, 会触发警告输出, 新输入的字符会被舍弃

    在本次实验中该缓冲区不可能满, 因为kernel启动后就始终在调用get_line()函数读取全局缓冲区. 如果修改代码, kernel启动后可能会执行其他耗时更长的任务而有一段时间没有调用get_line(), 则全局缓冲区可能会满, 结果是会触发警告输出, 用户新输入的字符会被舍弃

  3. 处理程序缓冲区: 在本次实验是get_line()函数, 它会循环等待并读取全局缓冲区, 直到读取到换行字符结束循环. 它使用一个预留了128字符空间的String实现. 因为String具有自动扩容的功能, 该缓冲区不可能满.


5.进行下列尝试, 并在报告中保留对应的触发方式及相关代码片段:

  • 尝试用你的方式触发 Triple Fault,开启 intdbg 对应的选项,在 QEMU 中查看调试信息,分析 Triple Fault 的发生过程。

kernel_main函数中添加以下代码, 用于触发page_fault

1
2
3
4
5
unsafe{
let a:*mut u8 = 0x1000000000 as *mut u8;
let b:u8 = *a + 1;
println!("{b}");
}

pkg/kernel/src/interrupt/exceptions.rs注释掉page_faultdouble_fault的中断处理函数, 然后启动内核

可以观察到, 内核一直在重启, 说明成功触发了Triple Fault

运行下面的指令启动QEMU并开启了interrupt debug, 可以看到中断记录如图

1
/usr/bin/qemu-system-x86_64 -bios assets/OVMF.fd -net none -serial stdio -m 96M -d int -drive format=raw,file=fat:rw:esp

image-20240326085635683

查询https://wiki.osdev.org/Exceptions 可知:

0xe对应Page Fault, 0xb对应Stack-Segment Fault, 0x8对应Double Fault

Triple Fault的发生过程为在触发general_protection_fault之后, CPU尝试在 IDT 中查找相应的处理函数, 由于找不到, 接着抛出了 double fault 异常, 找不到相应的处理函数, 就抛出了Triple Fault异常

  • 尝试触发 Double Fault,观察 Double Fault 的发生过程,尝试通过调试器定位 Double Fault 发生时使用的栈是否符合预期。

触发方式同上, 不同点是保留double_fault的中断处理函数

要查看double fault发生时的栈,首先确定栈的地址,可以从如图的输出中得到

image-20240330084843230

使用gdb调试器,查看指定内存地址,就能看到栈的数据

1
2
3
4
5
# Page Fault
x /512xg 0xffffff00008c0150
# Double Fault
x /512xg 0xffffff00008bf150
x /2xg 0xffffff00008bf150

结果如下图,可以看到double fault的栈里是有数据的, 说明此时触发了double fault, 而Page fault 的栈里没有数据, 因为Page Fault的处理函数被注释了

double fault的栈

image-20240330084735274

Page Fault的栈

image-20240330085102064

  • 通过访问非法地址触发 Page Fault,观察 Page Fault 的发生过程。分析 Cr2 寄存器的值,并尝试回答为什么 Page Fault 属于可恢复的异常

kernel_main加入以下代码以引入缺页异常

1
2
3
4
5
unsafe{
let a:*mut u8 = 0x1000000000 as *mut u8;
let b:u8 = *a + 1;
println!("{b}");
}

调试过程如下图:

image-20240326081634380

可以看出, Cr2寄存器的值0x0000001000000000就是上面尝试访问的地址

缺页异常经过异常处理函数处理之后, 可以将不在内存中的虚拟地址数据重新加载到内存中, 这样就可以正常访问了, 所以 Page Fault 属于可恢复的异常

使用QEMU可以看到中断记录如图

image-20240326084621565

check_exception old: 0xffffffff new 0xe表明当前出发了缺页中断, 0xe恰好就是缺页的中断向量号

image-20240326084811241


6.如果在 TSS 中为中断分配的栈空间不足, 会发生什么情况?请分析 CPU 异常的发生过程, 并尝试回答什么时候会发生 Triple Fault。

==方法1==

如果在 TSS 中为中断分配的栈空间不足,会发生Triple Fault

main函数添加如下代码,用以触发page_fault

1
2
3
4
5
6
//访问0x1000000000触发page_fault
unsafe{
let a:*mut u8 = 0x1000000000 as *mut u8;
let b:u8 = *a + 1;
println!("{b}");
}

pkg/kernel/src/interrupt/exceptions.rs修改page_fault_handler

1
2
3
4
5
6
7
debug!("at page_fault_handler, stack_frame={:#?}, err_code={:#?}", stack_frame, err_code);
//访问0x2000000000, 再次触发page_fault
unsafe{
let a:*mut u8 = 0x2000000000 as *mut u8;
let b:u8 = *a + 1;
println!("{b}");
}

启动操作系统, 可以看到一直在循环打印at page_fault_handler, 没有发生Triple Fault

中断栈的分配情况如下

image-20240406092959887

page_fault_handler中打印的日志如下

image-20240406093129673

栈指针一直指向0xffffff00008c1218, 在Page Fault Stack的栈顶附近

因为page_fault_handler中一直在访问同一个地址0x2000000000, 栈指针没有变化, 所以中断栈没有被填满

现在修改pkg/kernel/src/memory/gdt.rs, 修改给中断分配的栈大小如下

1
2
//pub const IST_SIZES: [usize; 3] = [0x1000, 0x1000, 0x1000];
pub const IST_SIZES: [usize; 3] = [0x1, 0x1, 0x1];

重新编译和启动操作系统, 可以看到Triple Fault发生了, 如下图

image-20240406094230105

==方法2==

该方法可以更加直观地看到中断栈被写满的过程

main函数添加如下代码,用以触发page_fault

1
2
3
4
5
6
//访问0x1000000000触发page_fault
unsafe{
let a:*mut u8 = 0x1000000000 as *mut u8;
let b:u8 = *a + 1;
println!("{b}");
}

pkg/kernel/src/interrupt/exceptions.rs修改page_fault_handler

1
2
3
4
5
6
7
debug!("at page_fault_handler, stack_frame={:#?}, err_code={:#?}", stack_frame, err_code);
// 用于触发除0错误
unsafe{
asm!("mov eax, 1");
asm!("mov ebx, 0");
asm!("div ebx");
}

修改divide_error_handler

1
2
3
4
5
6
7
debug!("at divide_error_handler, stack_pointer={:#?}", stack_frame.stack_pointer);
//循环触发除0错误
unsafe{
asm!("mov eax, 1");
asm!("mov ebx, 0");
asm!("div ebx");
}

启动操作系统, 大约几秒后发生了Triple Fault

image-20240406100055444

输出的开头:

image-20240406100242340

中间输出的内容很长,这里不展示了

输出的结尾:

image-20240406100451394

现在修改pkg/kernel/src/memory/gdt.rs, 修改给中断分配的栈大小如下

1
2
//pub const IST_SIZES: [usize; 3] = [0x1000, 0x1000, 0x1000];
pub const IST_SIZES: [usize; 3] = [0x500, 0x500, 0x500];

重新编译和启动操作系统, 结果如下

image-20240406101135817

Triple Fault前的输出明显变短了, 因为给中断分配的栈空间更小了

image-20240406101407566

==结论==

当处理器因为中断栈空间不足等原因无法调用中断处理程序时, 就会发生Triple Fault


7.在未使用 set_stack_index 函数时, 中断处理程序的栈可能哪里?尝试结合 gdb 调试器, 找到中断处理程序的栈, 并验证你的猜想是否正确。

在未使用 set_stack_index 函数时, 中断处理程序使用内核栈

在上一问方法2的基础上继续修改

修改pkg/kernel/src/interrupt/exceptions.rsregister_idt, 注释掉.set_stack_index

1
2
3
4
5
6
idt.double_fault
.set_handler_fn(double_fault_handler);
//.set_stack_index(gdt::DOUBLE_FAULT_IST_INDEX);
idt.page_fault
.set_handler_fn(page_fault_handler);
//.set_stack_index(gdt::PAGE_FAULT_IST_INDEX);

重新编译和启动操作系统

image-20240406103822270

输出很长,这里展示开头和结尾

image-20240406104026220

image-20240406104217588

加分项

1.😋 为全部可能的 CPU 异常设置对应的处理程序,使用 panic! 输出异常信息。

见关键代码部分, [点击跳转](# 实现注册中断处理程序)

2.😋 你如何定义用于计数的 COUNTER,它能够做到线程安全吗?如果不能,如何修改?

见关键代码部分, [点击跳转](# 实现时钟中断)

我使用AtomicU64实现计数的COUNTER, 在多线程下是安全的

3.🤔 操作 APIC 时存在大量比特操作,尝试结合使用 bitflagsbit_field 来定义和操作这些寄存器的值,从而获得更好的可读性。

见关键代码部分, [点击跳转](# 使用bitflags实现初始化APIC)

4.🤔 你的串口输入驱动是否能正确的处理中文甚至 emoji 输入?如何能够正确处理?

可以支持中文和emoji的输入,效果如下图

image-20240330093049877

处理方式如下

中文和emoji是采用UTF-8编码的, 这里需要以Unicode字符为单位保存. 串口输入接收到的单个字符长度在1到4字节不等, 需要在串口接收阶段就完成将字节流转为Unicode字符的操作. 在pkg/kernel/src/interrupt/serial.rsreceive()函数中, 使用一个长度为4的Unicode输入缓冲区, 它会在一个循环中逐字节读取, 直到当前Unicode输入缓冲区的数据刚好能够组成一个Unicode字符, 就清空Unicode输入缓冲区, 将该字符放入全局缓冲区.

3. 关键代码

实现tss.interrupt_stack_table

pkg/kernel/src/memory/gdt.rs

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
// FIXME: fill tss.interrupt_stack_table with the static stack buffers like above
tss.interrupt_stack_table[DOUBLE_FAULT_IST_INDEX as usize] = {
const STACK_SIZE: usize = IST_SIZES[1];
static mut STACK: [u8; STACK_SIZE] = [0; STACK_SIZE];
let stack_start = VirtAddr::from_ptr(unsafe { STACK.as_ptr() });
let stack_end = stack_start + STACK_SIZE as u64;
info!(
"Double Fault Stack: 0x{:016x}-0x{:016x}",
stack_start.as_u64(),
stack_end.as_u64()
);
stack_end
};
tss.interrupt_stack_table[PAGE_FAULT_IST_INDEX as usize] = {
const STACK_SIZE: usize = IST_SIZES[2];
static mut STACK: [u8; STACK_SIZE] = [0; STACK_SIZE];
let stack_start = VirtAddr::from_ptr(unsafe { STACK.as_ptr() });
let stack_end = stack_start + STACK_SIZE as u64;
info!(
"Page Fault Stack : 0x{:016x}-0x{:016x}",
stack_start.as_u64(),
stack_end.as_u64()
);
stack_end
};
// You can use `tss.interrupt_stack_table[DOUBLE_FAULT_IST_INDEX as usize]`

实现注册中断处理程序

pkg/kernel/src/interrupt/mod.rs修改init函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/// init interrupts system
pub fn init() {
IDT.load();

// FIXME: check and init APIC
match apic::XApic::support() {
true => info!("XAPIC supported"),
false => error!("XAPIC not supported"),
}
let mut xapic = unsafe { XApic::new(physical_to_virtual(LAPIC_ADDR)) };
xapic.cpu_init();

// FIXME: enable serial irq with IO APIC (use enable_irq)
enable_irq(consts::Irq::Serial0 as u8 , 0);
info!("Interrupts Initialized.");
}

pkg/kernel/src/interrupt/exceptions.rsregister_idt函数添加以下内容

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
// TODO: you should handle more exceptions here
// especially gerneral protection fault (GPF)
// see: https://wiki.osdev.org/Exceptions
idt.debug.set_handler_fn(debug_handler);
idt.non_maskable_interrupt.set_handler_fn(nmi_handler);
idt.breakpoint.set_handler_fn(breakpoint_handler);
idt.overflow.set_handler_fn(overflow_handler);
idt.bound_range_exceeded
.set_handler_fn(bound_range_exceeded_handler);
idt.invalid_opcode.set_handler_fn(invalid_opcode_handler);
idt.device_not_available
.set_handler_fn(device_not_available_handler);
idt.invalid_tss.set_handler_fn(invalid_tss_handler);
idt.segment_not_present
.set_handler_fn(segment_not_present_handler);
idt.stack_segment_fault
.set_handler_fn(stack_segment_fault_handler);
idt.general_protection_fault
.set_handler_fn(general_protection_fault_handler);
idt.x87_floating_point.set_handler_fn(x87_floating_point_handler);
idt.alignment_check.set_handler_fn(alignment_check_handler);
idt.machine_check.set_handler_fn(machine_check_handler);
idt.simd_floating_point.set_handler_fn(simd_floating_point_handler);
idt.virtualization.set_handler_fn(virtualization_handler);
//idt.control_protection.set_handler_fn(control_protection_handler);
idt.hv_injection_exception.set_handler_fn(hv_injection_handler);
idt.vmm_communication_exception.set_handler_fn(vmm_communication_handler);
idt.security_exception.set_handler_fn(security_exception_handler);

page_fault_handler中, 将Cr2::read().unwrap_or(0xdeadbeef),改为Cr2::read().unwrap_or(VirtAddr::new(0xdeadbeef)),

然后添加中断处理函数

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
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
pub extern "x86-interrupt" fn debug_handler(stack_frame: InterruptStackFrame) {
panic!("EXCEPTION: DEBUG\n\n{:#?}", stack_frame);
}
pub extern "x86-interrupt" fn nmi_handler(stack_frame: InterruptStackFrame) {
panic!("EXCEPTION: NMI\n\n{:#?}", stack_frame);
}
pub extern "x86-interrupt" fn breakpoint_handler(stack_frame: InterruptStackFrame) {
panic!("EXCEPTION: BREAKPOINT\n\n{:#?}", stack_frame);
}
pub extern "x86-interrupt" fn overflow_handler(stack_frame: InterruptStackFrame) {
panic!("EXCEPTION: OVERFLOW\n\n{:#?}", stack_frame);
}
pub extern "x86-interrupt" fn bound_range_exceeded_handler(stack_frame: InterruptStackFrame) {
panic!("EXCEPTION: BOUND RANGE EXCEEDED\n\n{:#?}", stack_frame);
}
pub extern "x86-interrupt" fn invalid_opcode_handler(stack_frame: InterruptStackFrame) {
panic!("EXCEPTION: INVALID OPCODE\n\n{:#?}", stack_frame);
}
pub extern "x86-interrupt" fn device_not_available_handler(stack_frame: InterruptStackFrame) {
panic!("EXCEPTION: DEVICE NOT AVAILABLE\n\n{:#?}", stack_frame);
}
pub extern "x86-interrupt" fn invalid_tss_handler(stack_frame: InterruptStackFrame, err_code: u64) {
panic!(
"EXCEPTION: INVALID TSS, ERROR_CODE: 0x{:016x}\n\n{:#?}",
err_code, stack_frame
);
}
pub extern "x86-interrupt" fn segment_not_present_handler(
stack_frame: InterruptStackFrame,
err_code: u64
) {
panic!("EXCEPTION: SEGMENT NOT PRESENT, ERROR_CODE: 0x{:016x}\n\n{:#?}", err_code, stack_frame);
}
pub extern "x86-interrupt" fn stack_segment_fault_handler(
stack_frame: InterruptStackFrame,
err_code: u64
) {
panic!("EXCEPTION: STACK SEGMENT FAULT, ERROR_CODE: 0x{:016x}\n\n{:#?}", err_code, stack_frame);
}
pub extern "x86-interrupt" fn general_protection_fault_handler(
stack_frame: InterruptStackFrame,
err_code: u64,
) {
panic!(
"EXCEPTION: GENERAL PROTECTION FAULT, ERROR_CODE: 0x{:016x}\n\n{:#?}",
err_code, stack_frame
);
}
pub extern "x86-interrupt" fn x87_floating_point_handler(stack_frame: InterruptStackFrame) {
panic!("EXCEPTION: X87 FLOATING POINT\n\n{:#?}", stack_frame);
}
pub extern "x86-interrupt" fn alignment_check_handler(stack_frame: InterruptStackFrame, err_code: u64) {
panic!("EXCEPTION: ALIGNMENT CHECK, ERROR_CODE: 0x{:016x}\n\n{:#?}",
err_code, stack_frame);
}
pub extern "x86-interrupt" fn machine_check_handler(stack_frame: InterruptStackFrame) -> ! {
panic!("EXCEPTION: MACHINE CHECK\n\n{:#?}", stack_frame);
}
pub extern "x86-interrupt" fn simd_floating_point_handler(stack_frame: InterruptStackFrame) {
panic!("EXCEPTION: SIMD FLOATING POINT\n\n{:#?}", stack_frame);
}
pub extern "x86-interrupt" fn virtualization_handler(stack_frame: InterruptStackFrame) {
panic!("EXCEPTION: VIRTUALIZATION\n\n{:#?}", stack_frame);
}
pub extern "x86-interrupt" fn hv_injection_handler(stack_frame: InterruptStackFrame) {
panic!("EXCEPTION: HV INJECTION\n\n{:#?}", stack_frame);
}
pub extern "x86-interrupt" fn vmm_communication_handler(stack_frame: InterruptStackFrame, err_code: u64) {
panic!("EXCEPTION: VMM COMMUNICATION, ERROR_CODE: 0x{:016x}\n\n{:#?}",
err_code, stack_frame);
}
pub extern "x86-interrupt" fn security_exception_handler(stack_frame: InterruptStackFrame, err_code: u64) {
panic!("EXCEPTION: SECURITY EXCEPTION, ERROR_CODE: 0x{:016x}\n\n{:#?}",
err_code, stack_frame);
}

实现初始化APIC

pkg/kernel/src/interrupt/apic/xapic.rs

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
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
/// If this type APIC is supported
fn support() -> bool {
// FIXME: Check CPUID to see if xAPIC is supported.
CpuId::new()
.get_feature_info()
.map(|f| f.has_apic())
.unwrap_or(false)
}

// Initialize the xAPIC for the current CPU.
fn cpu_init(&mut self) {
unsafe {
// FIXME: Enable local APIC; set spurious interrupt vector.
let mut spiv = self.read(0xF0);
spiv |= 1 << 8; // set EN bit
// clear and set Vector
spiv &= !(0xFF);
spiv |= Interrupts::IrqBase as u32 + Irq::Spurious as u32;
self.write(0xF0, spiv);
// FIXME: The timer repeatedly counts down at bus frequency
// 设置计时器相关寄存器
self.write(0x3E0, 0b1011); // set Timer Divide to 1
self.write(0x380, 0x20000); // set initial count to 0x20000
let mut lvt_timer = self.read(0x320); // LVT Timer Register
// clear and set Vector
lvt_timer &= !(0xFF);
lvt_timer |= Interrupts::IrqBase as u32 + Irq::Timer as u32;
lvt_timer &= !(1 << 16); // clear Mask
lvt_timer |= 1 << 17; // set Timer Periodic Mode
self.write(0x320, lvt_timer);

// FIXME: Disable logical interrupt lines (LINT0, LINT1)
self.write(0x350, 1 << 16); // set Mask, disable LINT0
self.write(0x360, 1 << 16); // set Mask, disable LINT1
// FIXME: Disable performance counter overflow interrupts (PCINT)
self.write(0x340, 1 << 16);
// FIXME: Map error interrupt to IRQ_ERROR.
// 设置错误中断 LVT Error 到对应的中断向量号
let mut lvt_error = self.read(0x370);
lvt_error &= !(0xFF);
lvt_error |= Interrupts::IrqBase as u32 + Irq::Error as u32;
self.write(0x370, lvt_error);
// FIXME: Clear error status register (requires back-to-back writes).
// 连续写入两次 0 以清除错误状态寄存器
self.write(0x280, 0);
self.write(0x280, 0);
// FIXME: Ack any outstanding interrupts.
// 向 EOI 寄存器写入 0 以确认任何挂起的中断。
//self.write(0x0B0, 0);
self.eoi();
// FIXME: Send an Init Level De-Assert to synchronise arbitration ID's.
// let mut icr = self.icr();
// icr |= 2 << 18;
// icr |= 5 << 8;
// icr &= !(1 << 14);
// icr |= 1 << 15;
// self.set_icr(icr);
// #
self.write(0x310, 0); // set ICR 0x310
const BCAST: u32 = 1 << 19;
const INIT: u32 = 5 << 8;
const TMLV: u32 = 1 << 15; // TM = 1, LV = 0
self.write(0x300, BCAST | INIT | TMLV); // set ICR 0x300
const DS: u32 = 1 << 12;
while self.read(0x300) & DS != 0 {} // wait for delivery status
// #
// FIXME: Enable interrupts on the APIC (but not on the processor).
//设置 TPR 寄存器为 0, 允许接收中断。
self.write(0x080, 0);
}

// NOTE: Try to use bitflags! macro to set the flags.
}

实现时钟中断

修改pkg/kernel/src/interrupt/clock.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// FIXME: use a counter to count ticks
static COUNTER: AtomicU64 = AtomicU64::new(0);

#[inline]
pub fn read_counter() -> u64 {
// FIXME: load counter value
COUNTER.load(core::sync::atomic::Ordering::SeqCst)
}

#[inline]
pub fn inc_counter() -> u64 {
// FIXME: read counter value and increase it
let id = COUNTER.fetch_add(1, core::sync::atomic::Ordering::SeqCst);
id
}

实现串口输入中断

pkg/kernel/src/drivers/uart16550.rsinit函数结尾加入以下内容

1
2
// Enable interrupts
self.interrupt_enable.write(0x01);

实现pkg/kernel/src/drivers/input.rs, 代码如下

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
54
55
56
57
58
use alloc::string::String;
/* your input type */
use crossbeam_queue::ArrayQueue;
use pc_keyboard::DecodedKey;
type Key = DecodedKey;

const BUFFER_SIZE: usize = 128;
lazy_static! {
static ref INPUT_BUF: ArrayQueue<Key> = ArrayQueue::new(BUFFER_SIZE);
}

#[inline]
pub fn push_key(key: Key) {
if INPUT_BUF.push(key).is_err() {
warn!("Input buffer is full. Dropping key '{:?}'", key);
}
}

#[inline]
pub fn try_pop_key() -> Option<Key> {
INPUT_BUF.pop()
}

pub fn pop_key() -> Key {
loop {
if let Some(key) = try_pop_key() {
return key;
}
}
}
pub fn get_line() -> String {
let mut line = String::with_capacity(BUFFER_SIZE);
loop{
let cur_key = pop_key();
match cur_key {
Key::Unicode(c) => {
match c {
'\n'|'\r' => {
print!("\n");
break;
},
'\x08' | '\x7f' => {
if !line.is_empty() {
print!("\x08\x20\x08");
line.pop();
}
}
c => {
print!("{}", c);
line.push(c);
}
}
}
_ => {}
}
}
line
}

实现pkg/kernel/src/interrupt/serial.rs

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
54
55
use core::str::from_utf8;
use alloc::vec::Vec;
use pc_keyboard::DecodedKey;
use x86_64::structures::idt::{InterruptDescriptorTable, InterruptStackFrame};

use crate::{drivers::input::push_key, serial::get_serial_for_sure};

use super::consts::*;

pub unsafe fn register_idt(idt: &mut InterruptDescriptorTable) {
idt[Interrupts::IrqBase as u8 + Irq::Serial0 as u8]
.set_handler_fn(serial_handler);
}

pub extern "x86-interrupt" fn serial_handler(_st: InterruptStackFrame) {
//info!("at serial handler");
receive();
super::ack();
}
const INPUT_BUFFER_SIZE: usize = 4;
/// Receive character from uart 16550
/// Should be called on every interrupt
fn receive() {
//info!("at receive");
// FIXME: receive character from uart 16550, put it into INPUT_BUFFER
let mut input_buffer:Vec<u8> = Vec::with_capacity(INPUT_BUFFER_SIZE);
loop{
//info!("at receive");
let mut serial = get_serial_for_sure();
let rec = serial.receive();
drop(serial);
// println!("{:?}", rec);
// break;

match rec{
Some(c) => {
// info!("at some");
// info!("{}", c as char);
input_buffer.push(c);
if let Ok(s) = from_utf8(&input_buffer){
let ch = s.chars().next().unwrap();
push_key(DecodedKey::Unicode(ch));
input_buffer.clear();
}else if input_buffer.len() >= INPUT_BUFFER_SIZE{
input_buffer.clear();
}
},
_ => {
// info!("failed to receive");
//println!("failed to receive");
break;
}
}
}
}

实现用户交互

修改pkg/kernel/src/main.rs如下

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
#![no_std]
#![no_main]

use ysos::*;
use ysos_kernel as ysos;
use crate::drivers::input;
use crate::interrupt::clock::read_counter;
extern crate alloc;

boot::entry_point!(kernel_main);

pub fn kernel_main(boot_info: &'static boot::BootInfo) -> ! {
ysos::init(boot_info);

loop {
print!("> ");
let input = input::get_line();

match input.trim() {
"exit" => break,
_ => {
println!("You said: {}", input);
println!("The counter value is {}", read_counter());
}
}
}

ysos::shutdown(boot_info);
}

使用bitflags实现初始化APIC

参照 https://ysos.gzti.me/wiki/apic/#local-apic-寄存器 中的表格

修改pkg/kernel/src/interrupt/apic/xapic.rs

添加bitflags

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
bitflags! {
pub struct APIC: u32 {
const TPR = 0x80;
const spiv = 0xf0;
const ErrorStatusRegister = 0x280;
const ICR0 = 0x300;
const ICR1 = 0x310;
const LVTTimerRegister = 0x320;
const LVTPerformance = 0x340;
const LVTLINT0Register = 0x350;
const LVTLINT1Register = 0x360;
const LVTErrorRegister = 0x370;
const InitialCountRegister = 0x380;
const DivideConfigurationRegister = 0x3e0;

}
}

cpu_init函数修改如下

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
54
55
56
fn cpu_init(&mut self) {
unsafe {
// FIXME: Enable local APIC; set spurious interrupt vector.
let mut spiv = self.read(APIC::spiv.bits());
spiv |= 1 << 8; // set EN bit
// clear and set Vector
spiv &= !(0xFF);
spiv |= Interrupts::IrqBase as u32 + Irq::Spurious as u32;
self.write(APIC::spiv.bits(), spiv);
// FIXME: The timer repeatedly counts down at bus frequency
// 设置计时器相关寄存器
self.write(APIC::DivideConfigurationRegister.bits(), 0b1011); // set Timer Divide to 1
self.write(APIC::InitialCountRegister.bits(), 0x20000); // set initial count to 0x20000
let mut lvt_timer = self.read(APIC::LVTTimerRegister.bits()); // LVT Timer Register
// clear and set Vector
lvt_timer &= !(0xFF);
lvt_timer |= Interrupts::IrqBase as u32 + Irq::Timer as u32;
lvt_timer &= !(1 << 16); // clear Mask
lvt_timer |= 1 << 17; // set Timer Periodic Mode
self.write(APIC::LVTTimerRegister.bits(), lvt_timer);

// FIXME: Disable logical interrupt lines (LINT0, LINT1)
self.write(APIC::LVTLINT0Register.bits(), 1 << 16); // set Mask, disable LINT0
self.write(APIC::LVTLINT1Register.bits(), 1 << 16); // set Mask, disable LINT1
// FIXME: Disable performance counter overflow interrupts (PCINT)
self.write(APIC::LVTPerformance.bits(), 1 << 16);
// FIXME: Map error interrupt to IRQ_ERROR.
// 设置错误中断 LVT Error 到对应的中断向量号
let mut lvt_error = self.read(APIC::LVTErrorRegister.bits());
lvt_error &= !(0xFF);
lvt_error |= Interrupts::IrqBase as u32 + Irq::Error as u32;
self.write(APIC::LVTErrorRegister.bits(), lvt_error);
// FIXME: Clear error status register (requires back-to-back writes).
// 连续写入两次 0 以清除错误状态寄存器
self.write(APIC::LVTErrorRegister.bits(), 0);
self.write(APIC::LVTErrorRegister.bits(), 0);
// FIXME: Ack any outstanding interrupts.
// 向 EOI 寄存器写入 0 以确认任何挂起的中断。
//self.write(0x0B0, 0);
self.eoi();
// FIXME: Send an Init Level De-Assert to synchronise arbitration ID's.
self.write(APIC::ICR1.bits(), 0); // set ICR 0x310
const BCAST: u32 = 1 << 19;
const INIT: u32 = 5 << 8;
const TMLV: u32 = 1 << 15; // TM = 1, LV = 0
self.write(APIC::ICR0.bits(), BCAST | INIT | TMLV); // set ICR 0x300
const DS: u32 = 1 << 12;
while self.read(APIC::ICR0.bits()) & DS != 0 {} // wait for delivery status
// #
// FIXME: Enable interrupts on the APIC (but not on the processor).
//设置 TPR 寄存器为 0,允许接收中断。
self.write(APIC::TPR.bits(), 0);
}

// NOTE: Try to use bitflags! macro to set the flags.
}

4. 实验结果

image-20240330092703692

如上图, 可以正常实现用户交互的功能, 支持中文输入和emoji, 并且打印出当前的时钟counter值, 输入exit能够正常关机

5. 总结

熟悉了中断处理的操作, 并且了解了计算机如何实现时钟中断和输入中断, 如何与用户通过命令行交互


YSOS-rust lab2
https://blog.algorithmpark.xyz/2024/04/06/YSOS/lab2/index/
作者
CJL
发布于
2024年4月6日
更新于
2024年5月14日
许可协议