File reading in real mode OS

Question about which tools to use, bugs, the best way to implement a function, etc should go here. Don't forget to see if your question is answered in the wiki first! When in doubt post here.
Post Reply
PRoX
Posts: 7
Joined: Thu Apr 17, 2025 1:58 am
Libera.chat IRC: PRoX

File reading in real mode OS

Post by PRoX »

Hello guys.

I am a beginner OS developer. Recently I wrote a simple, 16-bit operating system. I added FAT12 as a file system.

So what I did was:
- FAT12 bootloader (same as MikeOS bootloader)
- DIR command in the system kernel

And now I need to implement functionality for reading and displaying text files. But I don't have the necessary knowledge to implement this kind of thing, so I decided to ask here, because I'm at a dead end.

Please help me


Kernel source code:

Code: Select all

; ==================================================================
; x16-PRos -- The x16-PRos Operating System kernel
; Copyright (C) 2025 PRoX2011
;
; This is loaded from the second disk sector by BOOT.BIN
; ==================================================================

[BITS 16]
[ORG 0x0000]

start:
    mov ax, 0x2000
    mov ds, ax
    mov es, ax
    mov ss, ax
    mov sp, 0xFFFE

    cli
    call set_video_mode

    ; Set up frequency (1193180 Hz / 1193 = ~1000 Hz)
    mov al, 0xB6
    out 0x43, al
    mov ax, 1193
    out 0x42, al
    mov al, ah
    out 0x42, al

    call print_interface    ; Help menu and headler
    mov si, start_melody
    call play_melody        ; Startup melody
    call shell              ; PRos terminal
    jmp $

set_video_mode:
    ; VGA 640*460, 16 colors
    mov ax, 0x12
    int 0x10
    ret

print_string:
    mov ah, 0x0E
    mov bl, 0x0F
.print_char:
    lodsb
    cmp al, 0
    je .done
    int 0x10
    jmp .print_char
.done:
    ret

print_newline:
    mov ah, 0x0E
    mov al, 0x0D
    int 0x10
    mov al, 0x0A
    int 0x10
    ret

; ===================== Colored prints =====================

; ------ Green ------
print_string_green:
    mov ah, 0x0E
    mov bl, 0x0A
.print_char:
    lodsb
    cmp al, 0
    je .done
    int 0x10
    jmp .print_char
.done:
    ret
    
; ------ Cyan ------
print_string_cyan:
    mov ah, 0x0E
    mov bl, 0x0B
.print_char:
    lodsb
    cmp al, 0
    je .done
    int 0x10
    jmp .print_char
.done:
    ret
    
; ------ Red ------
print_string_red:
    mov ah, 0x0E
    mov bl, 0x0C
.print_char:
    lodsb
    cmp al, 0
    je .done
    int 0x10
    jmp .print_char
.done:
    ret


print_interface:
    mov si, header
    call print_string
    call print_newline
    mov si, menu
    call print_string_green
    call print_newline
    ret

print_help:
    mov si, menu
    call print_string_green
    call print_newline
    ret

shell:
    mov si, prompt
    call print_string
    call read_command
    call print_newline
    call execute_command
    jmp shell

read_command:
    mov di, command_buffer
    xor cx, cx
.read_loop:
    mov ah, 0x00
    int 0x16
    cmp al, 0x0D
    je .done_read
    cmp al, 0x08
    je .handle_backspace
    cmp cx, 255
    jge .done_read
    stosb
    mov ah, 0x0E
    mov bl, 0x1F
    int 0x10
    inc cx
    jmp .read_loop

.handle_backspace:
    cmp di, command_buffer
    je .read_loop
    dec di
    dec cx
    mov ah, 0x0E
    mov al, 0x08
    int 0x10
    mov al, ' '
    int 0x10
    mov al, 0x08
    int 0x10
    jmp .read_loop

.done_read:
    mov byte [di], 0
    ret

