Building an OS from Scratch (Day 2: Reading Files from Disk and Accepting User Input.)
5 min readJun 5, 2025
🔧 What We’re Doing Today
Welcome back! It’s been a while since our first post on February 10, 2025, where we printed our first “Hello World” from the bootloader.
Today, we’re taking a step forward by reading a file from disk, and accepting user input from the keyboard. That means we’re introducing:
- Basic file table logic
- Reading sectors from the disk
- A mini text input prompt
- Dynamic messages like Hello your username is:
Adarsh,
⚙️ Tools Used
Same as before:
- NASM for assembling code
- QEMU for emulation
- Text editor (Nano or VS Code)
- WSL / Linux terminal
📁 Directory Structure
TWZ/
├── src/
│ └── main.asm
├── build/
│ ├── main.bin
│ └── floppy.img
🧠 Concept Overview
Today’s bootloader will:
- Load itself to memory
- Load a file table from sector 2
- Check if the file “HELLO” exists
- Load the content of that file into memory
- Print the content using
BIOS
- Ask for the user’s name
- Display a greeting with that name
📜 File Table Format
Stored at sector 2.
Each entry is:
filename (8 bytes) | size (1 byte) | start_sector (1 byte)
So to look for a file named “HELLO”, we just compare the first 8 bytes of each entry in memory with 'HELLO '
.
🧾 Assembly Code
org 0x7C00
bits 16
start:
jmp main
a; =============================
; Print string pointed by SI
; =============================
puts:
push si
push ax
.print:
lodsb
or al, al
jz .done
mov ah, 0x0E
int 0x10
jmp .print
.done:
pop ax
pop si
ret
; =============================
; Get a key from keyboard (result in AL)
; =============================
getchar:
mov ah, 0x00
int 0x16
ret
; =============================
; Read a line into ES:DI
; Max 32 chars, terminates on Enter
; =============================
read_line:
xor cx, cx
.next_char:
call getchar
cmp al, 0x0D ; Enter key?
je .done
; Echo the character
mov ah, 0x0E
int 0x10
stosb
inc cx
cmp cx, 32
jne .next_char
.done:
mov al, 0
stosb
ret
; =============================
; Read sector into ES:BX
; DL = sector number
; =============================
read_sector:
push ax
push dx
mov ah, 0x02
mov al, 1
mov ch, 0
mov cl, dl
inc cl ; Convert to 1-based
mov dh, 0
mov dl, 0x00
int 0x13
pop dx
pop ax
ret
main:
xor ax, ax
mov ds, ax
mov es, ax
mov ss, ax
mov sp, 0x7C00
; === Print boot message ===
mov si, msg_boot
call puts
; === Read file table from sector 2 ===
mov bx, 0x8000
mov dl, 2
call read_sector
; === Search for file "HELLO" ===
mov si, 0x8000
mov di, filename
mov cx, 8
repe cmpsb
jne notfound
; === Load the file ===
mov al, [si] ; size
mov dl, [si+1] ; start sector
mov bx, 0x9000
call read_sector
; === Print file content ===
mov si, 0x9000
call puts
; === Ask for user input ===
mov si, newline
call puts
mov si, msg_prompt
call puts
mov di, 0x9100
mov es, ax
call read_line
; === Final greeting ===
mov si, newline
call puts
mov si, msg_hello
call puts
mov ax, es
mov ds, ax
mov si, 0x9100
call puts
mov si, newline
call puts
mov si, msg_tail
call puts
hang:
jmp hang
notfound:
mov si, msg_nf
call puts
jmp hang
; =============================
; Data
; =============================
filename: db 'HELLO '
msg_boot: db 'Booting OS...', 0x0D, 0x0A, 0
msg_nf: db 'File Not Found!', 0x0D, 0x0A, 0
msg_prompt: db 'Enter your name: ', 0
msg_hello: db 'Hello your username is: ', 0
msg_tail: db 0x0D, 0x0A, 'welcome to the', 0x0D, 0x0A, 0
newline: db 0x0D, 0x0A, 0
times 510 - ($ - $$) db 0
dw 0xAA55org 0x7C00
bits 16
start:
jmp main
; Print string at SI
puts:
push si
push ax
.loop:
lodsb
or al, al
jz .done
mov ah, 0x0E
int 0x10
jmp .loop
.done:
pop ax
pop si
ret
; Get a character from keyboard
getchar:
mov ah, 0x00
int 0x16
ret
; Read up to 32 characters to ES:DI
read_line:
xor cx, cx
.next:
call getchar
cmp al, 0x0D
je .done
mov ah, 0x0E
int 0x10
stosb
inc cx
cmp cx, 32
jne .next
.done:
mov al, 0
stosb
ret
; Read 1 sector from DL into ES:BX
read_sector:
push ax
push dx
mov ah, 0x02
mov al, 1
mov ch, 0
mov cl, dl
inc cl
mov dh, 0
mov dl, 0x00
int 0x13
pop dx
pop ax
ret
main:
xor ax, ax
mov ds, ax
mov es, ax
mov ss, ax
mov sp, 0x7C00
mov si, msg_boot
call puts
; Load file table from sector 2
mov bx, 0x8000
mov dl, 2
call read_sector
; Search for 'HELLO'
mov si, 0x8000
mov di, filename
mov cx, 8
repe cmpsb
jne notfound
; Found! Load file
mov al, [si]
mov dl, [si+1]
mov bx, 0x9000
call read_sector
; Print file
mov si, 0x9000
call puts
; Ask name
mov si, newline
call puts
mov si, msg_prompt
call puts
mov di, 0x9100
call read_line
; Greet user
mov si, newline
call puts
mov si, msg_hello
call puts
mov si, 0x9100
call puts
mov si, msg_tail
call puts
hang:
jmp hang
notfound:
mov si, msg_nf
call puts
jmp hang
; --- Data ---
filename: db 'HELLO '
msg_boot: db 'Booting OS...', 0x0D, 0x0A, 0
msg_nf: db 'File Not Found!', 0x0D, 0x0A, 0
msg_prompt: db 'Enter your username: ', 0
msg_hello: db 'Hello ', 0
msg_tail: db ', welcome to the OS!', 0x0D, 0x0A, 0
newline: db 0x0D, 0x0A, 0
times 510 - ($ - $$) db 0
dw 0xAA55
🛠️ Compile & Run
nasm -f bin src/main.asm -o build/main.bin
dd if=/dev/zero of=build/floppy.img bs=512 count=2880
dd if=build/main.bin of=build/floppy.img bs=512 count=1 conv=notrunc
printf 'HELLO \x01\x03' | dd of=build/floppy.img bs=512 seek=2 conv=notrunc
printf 'Welcome to my OS!\r\n\x00' | dd of=build/floppy.img bs=512 seek=3 conv=notrunc
qemu-system-x86_64 -fda build/floppy.img
📘 What You Learned Today
- Loading extra sectors (file table & data)
- Comparing file names
- Reading user input
- Printing dynamic messages
- Expanding bootloader functionality
🔮 What’s Next?
On Day 3, we’ll:
- Add multiple file support
- Print a menu of files
- Let the user choose which file to read!