使用Rust开发操作系统(GDT,IDT加载,以及GDB调试内核)

释放双眼,带上耳机,听听看~!

GDT,IDT加载,以及GDB调试内核

  • 明确一下目标

  • 开始干活

  • 初始化TSS

    • 初始化GDT
    • 初始化IDT
    • 初始化8259A
    • 关于调试
  • 下一步要做什么

我们花费了好大力气完成了全局描述符表,中断描述符表,TSS,PIC等结构和对应功能的代码,在本章中我们根据之前编写的GDT,IDT结构以及对应的方法来完成GDT,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
29
30
31
32
33
34
35
36
37
38
1.
2├── kernel
3│   ├── Cargo.lock
4│   ├── Cargo.toml
5│   ├── Makefile
6│   ├── src
7│   │   ├── main.rs
8│   │   └── vga.rs
9│   └── x86-64.json
10└── lib
11    ├── Cargo.lock
12    ├── Cargo.toml
13    └── src
14        ├── bits
15        │   └── mod.rs
16        ├── ia_32e
17        │   ├── addr.rs
18        │   ├── cpu
19        │   │   ├── mod.rs
20        │   │   ├── pic.rs
21        │   │   └── port.rs
22        │   ├── descriptor
23        │   │   ├── gdt.rs
24        │   │   ├── idt.rs
25        │   │   ├── mod.rs
26        │   │   └── tss.rs
27        │   ├── instructions
28        │   │   ├── interrupt.rs
29        │   │   ├── mod.rs
30        │   │   ├── port.rs
31        │   │   ├── segmention.rs
32        │   │   └── tables.rs
33        │   └── mod.rs
34        ├── lib.rs
35        ├── mutex.rs
36        └── tests.rs
37
38

kernel.toml的内容如下


1
2
3
4
5
6
7
8
9
10
11
1[dependencies.lazy_static]
2version = "1.0"
3features = ["spin_no_std"]
4
5[dependencies]
6# 用于系统引导
7bootloader = "0.8.0"
8# 我们自己写的
9system={path="../lib/",version="0.1.0"}
10
11

我们需要明确接下来的工作,主要的工作有这些:

  1. 使用lazy_static初始化TSS,GDT,IDT
  2. 使用IDT,GDT的load来加载IDT,GDT
  3. 使用set_cs来设置内核运行代码段,使用set_tss函数加载tss
  4. 初始化PIC并使用initialize来初始化8259A
  5. 使用sti指令开启中断

经过以上步骤我们可以完成描述以及中断的初始化操作,中断的处理将会放到下个章节讲述

开始干活

初始化TSS

我们在kernel/src/gdt.rs中添加以下内容(需要创建)


1
2
3
4
5
6
7
8
9
10
11
12
1// in kernel/src/gdt.rs
2use system::ia_32e::descriptor::tss::TaskStateSegment;
3use lazy_static::lazy_static;
4
5lazy_static! {
6    static ref TSS: TaskStateSegment = {
7        let mut tss = TaskStateSegment::new();
8        tss
9    };
10}
11
12

上述代码中我们使用lazy_static宏完成对TSS静态初始化,lazy_static是在第一次调用时进行初始化,而非编译时,这样我们几乎可以在初始化块中执行所有操作,甚至可以读取运行时值

然后我们需要创建一个结构来保存初始化的段选择子


1
2
3
4
5
6
7
8
9
10
1// in kernel/src/gdt.rs
2/// 在GDT中注册过的段选择子
3struct Selectors {
4   /// 代码段选择子
5    code_selector: SegmentSelector,
6    /// Tss段选择子
7    tss_selector: SegmentSelector,
8}
9
10

初始化GDT

与初始化TSS一样我们借助lazy_static完成GDT的初始化操作