execute_command:
    mov si, command_buffer
    ; Checking the command "help"
    mov di, help_str
    call compare_strings
    je do_help
    
    mov si, command_buffer
    ; Checking the command "info"
    mov di, info_str
    call compare_strings
    je print_OS_info

    mov si, command_buffer
    ; Checking the command "cls"
    mov di, cls_str
    call compare_strings
    je do_cls
    
    mov si, command_buffer
    ; Checking the command "CPU"
    mov di, CPU_str
    call compare_strings
    je do_CPUinfo
    
    mov si, command_buffer
    ; Checking the command "date"
    mov di, date_str
    call compare_strings
    je print_date
    
    mov si, command_buffer
    ; Checking the command "time"
    mov di, time_str
    call compare_strings
    je print_time

    mov si, command_buffer
    ; Checking the command "shut"
    mov di, shut_str
    call compare_strings
    je do_shutdown
    
    mov si, command_buffer
    ; Checking the command "reboot"
    mov di, reboot_str
    call compare_strings
    je do_reboot

    mov si, command_buffer
    ; Checking the command "dir"
    mov di, dir_str
    call compare_strings
    je list_directory
    
    call unknown_command
    ret

compare_strings:
    xor cx, cx
.next_char:
    lodsb
    cmp al, [di]
    jne .not_equal
    cmp al, 0
    je .equal
    inc di
    jmp .next_char
.not_equal:
    ret
.equal:
    ret

do_help:
    call print_newline
    call print_help
    call print_newline
    ret

do_cls:
    pusha
    mov ax, 0x12
    int 0x10
    popa
    ret

unknown_command:
    mov si, unknown_msg
    call print_string_red
    call print_newline
    ret

do_shutdown:
    mov si, shut_melody
    call play_melody
    mov ax, 0x5307
    mov bx, 0x0001
    mov cx, 0x0003
    int 0x15
    ret
    
do_reboot:
    int 0x19
    ret
   
print_OS_info:
    mov si, info
    call print_string_green
    call print_newline
    ret


; ===================== CPU info functions ===================== 

print_edx:
    mov ah, 0eh
    ; mov bh, 0 (Use it for very old BIOS)
    mov bx, 4
.loop4r:
    mov al, dl
    int 10h
    ror edx, 8
    dec bx
    jnz .loop4r
    ret
    
print_full_name_part:
    cpuid
    push edx
    push ecx
    push ebx
    push eax
    mov cx, 4
.loop4n:
    pop edx
    call print_edx
    loop .loop4n
    ret

; ------ Print CPU cores number ------
print_cores:
    mov si, cores
    call print_string
    mov eax, 1
    cpuid
    ror ebx, 16
    mov al, bl
    call print_al
    ret

; ------ Print CPU cache line ------
print_cache_line:
    mov si, cache_line
    call print_string
    mov eax, 1
    cpuid
    ror ebx, 8
    mov al, bl
    mov bl, 8
    mul bl
    call print_al
    ret

; ------ Print CPU stepping ID ------
print_stepping:
    mov si, stepping
    call print_string
    mov eax, 1
    cpuid
    and al, 15
    call print_al
    ret
 
print_al:
    mov ah, 0
    mov dl, 10
    div dl
    add ax, '00'
    mov dx, ax

    mov ah, 0eh
    mov al, dl
    cmp dl, '0'
    jz skip_fn
    mov bl, 0x0F
    int 10h
skip_fn:
    mov al, dh
    mov bl, 0x0F
    int 10h
    ret

; ------- Print all CPU information ------   
do_CPUinfo:
    pusha
    mov si, cpu_name
    call print_string
    ; Displaying information about the CPU
    mov eax, 80000002h
    call print_full_name_part
    mov eax, 80000003h
    call print_full_name_part
    mov eax, 80000004h
    call print_full_name_part
    mov si, mt
    call print_string
    call print_cores
    mov si, mt
    call print_string
    call print_cache_line
    mov si, mt
    call print_string
    call print_stepping
    mov si, mt
    call print_string
    popa
    ret


; ===================== Date and time functions =====================

