Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

The Shell — NPS (Not a POSIX Shell)

With keyboard input working, we build a simple interactive shell. The name is honest about its limitations.

Design

The shell maintains a command buffer. Each keystroke either appends to the buffer, handles a special key (backspace, enter), or is ignored. On Enter, the buffer is parsed and executed.

#![allow(unused)]
fn main() {
const MAX_COMMAND_LEN: usize = 64;

pub struct NPShell {
    buffer: [u8; MAX_COMMAND_LEN],
    pos: usize,
}
}

Fixed-size buffer on the stack — no heap allocation needed.

Input Handling

#![allow(unused)]
fn main() {
pub fn handle_char(&mut self, ch: u8) {
    match ch {
        b'\n' => {
            self.execute();
            self.clear();
            println!();
            self.show_prompt();
        }
        0x08 => {  // Backspace
            if self.pos > 0 {
                self.pos -= 1;
                writer().backspace();
            }
        }
        0x20..=0x7E => {  // Printable ASCII
            if self.pos < MAX_COMMAND_LEN - 1 {
                self.buffer[self.pos] = ch;
                self.pos += 1;
                print!("{}", ch as char);
            }
        }
        _ => {}  // Ignore everything else
    }
}
}

Command Dispatch

#![allow(unused)]
fn main() {
fn execute(&self) {
    let cmd = core::str::from_utf8(&self.buffer[..self.pos]).unwrap_or("");

    match cmd.trim() {
        "help"   => self.cmd_help(),
        "stack"  => self.cmd_stack(),
        "gdt"    => self.cmd_gdt(),
        "clear"  => self.cmd_clear(),
        "about"  => self.cmd_about(),
        "reboot" => self.cmd_reboot(),
        "halt"   => self.cmd_halt(),
        "42"     => self.cmd_42(),
        ""       => {}
        _        => println!("Unknown command: '{}'. Type 'help' for commands.", cmd),
    }
}
}

core::str::from_utf8 — Convert byte buffer to string slice. Can fail if the buffer contains invalid UTF-8, hence the unwrap_or("") fallback.

Commands

CommandFunction
helpList available commands
stackPrint kernel stack contents (ESP, EBP, top 16 dwords)
gdtDump all GDT entries
clearClear screen
aboutKernel information
haltcli; hlt — stop everything
rebootPulse keyboard controller reset line
42Print the obligatory 42 logo

Reboot Implementation

#![allow(unused)]
fn main() {
fn cmd_reboot(&self) {
    unsafe {
        // Wait for keyboard controller to be ready
        let mut port: u8;
        loop {
            core::arch::asm!("in al, dx", out("al") port, in("dx") 0x64u16);
            if (port & 0x02) == 0 { break; }
        }
        // Pulse the CPU reset line via keyboard controller port
        core::arch::asm!("out dx, al", in("dx") 0x64u16, in("al") 0xFEu8);

        // Fallback: triple fault
        core::arch::asm!("cli; hlt", options(noreturn));
    }
}
}

The keyboard controller (port 0x64) has a line connected to the CPU reset pin. Writing 0xFE pulses it low, resetting the CPU. This is the standard software reboot method on x86.

Global Instance

#![allow(unused)]
fn main() {
static mut NPSHELL: NPShell = NPShell::new();

pub fn handle_input(ch: u8) {
    unsafe {
        let shell = &mut *core::ptr::addr_of_mut!(NPSHELL);
        shell.handle_char(ch);
    }
}
}

The shell is a static global, accessed via addr_of_mut! for the same reasons as the VGA writer.

The keyboard handler now calls nps::handle_input(ascii) instead of printing directly — the shell owns all display of typed characters.