1
2
3
4
5
6
7
8
9
10
11
12
13
1// in kernel/src/gdt.rs
2lazy_static! {
3    static ref GDT:(GlobalDescriptorTable,Selectors) = {
4        let mut gdt = GlobalDescriptorTable::new();
5        // 加载内核代码段描述符
6        let code_selector = gdt.add_descriptor(Descriptor::kernel_code_segment());
7        // 加载TSS段描述符
8        let tss_selector = gdt.add_descriptor(Descriptor::tss_segment(&TSS));
9        (gdt,Selectors{code_selector,tss_selector})
10    };
11}
12
13

到现在为止我们只是使用lazy_static初始化,但是在编译时lazy_static中的内容不会执行需要调用时才能执行,因此我们需要提供init函数来调用让其生效


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
1// in kernel/src/gdt.rs
2pub fn init() {
3    use system::ia_32e::instructions::segmention::set_cs;
4    use system::ia_32e::instructions::tables::load_tss;
5   // 加载GDT
6    GDT.0.load();
7
8    unsafe {
9       // 设置内核代码段
10        set_cs(GDT.1.code_selector);
11        // 设置TSS段
12        load_tss(GDT.1.tss_selector);
13    }
14}
15
16

初始化IDT

我们创建一个新的文件kernel/src/idt.rs
跟之前一样,在kernel/src/idt.rs中使用lazy_static完IDT初始化操作


1
2
3
4
5
6
7
8
9
10
11
12
1// in kernel/src/idt.rs
2use system::ia_32e::descriptor::idt::InterruptDescriptorTable;
3use lazy_static::lazy_static;
4
5lazy_static! {
6    static ref IDT: InterruptDescriptorTable = {
7        let mut idt = InterruptDescriptorTable::new();
8        idt
9    }
10}
11
12

因为我们把不同的描述符初始化放在不同的文件中,主要是因为IDT以后添加的内容非常的多,不同的描述符放在一起可读性不是很好,因此IDT的处理放在单独的文件中,我们提供一个对外公开的函数来完成IDT初始化


1
2
3
4
5
6
1// in kernel/src/idt.rs
2pub fn init() {
3    IDT.load();
4}
5
6

初始化8259A

还记得嘛?PIC使用级联的方式,我们需要定义2个常数,代表主表IRQ0起始向量和从片IRQ8起始向量


1
2
3
4
5
1// in kernel/src/idt.rs
2pub const PIC_MAIN: u8 = 32;
3pub const PIC_SLAVE: u8 = PIC_MAIN + 8;
4
5

从片起始向量和主片起始向量相差8,因此我们只需要在IRQ0的基础上+8就可以了
随后我们开始定义PIC,在定义PIC时需要注意,端口也是临界资源,我们以后会实现多进程,多线程,为了方式以后产生一些不必要的麻烦,我们对PIC使用我们自己编写的自旋锁


1
2
3
4
5
1use system::mutex::Mutex;
2use system::ia_32e::cpu::pic::ChainedPics;
3pub static PICS: Mutex<ChainedPics> = Mutex::new(unsafe { ChainedPics::new(PIC_MAIN, PIC_SLAVE) });
4
5

好了,定义完毕后我们在kernel/src/lib.rs中提供一个公开的init_descriptor方法来合并以上操作


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
1#![no_std]
2#![feature(abi_x86_interrupt)]
3
4pub mod vga;
5pub mod idt;
6pub mod gdt;
7
8pub fn init_descriptor() {
9   // idt初始化操作
10    idt::init();
11    // GDT和TSS初始化
12    gdt::init();
13    unsafe {
14        // 初始化PIC 8259
15        idt::PICS.lock().initialize();
16    }
17    // 开启中断
18    system::ia_32e::instructions::interrupt::enable();
19}
20
21

enable函数定义在 system/ia_32e/instructions/interrupt.rs文件中,该函数主要使用了sti开启中断