; Function for displaying date
; Displays date in DD.MM.YY format
print_date:
    mov si, date_msg
    call print_string
    
    pusha
    ; Get the date: ch - century, cl - year, dh - month, dl - day
    mov ah, 0x04
    int 0x1a

    mov ah, 0x0e

    ; Print day (dl)
    mov al, dl
    shr al, 4
    add al, '0'
    mov bl, 0x0B
    int 0x10
    mov al, dl
    and al, 0x0F
    add al, '0'
    int 0x10

    ; Print dot
    mov al, '.'
    mov bl, 0x0B
    int 0x10

    ; Print mounth (dh)
    mov al, dh
    shr al, 4
    add al, '0'
    mov bl, 0x0B
    int 0x10
    mov al, dh
    and al, 0x0F
    add al, '0'
    mov bl, 0x0B
    int 0x10

    ; Print dot
    mov al, '.'
    mov bl, 0x0B
    int 0x10

    ; Print year (cl)
    mov al, cl
    shr al, 4
    add al, '0'
    mov bl, 0x0B
    int 0x10
    mov al, cl
    and al, 0x0F
    add al, '0'
    mov bl, 0x0B
    int 0x10
    
    mov si, mt
    call print_string
    
    popa
    ret
    

; Function for displaying time
; Displays date in HH.MM.SS format
print_time:
    mov si, time_msg
    call print_string
    
    pusha
    ; Get time: ch - hours, cl - minutes, dh - seconds
    mov ah, 0x02
    int 0x1a

    mov ah, 0x0e 

    ; Print hours (ch)
    mov al, ch
    shr al, 4
    add al, '0'
    mov bl, 0x0B
    int 0x10
    mov al, ch
    and al, 0x0F
    add al, '0'
    mov bl, 0x0B
    int 0x10

    ; Print separator
    mov al, ':'
    mov bl, 0x0B
    int 0x10

    ; Print minutes (cl)
    mov al, cl
    shr al, 4
    add al, '0'
    mov bl, 0x0B
    int 0x10
    mov al, cl
    and al, 0x0F
    add al, '0'
    mov bl, 0x0B
    int 0x10

    ; Print separator
    mov al, ':'
    mov bl, 0x0B
    int 0x10

    ; Print seconds (dh)
    mov al, dh
    shr al, 4
    add al, '0'
    mov bl, 0x0B
    int 0x10
    mov al, dh
    and al, 0x0F
    add al, '0'
    mov bl, 0x0B
    int 0x10
    
    mov si, mt
    call print_string
    
    popa
    ret

; ===================== PC speaker functions =====================

; ------ Turn on the speaker ------
on_pc_speaker:
    pusha
    in al, 0x61
    or al, 0x03
    out 0x61, al
    popa
    ret

; ------ Turn off the speaker ------
off_pc_speaker:
    pusha
    in al, 0x61
    and al, 0xFC
    out 0x61, al
    popa
    ret

; ------ Startup sound ------
play_melody:
    pusha
    ; mov si, melody
.next_note:
    mov ax, [si]
    cmp ax, 0
    je .done
    mov dx, [si+2]
    add si, 4
    call set_frequency
    call on_pc_speaker
    call delay_ms
    call off_pc_speaker
    jmp .next_note
.done:
    popa
    ret

set_frequency:
    push ax
    mov al, 0xB6
    out 0x43, al
    pop ax
    out 0x42, al
    mov al, ah
    out 0x42, al
    ret

delay_ms:
    pusha
    mov ax, dx
    mov cx, 1000
    mul cx
    mov cx, dx
    mov dx, ax
    mov ah, 0x86
    int 0x15
    popa
    ret


; ========== DISK FUNCTIONS ==========

disk_reset_floppy:
    push ax
    push dx
    mov ah, 0
    mov dl, 0
    stc
    int 13h
    pop dx
    pop ax
    ret

