Focus: PS/2 keyboard controller interface (port
0x60), scancode state machine decoding, thread-safe input ring buffers, hierarchical in-memory Virtual Filesystem (VFS), and interactive shell REPL with ANSI color formatting.
| Subsystem Component | Source Location | Design Decision | Key Systems Benefit |
|---|---|---|---|
| PS/2 Controller Port | src/interrupts.rs (L215) | Port::new(0x60).read() on IRQ 1 |
Low-latency asynchronous event reading directly off motherboard bus. |
| Keyboard Buffer | src/keyboard.rs (L8) | spin::Mutex<VecDeque<u8>> with hlt sleep |
Decouples interrupt servicing from shell string processing without busy-waiting. |
| In-Memory VFS | src/fs.rs (L19) | Recursive Directory and File tree structures |
Supports dynamic file creation, writing, reading, and subdirectories in RAM. |
| Interactive Shell REPL | src/shell.rs (L19) | Preemptive kernel task with command parser | Interactive CLI providing ls, cat, touch, mkdir, cd, pwd, echo, shutdown. |
"Bridging low-level hardware interrupts to an interactive user-space shell requires building an end-to-end event and storage pipeline entirely in bare-metal Rust.
When a physical key is pressed, the PS/2 keyboard controller asserts IRQ 1 (Vector 33). In interrupts.rs, the keyboard ISR reads the raw scancode from I/O port 0x60. Because keys emit multiple scancodes for make/break and modifier keys like Shift and Caps Lock, the ISR passes the byte through a stateful decoder (pc-keyboard). Once a printable Unicode character or control key is resolved, it is pushed into a thread-safe circular buffer (INPUT_BUFFER) in keyboard.rs, and an EOI is dispatched to the PIC.
The interactive shell runs as an independent preemptive kernel task. Rather than spinning in a busy-wait loop that burns CPU cycles, read_keyboard checks the input ring buffer. If empty, it executes the x86 hlt instruction, putting the CPU into low-power sleep until the next timer or keyboard interrupt wakes it up. It handles backspace dynamically by communicating with the VGA driver to erase characters and move the cursor backward.
For data management, I implemented an in-memory Hierarchical Virtual Filesystem (VFS) in fs.rs. It models the directory hierarchy as a tree of Directory and File nodes rooted at /. Path resolution splits paths on slashes, recursively traversing directory nodes to support navigation commands like cd, relative and absolute paths, directory creation via mkdir, file creation via touch, reading via cat, and content persistence via write. This gives the kernel a complete, interactive Unix-like operating experience."
In src/interrupts.rs, the keyboard ISR reads the hardware port and decodes the event:
// src/interrupts.rs
let mut port = Port::new(0x60);
let scancode: u8 = unsafe { port.read() };
if let Ok(Some(key_event)) = keyboard.add_byte(scancode) {
if let Some(key) = keyboard.process_keyevent(key_event) {
match key {
DecodedKey::Unicode(character) => {
if character.is_ascii_graphic() || character.is_whitespace()
|| character == '\n' || character == '\x08' {
keyboard::add_to_buffer(character as u8);
}
}
DecodedKey::RawKey(_) => {}
}
}
}
In src/keyboard.rs, read_keyboard consumes characters:
// src/keyboard.rs
pub fn read_keyboard(buffer: &mut String) {
loop {
if let Some(character) = fetch_from_buffer() {
if character != b'\x08' {
print!("{}", character as char); // Local echo
}
if character == b'\n' {
if !buffer.is_empty() {
buffer.push('\n');
return;
}
} else if character == b'\x08' {
buffer.pop();
WRITER.lock().handle_backspace(); // Erase character on VGA screen
} else {
buffer.push(character as char);
}
} else {
x86_64::instructions::hlt(); // Wait for next interrupt
}
}
}
In src/fs.rs, the filesystem is represented as:
// src/fs.rs
pub struct File {
pub name: String,
pub data: Vec,
}
pub struct Directory {
pub name: String,
pub files: Vec,
pub subdirectories: Vec,
}
pub struct FileSystem {
pub root: Directory, // Root directory "/"
pub current_directory: String, // Working directory path
}
Path navigation resolves components in src/fs.rs:
// src/fs.rs
fn get_current_directory_mut(&mut self) -> Option<&mut Directory> {
let path_components: Vec<&str> = self.current_directory
.split('/')
.filter(|&s| !s.is_empty())
.collect();
let mut current = &mut self.root;
for component in path_components {
if let Some(next_dir) = current.subdirectories.iter_mut().find(|d| d.name == component) {
current = next_dir;
} else {
return None;
}
}
Some(current)
}
The shell loop in src/shell.rs evaluates commands using pattern matching:
// src/shell.rs
match cmd.as_str() {
"help" => display_help_menu(),
"exit" => break,
"shutdown" => shutdown(),
"ls" => file_system.list_files(),
"pwd" => println!("{}", file_system.current_directory),
cmd if cmd.starts_with("touch ") => {
let filename = &cmd[6..];
file_system.create_file(filename.to_string());
}
cmd if cmd.starts_with("cat ") => {
let filename = &cmd[4..];
file_system.read_file(filename);
}
cmd if cmd.starts_with("mkdir ") => {
let dirname = &cmd[6..];
file_system.create_directory(dirname.to_string());
}
cmd if cmd.starts_with("cd ") => {
let dirname = &cmd[3..];
file_system.change_directory(dirname);
}
// ...
}
Initially, typing Backspace removed the character from the internal String buffer, but the character remained visible on the VGA text console, and pressing enter resulted in unexpected command strings.
Fix: Implemented Writer::handle_backspace() in src/vga_buffer.rs, which checks if column_position > 0, decrements the column, overwrites the character cell with an empty space (0x20), and preserves background color styling.
Running the shell in QEMU and executing a typical file manipulation session:
Entering interactive shell. Type `help` for commands, or `exit` to quit.
/ >> touch rust_rocks.txt
File 'rust_rocks.txt' created.
/ >> write rust_rocks.txt Systems_programming_in_Rust_is_safe_and_fast!
Data written to file 'rust_rocks.txt'.
/ >> cat rust_rocks.txt
Content of 'rust_rocks.txt':
Systems_programming_in_Rust_is_safe_and_fast!
/ >> mkdir tests
Directory 'tests' created.
/ >> cd tests
Changed directory to '/tests'
/tests >> pwd
/tests
/tests >> cd ..
Changed directory to '/'
/ >> ls
Contents of '/':
[FILE] rust_rocks.txt
[DIR] tests
/ >>
hlt inside the keyboard input loop instead of a tight loop?"Tactical Answer: "A tight loop (loop { if let Some(c) = fetch() { ... } }) continuously burns 100% CPU cycles on the host and generates unnecessary power consumption and thermal throttling. The x86 hlt instruction instructs the CPU to stop instruction execution and enter a low-power sleep state until an unmasked hardware interrupt (like the next timer tick or keyboard keypress) arrives. This bounds CPU consumption while keeping event latency under microseconds."
Tactical Answer: "The VFS stores all directory and file nodes dynamically inside heap memory using recursive Rust vectors (Vec<Directory> and Vec<File>). Because all heap memory is backed by our fixed-size block allocator, creating, resizing, or deleting files dynamically reallocates memory from the 100 KiB heap without needing block device drivers or ATA/AHCI disk controllers."