1
2
3
4
5
6
7
8
9
10
1// in  system/ia_32e/instructions/interrupt.rs
2/// 开启中断,已经使用unsafe包裹
3#[inline]
4pub fn enable() {
5    unsafe {
6        asm!("sti" :::: "volatile");
7    }
8}
9
10

与之对应的还有屏蔽中断指令cli


1
2
3
4
5
6
7
8
9
1/// 屏蔽中断,已经使用unsafe包裹
2#[inline]
3pub fn disable() {
4    unsafe {
5        asm!("cli" :::: "volatile");
6    }
7}
8
9

最后我们在main函数中调用完成初始化操作


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
1// in kernel/src/main.rs
2#![no_std]
3#![no_main]
4
5#[macro_use]
6extern crate kernel;
7
8use core::panic::PanicInfo;
9use kernel::{init_descriptor, vga};
10
11#[no_mangle]
12pub extern "C" fn _start() -> ! {
13    println!("Hello World");
14    init_descriptor();
15    loop{}
16}
17
18

好了到此为止,我们完成了GDT,TSS,IDT的初始化以及加载工作现在我们可以使用以下命令编译好我们的系统了


1
2
3
1cargo xbuild --target x86-64.json
2
3

编译完毕后我们通过qemu来启动系统


1
2
3
1qemu-system-x86_64 -drive format=raw,file=target/x86-64/debug/bootimage-kernel.bin
2
3

关于调试

我们使用qemu来启动系统,qemu是支持GDB调试的,下面介绍如何使用GDB进行调试
首先我们在终端 输入gdb


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
1admin@admin:~/OperatingSystem/src$ gdb
2GNU gdb (Ubuntu 8.1-0ubuntu3) 8.1.0.20180409-git
3Copyright (C) 2018 Free Software Foundation, Inc.
4License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
5This is free software: you are free to change and redistribute it.
6There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
7and "show warranty" for details.
8This GDB was configured as "x86_64-linux-gnu".
9Type "show configuration" for configuration details.
10For bug reporting instructions, please see:
11<http://www.gnu.org/software/gdb/bugs/>.
12Find the GDB manual and other documentation resources online at:
13<http://www.gnu.org/software/gdb/documentation/>.
14For help, type "help".
15Type "apropos word" to search for commands related to "word".
16>>>
17
18

看到一下界面表示gdb进入成功
新开一个终端然后用以下命令我们启用qemu


1
2
3
1qemu-system-x86_64 -s -S -m 512 -hda target/x86-64/debug/bootimage-kernel.bin
2
3

然后显示如下结果


1
2
3
4
5
6
7
1qemu-system-x86_64 -s -S -m 512 -hda target/x86-64/debug/bootimage-kernel.bin
2WARNING: Image format was not specified for 'target/x86-64/debug/bootimage-kernel.bin' and probing guessed raw.
3         Automatically detecting the format is dangerous for raw images, write operations on block 0 will be restricted.
4         Specify the 'raw' format explicitly to remove the restrictions.
5qemu-system-x86_64: warning: TCG doesn't support requested feature: CPUID.01H:ECX.vmx [bit 5]
6
7

表示现在等待gdb的链接
在打开GDB的终端中输入target remote localhost:1234(默认是1234)
或者可以使用LLDB进行调试,进入LLDB终端模式后输入LLDB gdb-remote 1234来链接

gdb链接完毕后结果如下
现在就可以使用gdb进行调试了

系统运行效果如下

貌似系统什么也没做,就在屏幕显示了一个Hello World!,不要灰心,我们在后期会做一个用户界面的~

下一步要做什么

在下一篇文章中我们开始编写时钟中断,以及为键盘编写驱动程序,大家加油~

给TA打赏
共{{data.count}}人
人已打赏
安全技术

C/C++内存泄漏及检测

2022-1-11 12:36:11

安全技术

ELK自动安装脚本

2021-8-18 16:36:11

个人中心
购物车
优惠劵
今日签到
有新私信 私信列表
搜索