SO Lab3
SO Lab3
Report
For laboratory work No. 3
“Floppy Disk I/O operations”
Did by:
Maria Procopii
Irina Racovcena
Maria Leșnco
gr. FAF-212
Checked by:
Rostislav Călin
Chișinău – 2023
Task: All students will form teams of 3 members from the academic group. Group
composition will be approved by the teacher, including teams of 2 or 4 members.
Teams must be formed promptly, within the time limit announced by the teacher
during class time.
The objectives of the work given by the laboratory are focused on the acquisition
of working skills with floppy disk access methods, especially reading and writing
data, but not limited to them. The given procedures also extend to other permanent
data storage media such as HDD disks.
The total disk volume of 1474560 bytes will be structured in 96 logical blocks of
30 sectors each. Each student will be allocated an individual block of 15360 bytes,
for recording his data as follows. The block distribution is represented in the
included file ("Floppy space distribution.xlsx").
Requirements:
1. In the first and last sector of each student's block (on diskette), textual
information must be entered in the following format (without quotes):
"@@@FAF-21* First name NAME###". This text string must be duplicated 10
times without additional delimiters.
Examples:
@@@FAF-212 Vlad URSU###@@@FAF-212 Vlad URSU###@@@FAF-212 Vlad
URSU###@@@FAF-212 Vlad URSU###@@@FAF-212 Vlad URSU###@@@FAF-212 Vlad
URSU###@@@FAF-212 Vlad URSU###@@@FAF-212 Vlad URSU###@@@FAF-212 Vlad
URSU###@@@FAF-212 Vlad URSU###
2. Create an assembly language program that will have the following functions:
- (KEYBOARD ==> FLOPPY) : Reading from the keyboard a string with a
maximum length of 256 characters (backspace correction should work) and
writing this string to the floppy "N" times, starting at address {Head, Track,
Sector }. After the ENTER key is detected, if the length of the string is
greater than 0 (zero), a blank line and then the newly entered string should
be displayed. The variables "N", "Head", "Track" and "Sector" must be read
visibly from the keyboard. After the disk write operation is finished, the
error code should be displayed on the screen.
- (FLOPPY ==> RAM) : Reading from floppy disk "N" sectors starting at
address {Head, Track, Sector} and transferring this data to RAM memory
starting at address {XXXX:YYYY}. After the read operation from the
diskette is finished, the error code should be displayed on the screen. After
the error code, the entire volume of data at address {XXXX:YYYY} that
was read from the disk should be displayed on the screen. If the displayed
data volume is larger than a video page, then it is necessary to implement
pagination by pressing the "SPACE" key. The variables "N", "Head",
"Track" and "Sector" as well as the address {XXXX:YYYY} must also be
read from the keyboard.
- (RAM ==> FLOPPY) : Writing to the floppy disk starting from the address
{Head, Track, Sector} a volume of "Q" bytes, from the RAM memory
starting from the address {XXXX:YYYY}. The data block of "Q" bytes
must be displayed on the screen, and after the disk write operation is
finished, the error code must be displayed on the screen.
3. After executing a function above, the program must be ready to execute the next
function (any of the 3 functions described above).
Implementation:
The bootloader is necessary so that the kernel, which has all the code for the menu
and its options, could be loaded, and executed. The bootloader should have the size
of 512 bytes of AA and 55 as the last two hexadecimal values.
Before jumping to the bootloader, let’s look at the utils folder.
print.asm
print_string_buffer:
; The function prints a string from the buffer
; stored in the SI register, it expects a
; null symbol to be found in the buffer.
; Parameters:
; si - memory offset to the buffer
pusha
.loop:
lodsb
or al, al
jz .done
; display character
mov ah, 0x0A
mov bh, 0x00
mov cx, 1
int 0x10
; move cursor
mov ah, 0x02
inc dl
int 0x10
jmp .loop
.done:
popa
ret
The function requires si register to be set. First the function is pushing all the
registers to the stack, then in a loop it loads a stored byte from the si register, while
incrementing it. If the loaded byte is zero, then the function terminates by popping
all the registers initial values from the stack, otherwise it displays a character,
updates the cursor and continues to loop once again, until it finds the zero byte.
; move cursor
mov ah, 0x02
inc dh
mov dl, 0
int 0x10
popa
ret
init_window.asm
%define color_black 0
%define color_blue 1
%define color_green 2
%define color_cyan 3
%define color_red 4
%define color_magenta 5
%define color_orange 6
%define color_gray 7
%define color_yellow 14
%define color_white 15
At the top the macros for the colors are set. This is done by the use of the define
macro from nasm.
Next the function that paints the screen in black and magenta for the font, as well
as the function that sets up the cursor are found.
init_window:
pusha
.clear_screen:
; Int 0x10
; AH = 06h
; AL = number of lines by which to scroll up (00h = clear entire window)
; BH = attribute used to write blank lines at bottom of window
; CH, CL = row, column of window's upper left corner
; DH, DL = row, column of window's lower right corner
.set_custom_cursor:
; Int 0x10
; AH = 01h
; CH = start scan line of character matrix (0-1fH; 20H=no cursor)
; CL = end scan line of character matrix (0-1fH)
The first function uses the 06H function from the 10H set of BIOS interrupts, in
order to clear the screen, paint black with magenta color for the font. Afterwards
the cursor is set by the use of the 01H function from the 10H set of BIOS
interrupts.
disk.asm
disk_load:
; The function reads DH number of sectors
; into ES:BX memory location from drive DL
; Parameters:
; es:bx - buffer memory address
mov ah, 0x02 ; INT 13H 02H, BIOS read disk sectors into memory
mov al, dh ; number of sectors
mov ch, 0x00 ; cylinder
mov dh, 0x00 ; head
mov cl, 0x02 ; start reading sector (2 is the sector after the bootloader)
int 0x13 ; BIOS interrupt for disk functions
The function requires es:bx registers to be set, this being the memory address
where the kernel will be loaded. At the beginning, the function is pushing to the
stack dx register, this is done in order to keep the initial value of the sectors that is
desired to be read, which can be handy later. After that by the use of the 02H
function from the 13H sets of BIOS interrupts a specified number of sectors are
read into the memory address. In the case of failure of the previous interrupt, a
jump will be performed to the disk_error function. Otherwise, the function will
continue to popping from the stack the previous value of the dx register and
compare it with the actual number of the read sectors, in the case of them not being
equal the same jump will be performed, and only in the case of passing all the
above checks successfully, a final jump to the disk_success will be done.
Knowing that the following labels are defined, the discussion of the last two
functions will continue:
DISK_ERROR_MESSAGE: db "ERROR: could not read from disk", 0
DISK_SUCCESS_MESSAGE: db "INFO: successfully loaded the disk", 0
The error handling is done by the following function:
disk_error:
mov si, DISK_ERROR_MESSAGE
call print_string_buffer
; move cursor
mov ah, 0x02
inc dh
mov dl, 0
int 0x10
jmp disk_load
At first the function displayes the error message, updates the cursor afterwards and
continues with the jump to the disk_load function to try once again to read the
desired number of sectors from the disk.
; move cursor
mov ah, 0x02
inc dh
mov dl, 0
int 0x10
ret
It prints the message of successfully reading loading from the disk, updates the
cursor and returns to the caller of the disk_load function.
bootloader.asm
org 0x7C00
call init_window
call print_start_up_message
%include "src/bootloader/utils/print.asm"
%include "src/bootloader/utils/disk.asm"
%include "src/bootloader/utils/init_window.asm"
times 510-($-$$) db 0
dw 0xAA55
At the beginning, we are defining the org directive which implies a memory
address, which will be the offset from which the bootloader should start. This
ensures the bootloader does not overlap with the BIOS.
After that the bootloader sets up things for later usage. The first thing that the
bootloader is doing is saving the drive number given set up by the BIOS, before it
forwards the control to the bootloader. After that the screen is cleared by the use of
the init_window function and the start-up message is printed on the screen. Next,
the dx register is set, by assigning the value 10H as the number of sectors that will
be loaded in the memory to the dh register, that being the approximate value of the
sectors that the kernel is occupying, as well as setting dl register to the value of the
drive number given by the BIOS previously.
As the kernel should also have an offset in memory, in order to not conflict with
the bootloader or other components, all other registers should also be set up prior
to the kernel loading.
After setting the segment registers, the entry point, i.e. the main logic of the
bootloader, is executed by calling the disk_load function, and making a far jump to
the segment where the kernel is loaded.
At the end of the bootloader the imports of the previously discussed files can be
found, as well as the start-up message label.
The last two lines of code, sign the bootloader with the AA 55 hex, while ensuring
that the size of it is 512 bytes.
const.asm
; signature.asm consts
signature: db "@@@FAF-212 Maria PROCOPII###", 0
; menu.asm consts
menu_prompt_option1: db "1. stdio to disk", 0
menu_prompt_option2: db "2. disk to ram", 0
menu_prompt_option3: db "3. ram to disk", 0
menu_prompt_option4: db "option := ", 0
opt_invalid: db "invalid", 0
; signature.asm vars
buffer: resb 100
The content of this file varies for all the users, as each member of the team is
assigned with his/her own sectors and signature, as described in the requirements.
The signature is the string that is associated with the student’s name and group.
The following four labels are used for printing debug information to the screen on
the runtime of the program. The start_track and start_sector are values that are
calculated from the given list in the task requirements, and are used to define the
first sector of the student. Similarly is with the end_track and end_sector.
The following labels are used to print the menu options and display the incorrectly
chosen one.
And the last label reserves a large amount of bytes to store the duplicated version
of the signature.
print.asm
print_string_buffer:
; The function prints a string from the buffer
; stored in the SI register, it expects a
; null symbol to be found in the buffer.
; Parameters:
; SI = memory offset to the buffer
; Returns:
pusha
.loop:
lodsb
or al, al
jz .done
; display character
mov ah, 0x0A
mov bh, 0x00
mov cx, 1
int 0x10
; move cursor
mov ah, 0x02
inc dl
int 0x10
jmp .loop
.done:
popa
ret
print_os_message:
; The function prints the OS message
; and moves the cursor to the next line
; Parameters: None
; Returns: None
pusha
; move cursor
mov ah, 0x02
mov dh, 2
mov dl, 0
int 0x10
; move cursor
mov ah, 0x02
inc dh
mov dl, 0
int 0x10
popa
ret
print_writing_to_disk_message_info:
pusha
; move cursor
mov ah, 0x02
mov dh, 3
mov dl, 0
int 0x10
; move cursor
mov ah, 0x02
inc dh
mov dl, 0
int 0x10
popa
ret
print_writing_to_disk_message_success:
pusha
; move cursor
mov ah, 0x02
mov dh, 4
mov dl, 0
int 0x10
; move cursor
mov ah, 0x02
inc dh
mov dl, 0
int 0x10
popa
ret
print_io_error:
pusha
; move cursor
mov ah, 0x02
mov dh, 4
mov dl, 0
int 0x10
; move cursor
mov ah, 0x02
inc dh
mov dl, 0
int 0x10
popa
jmp $
window.asm
clear_display:
; Int 0x10
; AH = 06h
; AL = number of lines by which to scroll up (00h = clear entire window)
; BH = attribute used to write blank lines at bottom of window
; CH, CL = row, column of window's upper left corner
; DH, DL = row, column of window's lower right corner
pusha
popa
ret
The above function first pushes all the registers values to the stack, after that uses
the 06H function from the 10H set of BIOS interrupts to clear the screen, then the
cursor is set at the top left corner, by the use of the 02H function from the 10H set
of BIOS interrupts. At the end the function is popping from the stack all the initial
values of the registers, after which the function returns to the caller.
signature.asm
create_signature:
; The function creates the signature, which
; is the given string (from SI) repeated 10
; times and written in the given buffer (from DI)
; The following register are set at the runtime
; SI = src (string, part of the signature)
; DI = dst (buffer where the signature will be written)
; CX = number of characters from signature
; Parameters:
; The function expects `buffer` and `signature` to be
; declared out of its scope.
; `buffer` = reserved bytes for the storage of the created signature
; `signature` = string that is part of the signature
; Returns: None
; subroutine
.copy_signature:
mov si, signature
mov cx, 25
.copy_string:
dec cx
jz .done
inc si
inc di
jmp .copy_string
.done:
ret
sign_sector:
; The function write the signature to the
; desired address based on the CHS scheme
; Parameters:
; Int 13H
; AH = 03h
; AL = sector count
; CH = track (cylinder) number
; CL = sector number
; DH = head number
; DL = drive: 0-3=diskette; 80H-81H=hard disk
; ES:BX = caller's buffer, containing data to write
; Returns:
; AH = BIOS disk error code if CF is set to CY
pusha
jc print_io_error ; if CF is set
popa
ret
print_signature:
pusha
; move cursor
mov ah, 0x02
inc dh
mov dl, 0
int 0x10
popa
ret
The first function, called create_signature is responsible for duplicating the given
signature 10 times, as specified in the requirements. It also writes the result of it
into a buffer.
The function simply calls 10 times a subroutine in order to achieve this. The
subroutine called .copy_signature, copies from the register si into the register di as
many bytes as are specified in the cx register. It does that byte by byte, while
decreasing the number in cx, which is the length of the signature.
The next function called sign_sector is responsible for writing to the disk the
duplicated string from the buffer. It does that by the use of the 03H function from
the 13H set of BIOS interrupts.
And the last function called print_signature is a helper function that can be called
to view the duplicated signature.
menu.asm
display_menu:
; read character
mov ah, 0x00
int 0x16
.clear_screen:
call clear_display
jmp display_menu
.cleared_screen:
xor ax, ax
xor dx, dx
call get_cursor_pos
; read character
mov ah, 0x00
int 0x16
; display character
mov ah, 0x0A
mov bh, 0x00
mov cx, 1
int 0x10
; move cursor
mov ah, 0x02
inc dl
int 0x10
jmp handle_invalid_option
handle_first_option:
mov ah, 0x02
inc dh
mov dl, 0
int 0x10
call first_option
jmp display_menu
handle_second_option:
mov ah, 0x02
inc dh
mov dl, 0
int 0x10
call second_option
jmp display_menu
handle_third_option:
mov ah, 0x02
inc dh
mov dl, 0
int 0x10
call third_option
jmp display_menu
handle_invalid_option:
mov ah, 0x02
inc dh
mov dl, 0
int 0x10
jmp display_menu
%include "src/kernel/utils/menu_options/common.asm"
%include "src/kernel/utils/menu_options/first_option.asm"
%include "src/kernel/utils/menu_options/second_option.asm"
%include "src/kernel/utils/menu_options/third_option.asm"
At first the display_menu function a character is input from the user. In the case of
the input character being a the Spacebar the cursor’s position is updated and the
program continues, otherwise the screen is cleared first. Afterwards the menu is
displayed, by firstly updating the cursor on the screen, with the help of the 02H
function from the 10H set of BIOS interrupts, and secondly an option is printed to
the screen, by the use of the print_string_buffer function. These instructions are
repeated a few times, one block of code for each option. After the printing of the
menu is done, an char is input from the user, then the given char is displayed on the
screen and the cursor is updated. The given char is compared to the following
chars: “1”, “2”, “3”; in the case of giving one of the previously mentioned chars, a
respective option will be executed, otherwise the invalid message is displayed.
common.asm
get_cursor_pos:
; The function obtains the current cursor position
; and the size/shape of the cursor for a specified video page.
; Parameters:
; AH = 03H
; BH = video page number
; Returns:
; CH = cursor starting scan-line
; CL = cursor ending scan-line
; DH = current row
; DL = current column
ret
The above function is using the 03H function from the 10H set of BIOS interrupts,
which sets the cursor’s scan-line value into the cx register and the current row and
column in the dx register.
empty_buffer:
; The function clears the buffer set in SI register, by
; assigning 0s into it.
; Parameters:
; SI = buffer to be emptified
; DI = the same buffer from the SI register
; but with the desired offset.
; Returns: None
pusha
.empty_buffer_loop:
mov byte[si], 0
inc si
cmp si, di
jl .empty_buffer_loop
popa
ret
This function is setting all the buffer content to the zero byte, thus emptying it. It
does that by setting the current byte of the si register to zero, incrementing the si
register value afterwards, until the si value is not the same one the di has.
null_buffers:
; The function nulls out all the buffers used by the
; menu. The indented use of it, is to emptify all the
; buffers before their usage after a successful completion
; of an selected option.
; Parameters: None
; Returns: None
pusha
popa
ret
The above function nulls out all the buffers used by the program, by the use of the
previously described function.
read_input:
; The function reads the input and stores it into the `storage_buffer`,
; the user can edit the written input, by the use of backspace, and
; by typing enter, the input is read. The input shall be saved into
; another buffer.
; Parameters: None
; Returns:
; The input is stored in the `storage_buffer`.
.read_char:
mov ah, 0x00
int 0x16
mov [si], al
inc si
jmp .read_char
.handle_backspace:
cmp si, storage_buffer
je .read_char
dec si
mov byte [si], 0
call get_cursor_pos
cmp dl, 0
je .previous_line
jmp .read_char
.previous_line:
mov ah, 0x02
dec dh
mov dl, 79
int 0x10
jmp .read_char
.handle_enter:
cmp si, storage_buffer
je .read_char
ret
This function is a universal function for reading the input from the user. At the
beginning the function is reading a char, if the char is the Enter key then the
respective function handler is called, the similar behavior is encountered with the
Backspace key. Otherwise, the char is stored in the storage_buffer, and this char is
displayed on the screen. When the function encounters the limit of 255 bytes for
the string, i.e. more than 255 characters were given by the user, the only available
keys that are handled are the previously described, Enter and Backspace ones.
If the Backspace key is pressed, the routine first checks if any chars where given,
otherwise a jump to the .read_char is performed. If some chars where given, and
the Backspace was pressed, then the previous byte from the si register is set to
zero. Afterwards the cursor is updated. If the cursor is at the beginning of the line
then it jumps to the previous line, otherwise it moves to the left. At the end a space
in the place of the last char is displayed on the screen, and the loop repeats.
If the Enter key is pressed, the routine first checks if any chars where given,
otherwise a jump to the .read_char is performed. If some chars where given, the
zero byte is added to the buffer, to mark the end of the string and a return to the
caller is executed.
The following two helper functions are self-explanatory. And the algorithm are
exemplified in the doc-string of the function.
atoi:
; The function converts a string to integer. For example,
; "234" -> 234
; iteration 1:
; 1.1) "2" - "0" = 2 == ax
; 1.2) 0 * 10 = 0 == bx
; 1.3) 2 + 0 = 2 == bx
; iteration 2:
; 2.1) "3" - "0" = 3 == ax
; 2.2) 2 * 10 = 20 == bx
; 2.3) 3 + 20 = 23 == bx
; iteration 3:
; 3.1) "4" - "0" = 4 == ax
; 3.2) 23 * 10 = 230 == bx
; 3.3) 4 + 230 = 234 == bx
; Parameters:
; SI = src, buffer from where the string value is taken
; DI = dst, buffer where integer value is stored
; Returns: None
.atoi_loop:
cmp byte [si], 0
je .atoi_done
xor ax, ax
mov al, [si]
sub al, '0'
inc si
jmp .atoi_loop
.atoi_done:
ret
atoh:
; The function converts a string to integer. For example,
; "2C" -> 0x2C
; iteration 1:
; 1.1) "2" --dec--> 50
; 1.2) 50 is less than 65, thus jump to conversion of digit
; 1.3) al = 50-48 = 2
; 1.4) shift 0x0 to 0x00, and add 0x00 + 0x02 => 0x02
; 1.5) inc si, thus move the pointer in the string
; iteration 2:
; 2.1) "C" --dec--> 67
; 2.2) 67 is greater than 65, thus jump to conversion of letter
; 2.3) al = 67-55 = 12
; 2.4) shift 0x02 to 0x20, and add 0x20 + 0x12 => 0x32
; 2.5) inc si, thus move the pointer in the string
; Parameters:
; SI = src, buffer from where the string value is taken
; DI = dst, buffer where hexadecimal value is stored
; Returns: None
.atoh_loop:
cmp byte [si], 0
je .atoh_done
xor ax, ax
mov al, [si]
cmp al, 65
jl .conv_digit
.conv_letter:
sub al, 55
jmp .atoh_finish_iteration
.conv_digit:
sub al, 48
.atoh_finish_iteration:
mov bx, [di]
imul bx, 16
add bx, ax
mov [di], bx
inc si
jmp .atoh_loop
.atoh_done:
ret
The first one is used to convert a string to integer, and the second one is used to
convert a string to its hexadecimal equivalent.
read_nhts:
; The function handling input for N, Heads, Tracks, Sectors
; Parameters: None
; Returns: None
.read_n:
; display "N := "
call get_cursor_pos
inc dh
mov dl, 0x00
.read_h:
; display "H := "
call get_cursor_pos
inc dh
mov dl, 0x00
.read_t:
; display "T := "
call get_cursor_pos
inc dh
mov dl, 0x00
.read_s:
; display "S := "
call get_cursor_pos
inc dh
mov dl, 0x00
ret
The function above reads several parameters from the user. Each subroutine is
working in a similar way. First it updates the cursor, then displays the parameters
string, to inform the user which parameter to set. After a call to the read_input
function, which reads the given string, it is transformed to the appropriate format,
by the use of the atoi function.
read_ram_address:
; The function handling input for segment and offset
; Parameters: None
; Returns: None
ret
Similar to the previously described function, this one is taking the segment and
offset parameters.
duplicate_string:
; The function is duplicating the given `string`
; Parameters:
; si = SRC, the buffer which stores the string that will be duplicated
; di = DST, the buffer that will store the duplicated string
; Returns: None
pusha
mov cx, 0
mov bx, word[nhts]
.get_string_length:
lodsb
or al, al
jz .get_string_length_done
inc cx
jmp .get_string_length
.get_string_length_done:
; store the the size of the storage
; for the duplicated string
pusha
mov ax, bx
mov dx, cx
mul dx
mov [string_buffer_size], ax
popa
.main:
cmp bx, 0
je .done
jmp .copy_string
.copy_string:
push cx
.copy_string_loop:
dec cx
jz .copy_string_done
inc si
inc di
jmp .copy_string_loop
.copy_string_done:
pop cx
dec bx
mov si, string
jmp .main
.done:
popa
ret
Used to duplicate a string N times, the function works as follows, first the function
is setting cx to zero, this register will hold the string length stored in the buffer. The
bx has the value of N, which defines how many times the string will be duplicated.
The .get_string_length subroutine is incrementing the value of cx, until the end of
the string is encountered in the buffer. When the subroutine is done, the size of the
duplicated string is stored in the string_buffer_size buffer. It does that by
multiplying the length of the string to the number of times the string will be
duplicated.
After that the function enters a loop that repeats until the bx value is zero, at each
iteration this value is decreased by one. At each iteration the .copy_string
subroutine is called. It first pushes the value of cx to the stack, then it copies the
string to the buffer byte by byte while decrementing the cx value. When the string
was successfully copied byte by byte the value of cx is restored from the stack, and
the next iteration is executed.
The function expects the si register to hold the value of the buffer with the string
that will be duplicated, while di register holds the value of the buffer where the
duplicated string will be stored.
print_error_code:
; The function print the error code to the
; user, after the complition of a I/O operation
; on the disk.
; Parameters: None
; Returns: None
push ax
call get_cursor_pos
inc dh
mov dl, 0x00
ret
This function is displaying the error string that informs the user that the error code
is being displayed, after which the char value of the error code is displayed. That is
done by adding the decimal value of the ‘0’ char to the value of the error code,
after getting the ascii equivalent of the decimal error code, it is being displayed by
the help of the 0EH function from the 10H set of BIOS interrupts.
page_num: dw 0
string_param: db "string := ", 0
string_param_len: equ $ - string_param
N_param: db "N := ", 0
N_param_len: equ $ - N_param
H_param: db "H := ", 0
H_param_len: equ $ - H_param
T_param: db "T := ", 0
T_param_len: equ $ - T_param
S_param: db "S := ", 0
S_param_len: equ $ - S_param
segment_param: db "segment := ", 0
segment_param_len: equ $ - segment_param
offset_param: db "offset := ", 0
offset_param_len: equ $ - offset_param
error_msg: db "error code := ", 0
error_msg_len: equ $ - error_msg
The above declarations are declarations of the helper labels used all over the
previously described functions.
first_option.asm
first_option:
pusha
call null_buffers
popa
ret
First all the buffers should be null from the previous use, which is done by the call
to the null_buffers function.
After that the function displays an informative string to the screen, such that the
user does know which parameter is input next.
With the call to the read_input the given string is read. Then it is copied byte by
byte from the temporary buffer, called storage_buffer, to its own buffer, called
string. With the call of the read_nhts function, all the parameters are given for the
completion of the function.
The following block of code is duplicating the string as many times as the user
specified in the N parameter. After that the number of sectors needed to hold the
duplicated string is calculated, by dividing the previously calculated string buffer’s
size to the number of bytes of a single sector.
Next, the write to the floppy is executed by the use of the 03H function from the
13H set of BIOS interrupts. At the end, the error code setted by the previous
interrupt is displayed, as well as the duplicated string.
second_option:
pusha
call null_buffers
pop bx
pop es
popa
ret
The second option is similar to the previously described one, with the exception
that it takes the segment and offset from the user as one of the parameters, and
instead of writing to the floppy it reads from it, by the use of the 02H function from
the 13H set of BIOS interrupts.
third_option:
pusha
call null_buffers
; read user input `ram address`
call read_ram_address
pop bx
pop es
popa
ret
And this option is the exact opposite of the second one, as it writes to the floppy
from the ram.
As all the helper function were described, the internal structure of the kernel can be
shown.
org 0
_start:
call print_os_message
call create_signature
; call print_signature
call print_writing_to_disk_message_info
call print_writing_to_disk_message_success
%include "src/kernel/utils/print.asm"
%include "src/kernel/utils/signature.asm"
%include "src/kernel/utils/window.asm"
%include "src/kernel/utils/menu.asm"
%include "src/kernel/utils/const.asm"
; sector padding
times 1474048-($-$$) db 0
At the very beginning the org directive defines that the kernel should start with an
offset of zero. At the very bottom, the imports and the padding can be found. The
padding ensures that the kernel’s size together with the first sector, occupied by the
bootloader, creates a file of the size of 1.44MB, which matches the 3.5 inches
floppy parameters. Looking at the _start function a message from the kernel is
printed, after which the signature is duplicated by the use of the create_signature
function. Then, the first and last sectors are marked, beforehand a message of trial
to mark them, will be printed, and after successfully signing the sectors, a message
will be displayed. At the end, the program enters an endless loop incorporated into
the menu.
Test:
The testing phase of the program is done via a makefile, it’s source code follows:
default: build run
build:
@nasm -f bin src/bootloader/bootloader.asm -o bin/boot.bin
run:
hex-floppy:
@hexdump -C bin/floppy.img
hex-os:
@hexdump -C bin/os.bin
clean:
@rm -rfv bin/*.bin bin/*.img
Just by running make in the terminal will trigger the build and run targets. The first
one will build the program and the second one will run it, as expected.
In order to build the program, the user should have the following tool installed on
the system:
NASM version 2.16.01
The build target is compiling the bootloader first, and the kernel afterwards. It then
concatenates both binary files into one, by the use of the cat Linux util. In order to
facilitate the usage of the Virtual Box, a floppy image for it is created as well, by
the use of the dd Linux util.
The run target uses qemu, a virtual machine, usually available out of the box for
multiple Linux distros. The user should have the following tool installed on the
system:
QEMU emulator version 8.1.1
There are also helper targets. The clean target is responsible for deleting all the
files from the bin folder. The hex-floppy target will display the hexdump of the
floppy.img file. And the hex-os target will display the hexdump of the os.bin file. In
order to use these helper targets, the user should have the following tool installed
on the system:
hexdump from util-linux 2.39.2
We also dove into the 13H BIOS interrupts, which offer essential services for disk
operations in assembly language programming. This hands-on experience is key
for tasks like bootloading, file system work, and direct disk manipulation. Essential
knowledge for those venturing into operating systems or low-level software
development.