Giao diện
OS Development Basics Kernel
Từ Power-On đến Hello World trong Kernel
1. Boot Process Overview
BIOS/UEFI → Kernel
BOOT SEQUENCE
┌─────────────────────────────────────────────────────────────────┐
│ │
│ Power On │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ BIOS / UEFI │ │
│ │ • POST (Power-On Self-Test) │ │
│ │ • Initialize hardware │ │
│ │ • Find boot device │ │
│ │ • Load bootloader to 0x7C00 │ │
│ └───────────────────────────┬─────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ Bootloader │ │
│ │ • Switch to 32-bit Protected Mode │ │
│ │ • Enable paging (optional) │ │
│ │ • Load kernel to memory │ │
│ │ • Jump to kernel entry point │ │
│ └───────────────────────────┬─────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ Kernel (Rust) │ │
│ │ • Setup stack │ │
│ │ • Initialize IDT │ │
│ │ • Setup paging │ │
│ │ • Initialize drivers │ │
│ │ • Start scheduler │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘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
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
Project Structure
my_os/
├── Cargo.toml
├── x86_64-my_os.json # Custom target
├── .cargo/
│ └── config.toml # Build settings
├── src/
│ ├── main.rs # Kernel entry
│ ├── vga_buffer.rs # VGA text mode
│ └── interrupts.rs # IDT setup
└── bootloader/ # Or use bootloader crate1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
2. Inline Assembly
Syntax cơ bản
rust
use core::arch::asm;
/// Read from I/O port
unsafe fn inb(port: u16) -> u8 {
let value: u8;
asm!(
"in al, dx",
in("dx") port,
out("al") value,
options(nomem, nostack)
);
value
}
/// Write to I/O port
unsafe fn outb(port: u16, value: u8) {
asm!(
"out dx, al",
in("dx") port,
in("al") value,
options(nomem, nostack)
);
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
CPU Control Instructions
rust
/// Halt CPU until next interrupt
pub fn hlt() {
unsafe {
asm!("hlt", options(nomem, nostack));
}
}
/// Disable interrupts
pub fn cli() {
unsafe {
asm!("cli", options(nomem, nostack));
}
}
/// Enable interrupts
pub fn sti() {
unsafe {
asm!("sti", options(nomem, nostack));
}
}
/// Read CR3 (page table base)
pub fn read_cr3() -> u64 {
let value: u64;
unsafe {
asm!("mov {}, cr3", out(reg) value, options(nomem, nostack));
}
value
}
/// Load IDT
pub unsafe fn lidt(idtr: &DescriptorTablePointer) {
asm!("lidt [{}]", in(reg) idtr, options(readonly, nostack));
}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
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
Stack Setup
rust
// Trong assembly boot code
core::arch::global_asm!(r#"
.section .bss
.align 16
stack_bottom:
.space 16384 # 16 KB stack
stack_top:
.section .text
.global _start
_start:
# Setup stack
mov rsp, offset stack_top
# Call Rust entry point
call kernel_main
# Should never return
1: hlt
jmp 1b
"#);
)1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
3. Interrupt Descriptor Table (IDT)
IDT Structure
INTERRUPT DESCRIPTOR TABLE
┌─────────────────────────────────────────────────────────────────┐
│ │
│ Entry 0: Division Error (#DE) │
│ Entry 1: Debug (#DB) │
│ Entry 2: NMI │
│ Entry 3: Breakpoint (#BP) │
│ ... │
│ Entry 13: General Protection Fault (#GP) │
│ Entry 14: Page Fault (#PF) │
│ ... │
│ Entry 32-255: User-defined interrupts │
│ │
│ Each entry (16 bytes in x86_64): │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ Offset 0:15 │ Selector │ IST │ Type│ Offset 16:31 │ │
│ ├─────────────────────────────────────────────────────────┤ │
│ │ Offset 32:63 │ Reserved │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
IDT Implementation
rust
use core::arch::asm;
/// IDT Entry (Gate Descriptor)
#[repr(C, packed)]
#[derive(Clone, Copy)]
pub struct IdtEntry {
offset_low: u16,
selector: u16,
ist: u8,
type_attr: u8,
offset_mid: u16,
offset_high: u32,
reserved: u32,
}
impl IdtEntry {
pub const fn missing() -> Self {
Self {
offset_low: 0,
selector: 0,
ist: 0,
type_attr: 0,
offset_mid: 0,
offset_high: 0,
reserved: 0,
}
}
pub fn set_handler(&mut self, handler: extern "x86-interrupt" fn()) {
let addr = handler as u64;
self.offset_low = addr as u16;
self.offset_mid = (addr >> 16) as u16;
self.offset_high = (addr >> 32) as u32;
self.selector = 0x08; // Code segment
self.type_attr = 0x8E; // Present, DPL=0, Interrupt Gate
self.ist = 0;
self.reserved = 0;
}
}
/// IDT Table
#[repr(C, align(16))]
pub struct Idt {
entries: [IdtEntry; 256],
}
impl Idt {
pub const fn new() -> Self {
Self {
entries: [IdtEntry::missing(); 256],
}
}
pub fn load(&'static self) {
let ptr = DescriptorTablePointer {
limit: (core::mem::size_of::<Self>() - 1) as u16,
base: self as *const _ as u64,
};
unsafe {
asm!("lidt [{}]", in(reg) &ptr, options(readonly, nostack));
}
}
}
#[repr(C, packed)]
struct DescriptorTablePointer {
limit: u16,
base: u64,
}
// Interrupt handlers
extern "x86-interrupt" fn divide_error_handler() {
panic!("EXCEPTION: Divide Error");
}
extern "x86-interrupt" fn page_fault_handler() {
panic!("EXCEPTION: Page Fault");
}
// Setup IDT
static mut IDT: Idt = Idt::new();
pub fn init_idt() {
unsafe {
IDT.entries[0].set_handler(divide_error_handler);
IDT.entries[14].set_handler(page_fault_handler);
IDT.load();
}
}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
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
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
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
4. VGA Text Mode
Memory Layout
VGA TEXT BUFFER (0xB8000)
┌─────────────────────────────────────────────────────────────────┐
│ │
│ Address: 0xB8000 - 0xB8FA0 (80 × 25 × 2 bytes = 4000 bytes) │
│ │
│ Each character cell (2 bytes): │
│ ┌────────────┬────────────┐ │
│ │ Character │ Attribute │ │
│ │ (ASCII) │ (color) │ │
│ └────────────┴────────────┘ │
│ Byte 0 Byte 1 │
│ │
│ Attribute byte: │
│ ┌───┬───┬───┬───┬───┬───┬───┬───┐ │
│ │ 7 │ 6 │ 5 │ 4 │ 3 │ 2 │ 1 │ 0 │ │
│ └───┴───┴───┴───┴───┴───┴───┴───┘ │
│ │ └─────┬─────┘ └─────┬─────┘ │
│ │ Background Foreground │
│ └── Blink │
│ │
│ Colors: 0=Black 1=Blue 2=Green 3=Cyan 4=Red 5=Magenta │
│ 6=Brown 7=LightGray 8-15=Bright versions │
│ │
└─────────────────────────────────────────────────────────────────┘1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
VGA Writer Implementation
rust
use core::fmt;
use core::ptr::write_volatile;
const VGA_BUFFER: *mut u8 = 0xB8000 as *mut u8;
const VGA_WIDTH: usize = 80;
const VGA_HEIGHT: usize = 25;
#[repr(u8)]
#[derive(Clone, Copy)]
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,
}
pub struct VgaWriter {
col: usize,
row: usize,
color: u8,
}
impl VgaWriter {
pub const fn new() -> Self {
Self {
col: 0,
row: 0,
color: (Color::Black as u8) << 4 | (Color::LightGreen as u8),
}
}
pub fn write_byte(&mut self, byte: u8) {
match byte {
b'\n' => self.new_line(),
byte => {
if self.col >= VGA_WIDTH {
self.new_line();
}
let offset = (self.row * VGA_WIDTH + self.col) * 2;
unsafe {
write_volatile(VGA_BUFFER.add(offset), byte);
write_volatile(VGA_BUFFER.add(offset + 1), self.color);
}
self.col += 1;
}
}
}
fn new_line(&mut self) {
self.col = 0;
if self.row < VGA_HEIGHT - 1 {
self.row += 1;
} else {
self.scroll();
}
}
fn scroll(&mut self) {
// Move all lines up by 1
for row in 1..VGA_HEIGHT {
for col in 0..VGA_WIDTH {
let src = (row * VGA_WIDTH + col) * 2;
let dst = ((row - 1) * VGA_WIDTH + col) * 2;
unsafe {
let char = *VGA_BUFFER.add(src);
let attr = *VGA_BUFFER.add(src + 1);
write_volatile(VGA_BUFFER.add(dst), char);
write_volatile(VGA_BUFFER.add(dst + 1), attr);
}
}
}
// Clear last line
self.clear_row(VGA_HEIGHT - 1);
}
fn clear_row(&mut self, row: usize) {
for col in 0..VGA_WIDTH {
let offset = (row * VGA_WIDTH + col) * 2;
unsafe {
write_volatile(VGA_BUFFER.add(offset), b' ');
write_volatile(VGA_BUFFER.add(offset + 1), self.color);
}
}
}
pub fn clear(&mut self) {
for row in 0..VGA_HEIGHT {
self.clear_row(row);
}
self.col = 0;
self.row = 0;
}
}
impl fmt::Write for VgaWriter {
fn write_str(&mut self, s: &str) -> fmt::Result {
for byte in s.bytes() {
self.write_byte(byte);
}
Ok(())
}
}
// Global writer
use spin::Mutex;
pub static WRITER: Mutex<VgaWriter> = Mutex::new(VgaWriter::new());
// print! macro
#[macro_export]
macro_rules! print {
($($arg:tt)*) => {{
use core::fmt::Write;
WRITER.lock().write_fmt(format_args!($($arg)*)).unwrap();
}};
}
#[macro_export]
macro_rules! println {
() => (print!("\n"));
($($arg:tt)*) => (print!("{}\n", format_args!($($arg)*)));
}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
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
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
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
5. Complete Minimal Kernel
rust
#![no_std]
#![no_main]
#![feature(abi_x86_interrupt)]
use core::panic::PanicInfo;
mod vga_buffer;
mod interrupts;
#[no_mangle]
pub extern "C" fn kernel_main() -> ! {
// Clear screen
vga_buffer::WRITER.lock().clear();
// Print hello
println!("Hello from Rust Kernel!");
println!("=====================================");
// Initialize IDT
interrupts::init_idt();
println!("[OK] IDT initialized");
// Enable interrupts
unsafe { core::arch::asm!("sti"); }
println!("[OK] Interrupts enabled");
println!("\nKernel running. Halting CPU...");
loop {
unsafe { core::arch::asm!("hlt"); }
}
}
#[panic_handler]
fn panic(info: &PanicInfo) -> ! {
println!("\n!!! KERNEL PANIC !!!");
println!("{}", info);
loop {
unsafe { core::arch::asm!("hlt"); }
}
}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
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
Build & Run
toml
# Cargo.toml
[package]
name = "my_os"
version = "0.1.0"
edition = "2021"
[dependencies]
bootloader = "0.9"
spin = "0.9"
[profile.release]
panic = "abort"
lto = true1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
bash
# Build
cargo build --release --target x86_64-unknown-none
# Run with QEMU
qemu-system-x86_64 -drive format=raw,file=target/x86_64-unknown-none/release/bootimage-my_os.bin1
2
3
4
5
2
3
4
5
🎯 OS Dev Checklist
| Step | Component |
|---|---|
| 1 | Bootloader (use bootloader crate) |
| 2 | VGA text mode output |
| 3 | Serial port debugging |
| 4 | IDT for CPU exceptions |
| 5 | Keyboard driver |
| 6 | Memory management |
| 7 | Process scheduling |
💡 RESOURCE
Xem thêm: Writing an OS in Rust - Tutorial series xuất sắc.