16-bit OS on fasm + C. Part 1
This article is more than a guide or a manual, but just my notes. The idea of this article is to collect a lot of features and knowledge into one whole, I hope it will be useful to someone =)
What happens to RAM when you boot your computer
When you press the start button on the computer (or close the contacts on the motherboard), the BIOS checks the hardware and loads the first sector of the hard disk (512 bytes), which is marked as bootable, at the address 7C00h (h – hex) and starts executing the program that lies in these 512 bytes. From here it follows that we have only 512 bytes at our disposal.
At the end of our program (called the bootloader) there should be a bootloader signature – these are two bytes 55h and AAh, these two bytes determine whether this program is a bootloader. In the bootloader, we must write the boot from the hard disk of either the second bootloader, or the OS kernel right away, in our case, the OS kernel right away.
The OS kernel will be located at address 0500h, programs at address 7E00h, the top of the stack is 7DFFh.
Memory structure at computer startup.
The kernel is located on the 3rd sector of the hard disk and will occupy 4 sectors (4 * 512) or 2 Kb. To load data from the hard disk, it will use interrupt 13h and function 42h.
This function has a DAPS structure as input, which describes where, how much and from where to load sectors.
DAPS structure
1 byte – structure size (in our case 16 bytes)
1 byte – always 0, reserved
1 byte – how many sectors to load (in our case 4 (kernel size))
1 byte – always 0, reserved
2 bytes – at what offset to load data
2 bytes – on which segment to load data
8 bytes – sector number from which to start downloading data
#define u_int16 unsigned short int
#define u_char8 unsigned char
#define u_long_int unsigned long int
#define u_long_int64 unsigned long long int
struct daps
{
u_char8 p_size = 16;
u_char8 p_empty = 0;
u_char8 p_n_setors;
u_char8 p_empty2 = 0;
u_int16 p_adres;
u_int16 p_segment;
u_long_int64 sector;
file data_file;
};
On the file data_file do not look yet, this will come in handy in the future, for the convenience of reading files in our FS (file system).
Loader
Loader Code
use16
org 7c00h
cli ;запрещаем прерывания
xor ax,ax ;обнуляем регистр ах
mov ds,ax ;настраиваем сегмент данных на нулевой адрес
mov es,ax ;настраиваем сегмент es на нулевой адрес
mov ss,ax ;настраиваем сегмент стека на нулевой адрес
mov sp,07DFFh ;сегмент sp указывает на текущую вершину стека
sti ;разрешаем прерывания
push cs
pop ds
mov si,paket
mov ah,42h
int 13h
jmp 0000:0500h
jmp $
paket:;DAPS
db 16;const paksize
db 0;null
db 4;кол-во секторов
db 0;null
dw 0500h;смещение
dw 0;сегмент
dq 2;начало
times(512-2-($-07C00h)) db 0
db 055h,0AAh
;16 байт 1 сегмент
OS kernel
Our kernel at startup saves the disk number that the BIOS put in the register DLinto a variable BOOT_DISK(it will be needed to access the disk, files, etc.) and jumps to the label START_K. What comes after START_K sets interrupt vectors 90h(main OS API) and 91h(Return OS control).
Interrupt vectors are set using this macro, to the input interrupt number and handler function address.
macro SET_INTERRUPT_HANDLER NUM, HANDLER
{
pusha
xor ax,ax
push ax
pop es
mov al,NUM
mov bl,4h
mul bl
mov bx,ax
mov si,HANDLER
mov [es:bx],si
add bx,2
push cs
pop ax
mov [es:bx], ax
popa
}
Next, the file table is loaded, in our OS it is located in the second sector of the hard disk. The download also takes place via DAPS.
DAPS table files
DAPS_TABEL_FILES:
db 16;const paksize
db 0;null
db 1;кол-во секторов
db 0;null
dw TABLE_FILES;смещение
dw 0;сегмент
dq 1;начало
Download using macro and features 17h interrupts 90h(which installed the kernel)
macro LOAD_DAPS DAPS
{
push cs
pop ds
mov si, DAPS
mov ah, 17h
int 90h
}
Function 17h interrupt 90h (essentially just a wrapper over 13h)
cmp ah,17h;-|-in - ds:si - daps
je HF_LOAD_DAPS
iret
HF_LOAD_DAPS:
call F_LOAD_DAPS
iret
;-|-in - ds:si - daps
; |-out - (load file table on ram)
F_LOAD_DAPS:
mov dl,[BOOT_DISK];вот и пригодилась наша переменная с номером диска
mov ah,42h
int 13h
ret
Next comes the printing of the hello string using a macro PRINT.
macro PRINT STR,COLOR
{
mov ah,2
push cs
pop ds
mov di,STR
mov bl,COLOR
int 90h
}
He calls 2 interrupt function 90hwhich calls the function F_PRINT.
;--------------------Печать Форматированной Строки-------------------------
F_PRINTSF:;ds:di-str,bl-color
call F_GET_CURSOR
xor cx,cx
mov cl,[ds:di]
inc di
MAIN_START_F_PRINTSF:
call F_READ_VIDEO
mov ah,013h
push ds
pop es
mov bp,di
mov al,1
int 10h
ret
;--------------------Печать Строки-------------------------
F_PRINT:;ds:di-str,bl-color
push di
push ds
call F_GET_CURSOR
call F_GET_LEN_STR
pop ds
pop di
call MAIN_START_F_PRINTSF
ret
;-------------------Чтение видео режима------------------------------------------------------
F_READ_VIDEO:;out al=video ah=число колонок bh= номер активной страницы дисплея
mov ah,0fh
int 10h
ret
;------------Получение курсора; Выход: dh,dl - string,char ch,cl=нач.и кончеч строки курсора ----------------------------------------------------
F_GET_CURSOR:;out= dh,dl - string,char ch,cl=нач.и кончеч строки курсора
call F_READ_VIDEO
mov ah,03h
int 10h
ret
;-------------------Фуекция подсчета длины строки.------------------------------------------------------------------------------
;-|-in - ds:di=str, cx=len
; |-out - cx=len
F_GET_LEN_STR:
xor cx,cx
START_F_GET_LEN_STR:
mov al,[ds:di]
cmp al,0
je EXIT_F_GET_LET_STR
inc di
inc cx
jmp START_F_GET_LEN_STR
EXIT_F_GET_LET_STR:
ret
The next step is to search for a file named cmd and its launch. Implemented using macros SEACH_FILE and LOAD_DAPS.
macro SEACH_FILE TABLE_FILES, FILENAME
{
push cs
pop ds
mov bx,TABLE_FILES
mov di, FILENAME
mov ah,10h
int 90h
}
Macro calls 10h interrupt function 90h.
;-------------------Поиск адреса файла------------------------------------------------------------------------------------------
;-|-in - ds:bx=tableFiles, ds:di=flename
; |-out - ch-dorogka cl=sector, al=numSectors ah = type
; |-except - not found - ax=0, cx=0
F_SEACH_FILE:
jmp startpfseachFile
pfseachFilecxsave: db 0
pfseachFilebxsave: db 0,0
pfseachFiledisave: db 0,0
startpfseachFile:
xor cx,cx
mov cl,32
add bx,4
mov [pfseachFiledisave],di
startSeach:
mov [pfseachFilecxsave],cl
mov [pfseachFilebxsave],bx
mov di,[pfseachFiledisave]
mov si,[pfseachFilebxsave]
call F_CMP_STRING
cmp al,0
je pgetDataForstartFile
mov cl,[pfseachFilecxsave]
mov bx,[pfseachFilebxsave]
add bx,16
loop startSeach
xor bx,bx
xor cx,cx
xor ax,ax
jmp exitpfseachFile
pgetDataForstartFile:
mov bx,[pfseachFilebxsave]
mov di,bx
dec di
mov cl,[ds:di]
dec di
mov ch,[ds:di]
mov al,ch
dec di
mov ch,[ds:di]
dec di
mov ah,[ds:di]
exitpfseachFile:
ret
File launch cmd.
START_PROGRAMM:
mov si, DAPS_RUNTIME_FILE
mov [si + 2], al
mov [si + 8], cl
LOAD_DAPS DAPS_RUNTIME_FILE
;LOAD_FILE ch, cl, al, 0000, 0500h
NEW_LINE
jmp 0000:7E00h
DAPS programs.
DAPS_RUNTIME_FILE:
db 16;const paksize
db 0;null
db 1;кол-во секторов
db 0;null
dw 7E00h;смещение
dw 0;сегмент
dq 7;начало
Core Code
org 0500h
GLOBAL:
mov [BOOT_DISK],dl
jmp START_K
;-----------------------------------------------------------
include 'INCLUDES\MACROS.INC'
include 'INCLUDES\BASE_FUNCTIONS.INC'
include 'INCLUDES\INTERRUPT_HANDLER_RETURN.INC'
include 'INCLUDES\MAIN_INTERRUPT_HANDLER.INC'
include 'INCLUDES\KEYBOARD.INC'
include 'INCLUDES\CONST.INC'
;-----------------------------------------------------------
START_K:
SET_INTERRUPT_HANDLER 90H,MAIN_INTERRUPT_HANDLER
SET_INTERRUPT_HANDLER 91H,INTERRUPT_HANDLER_RETURN
LOAD_DAPS DAPS_TABEL_FILES
PRINT HELLO_WORLD, BLACK
MAIN:
;NEW_LINE
;PRINT INPUT_STR, BLACK
;GET_STRING BUFFER, 13
SEACH_FILE TABLE_FILES, CMD
cmp ax,0
je PRINT_ERROR
cmp ah,1
je START_PROGRAMM
jmp MAIN
START_PROGRAMM:
mov si, DAPS_RUNTIME_FILE
mov [si + 2], al
mov [si + 8], cl
LOAD_DAPS DAPS_RUNTIME_FILE
;LOAD_FILE ch, cl, al, 0000, 0500h
NEW_LINE
jmp 0000:7E00h
PRINT_ERROR:
NEW_LINE
PRINT ERROR, RED
jmp MAIN
RETURN_INT:
jmp MAIN
;сюда передает управление int 91h
jmp $
DAPS_RUNTIME_FILE:
db 16;const paksize
db 0;null
db 1;кол-во секторов
db 0;null
dw 7E00h;смещение
dw 0;сегмент
dq 7;начало
DAPS_TABEL_FILES:
db 16;const paksize
db 0;null
db 1;кол-во секторов
db 0;null
dw TABLE_FILES;смещение
dw 0;сегмент
dq 1;начало
HELLO_WORLD: string "WaaOS Loaded, Hello! =)"
ERROR: string "Command not found :("
CMD: string "cmd"
INPUT_STR: string "user:>"
BOOT_DISK: db 0
BUFFER: db 13 dup(0)
TMP: db 255 dup(0)
TABLE_FILES:
CALC_SIZE SIZE_KERNEL, GLOBAL
CMD code
#include "BASE_LIB.H"
void clear_str_file_name(u_char8 *str, u_char8 len){
for(u_int16 i = 0; i < len; i++){
str[i] = 0;
}
}
void main(void)
{
u_char8 user[] = "user:>";
u_char8 not_found[] = "Command not found :(";
while (true)
{
print(new_line, Black);
print(user, White);
f_string user_guffer = input();
for(u_char8 i =0 ; i < 254; i++){
if(user_guffer.data[i] == ' '){
user_guffer.data[i] = 0;
}
}
u_char8 file_name[13];
clear_str_file_name(file_name, 13);
for(u_char8 i = 0; i < 13; i++){
if(user_guffer.data[i] == 0) break;
if(user_guffer.data[i] == ' ') break;
file_name[i] = user_guffer.data[i];
}
if(file_name[0] != ' ' && file_name[0] != 0){
daps daps_file = get_r_daps_file(file_name, (u_int16) 0x07E00);
print(new_line, Black);
if(daps_file.p_empty != 1){
start_programm(&daps_file, user_guffer.data);
} else {
print(not_found, Red);
}
}
}
}
Conclusion
If you are interested in reading about the file system used in this OS, and the library for SI and add. interrupt functions 90h, I will make the second, third and so on parts. Thank you for reading to the end.
Authors: @lllzebralll @aovzerk