16-битная ОС на fasm + Cи. Часть 1
Данная статья в большей степени является не руководством и не мануалом, а просто моими заметками. Идея этой статьи собрать множество особенностей и знаний в одно целое, надеюсь, она кому-то пригодится =)
Что происходит с ОЗУ при загрузке компьютера
Когда вы нажимаете кнопку старта на компьютере (или замыкаете контакты на материнке) BIOS проверяет оборудование и загружает первый сектор жесткого диска (512 байт), который помечен как загрузочный, по адресу 7C00h (h — hex) и начинает выполнять программу которая лежит в этих 512 байтах. От сюда следует, что у нас в распоряжеии есть только 512 байт.
В конце нашей программы (именуемой загрузчиком) должна быть сигнатура загрузчика — это два байта 55h и AAh, по этим двум байтам BIOS определяет, является ли эта программа загрузчиком. В загрузчике мы должны написать загрузку с жесткого диска либо второго загрузчика, либо сразу ядра ОС, в нашем случае сразу ядра ОС.
Ядро ОС будет располагаться по адресу 0500h, программы по адресу 7E00h, вершина стека 7DFFh.
Структура памяти при запуске компьютера.
Ядро располагается на 3 секторе жесткого диска и будет занимать 4 сектора (4×512) или 2 Kb.Для загрузки даных с жесткого диска будет использовать прерывание 13h и функция 42h.
У этой функции на вход идет DAPS структура, в которой описано куда, сколько и от куда грузить сектора.
Структура DAPS
1 байт — размер структура (в нашем случае 16 байт)
1 байт — всегда 0, резерв
1 байт — сколько загружать секторов (в нашем случае 4(размер ядра))
1 байт — всегда 0, резерв
2 байта — по какому смещению загружать данные
2 байта — по какому сегменту загружать данные
8 байт — номер сектора с которого начинать загружать данные
#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;
};
На file data_file пока не смотрите, это пригодиться в будущем, для удобства чтения файлов в нашей ФС (файловай системе).
Загрузчик
Код загрузчика
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 сегмент
Ядро ОС
Наше ядро при запуске сохраняет номер диска, который BIOS положил в регистр DL, в переменную BOOT_DISK(она нужна будет для доступа к диску, файлам и тд) и прыгает на метку START_K. То что идет после START_K ставит вектора прерываний 90h(основное API ОС) и 91h(Возврат управления ОС).
Установка векторов прерываний осуществляется с помощью этого макроса, на вход номер прерывания и адрес функции обработчика.
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
}
Далее загружается таблица файлов, в нашей ОС она находится во втором секторе жесткого диска. Загрузка также происходит через DAPS.
DAPS таблицы файлов
DAPS_TABEL_FILES:
db 16;const paksize
db 0;null
db 1;кол-во секторов
db 0;null
dw TABLE_FILES;смещение
dw 0;сегмент
dq 1;начало
Загрузка с помощью макроса и функции 17h прерывания 90h(которое установило ядро)
macro LOAD_DAPS DAPS
{
push cs
pop ds
mov si, DAPS
mov ah, 17h
int 90h
}
Функция 17h прерывания 90h (по сути просто бертка над 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
Далее идет печать строки приветствия с помощью макроса PRINT.
macro PRINT STR,COLOR
{
mov ah,2
push cs
pop ds
mov di,STR
mov bl,COLOR
int 90h
}
Он вызывает 2 функцию прерывания 90h, которая вызывает функцию 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
Далее идет поиск файла с именем cmd и его запуск. Реализованно это с помощью макросов SEACH_FILE и LOAD_DAPS.
macro SEACH_FILE TABLE_FILES, FILENAME
{
push cs
pop ds
mov bx,TABLE_FILES
mov di, FILENAME
mov ah,10h
int 90h
}
Макрос вызывает 10h функцию прерывания 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
Запуск файла 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 программ.
DAPS_RUNTIME_FILE:
db 16;const paksize
db 0;null
db 1;кол-во секторов
db 0;null
dw 7E00h;смещение
dw 0;сегмент
dq 7;начало
Код Ядра
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
#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);
}
}
}
}
Заключение
Если вам интересно будет почитать про файловую систему, которая используется в этйо ОС, и библиотеку для СИ и доп. функции прерывания 90h, сделю вторую, третью и тд части. Спасибо что дочитали до конца.
Весь код ОС
Авторы: @lllzebralll @aovzerk