disk_convert_l2hts:
    push bx
    push ax

    mov bx, ax
    mov dx, 0
    div word [SecsPerTrack]
    add dl, 1
    mov cl, dl
    mov ax, bx

    mov dx, 0
    div word [SecsPerTrack]
    mov dx, 0
    div word [Sides]
    mov dh, dl
    mov ch, al

    pop ax
    pop bx
    mov dl, 0
    ret


; ------ Getting FAT12 files list ------
get_file_list:
    pusha

    mov word [.file_list_tmp], ax

    mov eax, 0

    call disk_reset_floppy

    mov ax, 19
    call disk_convert_l2hts

    mov si, disk_buffer
    mov bx, si

    mov ah, 2
    mov al, 14

    pusha

.read_root_dir:
    popa
    pusha

    stc
    int 13h
    call disk_reset_floppy
    jnc .show_dir_init

    call disk_reset_floppy
    jnc .read_root_dir
    jmp .done

.show_dir_init:
    popa

    mov ax, 0
    mov si, disk_buffer

    mov word di, [.file_list_tmp]

.start_entry:
    mov al, [si+11]
    cmp al, 0Fh
    je .skip

    test al, 18h
    jnz .skip

    mov al, [si]
    cmp al, 229
    je .skip

    cmp al, 0
    je .done

    mov cx, 1
    mov dx, si

.testdirentry:
    inc si
    mov al, [si]
    cmp al, ' '
    jl .nxtdirentry
    cmp al, '~'
    ja .nxtdirentry

    inc cx
    cmp cx, 11
    je .gotfilename
    jmp .testdirentry

.gotfilename:
    mov si, dx

    mov cx, 0
.loopy:
    mov byte al, [si]
    cmp al, ' '
    je .ignore_space
    mov byte [di], al
    inc si
    inc di
    inc cx
    cmp cx, 8
    je .add_dot
    cmp cx, 11
    je .done_copy
    jmp .loopy

.ignore_space:
    inc si
    inc cx
    cmp cx, 8
    je .add_dot
    jmp .loopy

.add_dot:
    mov byte [di], '.'
    inc di
    jmp .loopy

.done_copy:
    mov byte [di], ','
    inc di

.nxtdirentry:
    mov si, dx

.skip:
    add si, 32
    jmp .start_entry

.done:
    dec di
    mov byte [di], 0

    popa
    ret

.file_list_tmp dw 0


; ------ Print list of files ------
list_directory:
    call print_newline
    
    mov cx, 0

    mov ax, dirlist
    call get_file_list

    mov si, dirlist
    mov ah, 0Eh

.repeat:
    lodsb
    cmp al, 0
    je .done

    cmp al, ','
    jne .nonewline
    pusha
    call print_newline
    popa
    jmp .repeat

.nonewline:
    int 10h
    jmp .repeat

.done:
    call print_newline
    call print_newline
    jmp shell


; ===================== Data section =====================

; ------ Headler ------
header db '============================= x16 PRos v0.4 ====================================', 0

; ------ Help menu ------
menu db 0xC9, 47 dup(0xCD), 0xBB, 10, 13 ; ╔═══════════════════════════════════════════════╗
     db 0xBA, 'Commands:                                      ', 0xBA, 10, 13  ; ║ ... ║
     db 0xBA, '  help - get list of the commands              ', 0xBA, 10, 13
     db 0xBA, '  info - print information about OS            ', 0xBA, 10, 13
     db 0xBA, '  cls - clear terminal                         ', 0xBA, 10, 13
     db 0xBA, '  shut - shutdown PC                           ', 0xBA, 10, 13
     db 0xBA, '  reboot - restart system                      ', 0xBA, 10, 13
     db 0xBA, '  date - print current date (DD.MM.YY)         ', 0xBA, 10, 13
     db 0xBA, '  time - print current time (HH.MM.SS)         ', 0xBA, 10, 13
     db 0xBA, '  CPU - print CPU info                         ', 0xBA, 10, 13
     db 0xBA, '  dir - list files on disk                     ', 0xBA, 10, 13
     db 0xC0, 47 dup(0xCD), 0xBC, 10, 13, 0 ; ╚═══════════════════════════════════════════════╝

