Данная статья в большей степени является не руководством и не мануалом, а просто моими заметками. Идея этой статьи собрать множество особенностей и знаний в одно целое, надеюсь, она кому-то пригодится =)
Что происходит с ОЗУ при загрузке компьютера
Когда вы нажимаете кнопку старта на компьютере(или замыкаете контакты на материнке) 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
Комментарии (16)
eurol
01.06.2022 10:46+1Сразу несколько моментов.
Почему в sp загружается нечетное число? Всё-таки выравнивание… Может, прибавить единицу?
Не делается проверка на поддержку расширенных функций чтения через int 13h, ну да это ладно, надеемся, что на старых компьютерах не будут запускать. Хотя если даже развивать тему и сделать защищенный режим, то запускать можно хоть на 286 в теории, 386 — более вероятно. Тогда однозначно проверка нужна.
И ещё, что касается загрузки… Не лучше ли сделать загрузчик так, чтобы систему можно было запускать на компьютере, где есть и другие ОС? То есть сделать загрузочный сектор с таблицей разделов ИЛИ чтобы он учитывал смещение от начала диска (если загружать его с раздела, а не из MBR).aovzerk Автор
01.06.2022 18:07Спасибо за отзыва, я сейчас, можно сказать, только вникаю как работать в защищенном режиме, читаю про порты, что бы написать драйвер для клавиатуры и тд, что бы ОС можно было запускать с другими ОСями надо ставить grub и тд, это не сложно, просто grub автоматически перевожит процессор в защищенный режим, а ос нацелена на realmode
hard2018
02.06.2022 08:54чтобы он учитывал смещение от начала диска
Тогда придётся вникать в структуру ФС. Какую ФС вы собираетесь использовать?
eurol
02.06.2022 09:06Тогда придётся вникать в структуру ФС. Какую ФС вы собираетесь использовать?
Тип ФС в целом тут не играет роли. Тип таблицы разделов — да, пожалуй. Я в них не силен, и предполагаю, что будет обычная таблица разделов, которая принята в DOS была.
MBR-загрузчик выбирает раздел, с которого загрузиться. Если же загрузчик более крутой (типа GRUB), обычно есть возможность выбора раздела, с которого грузиться.
В итоге запускается код из BOOT-сектора выбранного раздела, а тому просто необходимо знать, с какого сектора его раздел начинается. Поэтому BOOT-сектор раздела обычно и содержит этот самый номер сектора.
hard2018
01.06.2022 11:07+1То есть у вас ось работает в режиме реальных адресов. Вы описали то, с чего начинался совремнный хрюсофт в том числе. Вы же MS DOS помните?
Но сейчас технические возможности современных процессоров позволяют использовать и защищённый режим и страничную адресацию.
Только использовать системные вызовы биос уже не получится
Рекомендую почитать http://sasm.narod.ru/index.htm
aovzerk Автор
01.06.2022 18:12Конечно помним про MS-DOS, я на нем учился программировать на ассемблере, прерывание 21h волшебно!
Благодарю за ресурс
longtolik
01.06.2022 18:27-2BIOS проверяет...
А без BIOS?
Ставите свою микросхему со своим кодом на видеокарту. ( Или в сетевую карту). Процессор (при подаче питания) послушно выставляет на шине адреса адрес ПЗУ, считывает команды и исполняет их же.
И никто не обязан считывать некий сектор с некого HDD. А если контроллера нету просто?
Вытащите микросхему BIOS, и работайте на "голом железе". В конце-концов, BIOS тоже кто-то пишет.
aovzerk Автор
01.06.2022 18:34+1так ни кто и не говорит делать так, как написанно в этой статье, в начале статьи написанно , что это мои личные наблюдения и тд)
hard2018
01.06.2022 22:44+2Где это видано?
Радуйтесь, что биос есть в каждом нормально работающем компьютере.
Во первых, лишний слой совместимости вам только поможет
Во вторых, без биос вам придётся проделывать вручную инициализацию оборудования.
Если ещё оперативку не сожгёте, выставив неправильно таймауты
NikitaKi2003
01.06.2022 23:52Здравствуйте, прочитал статью. Всегда хотел написать свою собственную OC. Был бы очень благодарен, если бы вы мне подсказали какие книги почитать, чтобы относительно быстро и безболезненно разобраться в этой теме и приступить к созданию собственной OC.
aovzerk Автор
01.06.2022 23:53https://fasmworld.ru/uchebnik/
https://wiki.osdev.org/Expanded_Main_Page
http://www.codenet.ru/progr/dos/
это основные ресурсы из которых я брал инфу
BiosUefi
Когда есть такой скелет, тогда можно и многозадачность, на многоядерности замутить.
Пусть для начала и статическую, сколько ядер, столько и крутящихся статических задач.
Причём 640кб позволят запустить до 128 задач, при наличии 128 ядер.
aovzerk Автор
Есть прерывание которые процессор вызывает 18 раз в секунду, на основе него можно что-нибудь придумать)