; ------ OS info text ------
info db 10, 13
     db 0xC9, 46 dup(0xCD), 0xBB, 10, 13 ; ╔══════════════════════════════════════════════╗
     db 0xBA, '  x16 PRos is the simple 16 bit operating     ', 0xBA, 10, 13 ; ║ ... ║
     db 0xBA, '  system written in NASM for x86 PC`s         ', 0xBA, 10, 13
     db 0xC3, 46 dup(0xC4), 0xB4, 10, 13 ; ╠══════════════════════════════════════════════╣
     db 0xBA, '  Autor: PRoX                                 ', 0xBA, 10, 13
     db 0xBA, '  Video mode: 0x12 (640x480; 16 colors)       ', 0xBA, 10, 13
     db 0xBA, '  File system: FAT12                          ', 0xBA, 10, 13
     db 0xBA, '  License: MIT                                ', 0xBA, 10, 13
     db 0xBA, '  Version: 0.4                                ', 0xBA, 10, 13
     db 0xC0, 46 dup(0xCD), 0xBC, 10, 13, 0 ; ╚══════════════════════════════════════════════╝

; ------ Commands ------
help_str       db 'help', 0
info_str       db 'info', 0
cls_str        db 'cls', 0
shut_str       db 'shut', 0
reboot_str     db 'reboot', 0
CPU_str        db 'CPU', 0
date_str       db 'date', 0
time_str       db 'time', 0
dir_str        db 'dir', 0

unknown_msg    db 'Unknown command.', 0 ; Uncnow command message
prompt         db '[PRos] > ', 0        ; Terminal prompt
mt             db '', 10, 13, 0         ; \n
command_buffer db 128 dup(0)            ; Buffer for entered command
dirlist        times 1024 db 0          ; Buffer for directory list


; ------ CPU ------
cpu_name       db '  CPU name: ', 0
cores          db '  CPU cores: ', 0
stepping       db '  Stepping ID: ', 0
cache_line     db '  Cache line: ', 0
time_msg       db 'Current time: ', 0
date_msg       db 'Current date: ', 0

; ------ Sounds ------
start_melody:
    dw 1811, 250   ; E5
    dw 1015, 250   ; D6
    dw 761, 250    ; G6
    dw 0, 0        ; Melody end

shut_melody:
    dw 761, 250    ; G6
    dw 1015, 250   ; D6
    dw 1811, 250   ; E5
    dw 0, 0        ; Melody end

error_sound:
    dw 2415, 250   ; B4
    dw 2415, 250   ; B4
    dw 0, 0        ; Melody end

disk_buffer    times 8192 db 0    ; Disk buffer (8 KB)
Sides          dw 2               ; Number of floppy sides (heads)
SecsPerTrack   dw 18              ; Sectors per track
I have reread articles about FAT12 several times, but have not achieved anything. Of course, I would like to receive a ready implementation with explanations.
User avatar
iansjack
Member
Member
Posts: 4792
Joined: Sat Mar 31, 2012 3:07 am
Location: Chichester, UK

Re: File reading in real mode OS

Post by iansjack »

It’s pretty straightforward. The directory entry tells you the first logical cluster of the file. Look at the entry for that in the FAT; that tells you the next logical cluster and so on until you reach a value between 0xFF8 and 0xFFF which indicates the last cluster. https://www.sqlpassion.at/archive/2022/ ... partition/

The only difficulty is that each FAT entry is one and a half bytes. You’d actually find it easier to implement FAT16, where each FAT entry occupies exactly tw bytes. That makes the calculations slightly more straightforward.

Honestly, if you trying to learn I wouldn’t ask for complete implementations. You’ll learn far more by producing your own implementation given the details of how something works. (And, to be brutally honest, with something as well documented as the FAT file system you’d learn more by searching for that information yourself. The most important lesson you can learn in any sort of development is not how things work but how to find out how things work. You are lucky to be living in the world of the Internet when there is a wealth of easily found information about all aspects of computer programming. And it’s all free, as is the software!)
Post Reply