Данная статья в большей степени является не руководством и не мануалом, а просто моими заметками. Идея этой статьи собрать множество особенностей и знаний в одно целое, надеюсь, она кому-то пригодится =)

Что происходит с ОЗУ при загрузке компьютера

Когда вы нажимаете кнопку старта на компьютере(или замыкаете контакты на материнке) 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)


  1. BiosUefi
    01.06.2022 10:17
    +2

    Когда есть такой скелет, тогда можно и многозадачность, на многоядерности замутить.

    Пусть для начала и статическую, сколько ядер, столько и крутящихся статических задач.

    Причём 640кб позволят запустить до 128 задач, при наличии 128 ядер.


    1. aovzerk Автор
      01.06.2022 18:08

      Есть прерывание которые процессор вызывает 18 раз в секунду, на основе него можно что-нибудь придумать)


  1. eurol
    01.06.2022 10:46
    +1

    Сразу несколько моментов.
    Почему в sp загружается нечетное число? Всё-таки выравнивание… Может, прибавить единицу?
    Не делается проверка на поддержку расширенных функций чтения через int 13h, ну да это ладно, надеемся, что на старых компьютерах не будут запускать. Хотя если даже развивать тему и сделать защищенный режим, то запускать можно хоть на 286 в теории, 386 — более вероятно. Тогда однозначно проверка нужна.
    И ещё, что касается загрузки… Не лучше ли сделать загрузчик так, чтобы систему можно было запускать на компьютере, где есть и другие ОС? То есть сделать загрузочный сектор с таблицей разделов ИЛИ чтобы он учитывал смещение от начала диска (если загружать его с раздела, а не из MBR).


    1. aovzerk Автор
      01.06.2022 18:07

      Спасибо за отзыва, я сейчас, можно сказать, только вникаю как работать в защищенном режиме, читаю про порты, что бы написать драйвер для клавиатуры и тд, что бы ОС можно было запускать с другими ОСями надо ставить grub и тд, это не сложно, просто grub автоматически перевожит процессор в защищенный режим, а ос нацелена на realmode


    1. hard2018
      02.06.2022 08:54

      чтобы он учитывал смещение от начала диска

      Тогда придётся вникать в структуру ФС. Какую ФС вы собираетесь использовать?


      1. eurol
        02.06.2022 09:06

        Тогда придётся вникать в структуру ФС. Какую ФС вы собираетесь использовать?

        Тип ФС в целом тут не играет роли. Тип таблицы разделов — да, пожалуй. Я в них не силен, и предполагаю, что будет обычная таблица разделов, которая принята в DOS была.
        MBR-загрузчик выбирает раздел, с которого загрузиться. Если же загрузчик более крутой (типа GRUB), обычно есть возможность выбора раздела, с которого грузиться.
        В итоге запускается код из BOOT-сектора выбранного раздела, а тому просто необходимо знать, с какого сектора его раздел начинается. Поэтому BOOT-сектор раздела обычно и содержит этот самый номер сектора.


  1. hard2018
    01.06.2022 11:07
    +1

    То есть у вас ось работает в режиме реальных адресов. Вы описали то, с чего начинался совремнный хрюсофт в том числе. Вы же MS DOS помните?

    Но сейчас технические возможности современных процессоров позволяют использовать и защищённый режим и страничную адресацию.

    Только использовать системные вызовы биос уже не получится

    Рекомендую почитать http://sasm.narod.ru/index.htm


    1. aovzerk Автор
      01.06.2022 18:12

      Конечно помним про MS-DOS, я на нем учился программировать на ассемблере, прерывание 21h волшебно!
      Благодарю за ресурс


  1. JackKatch
    01.06.2022 13:13
    +1

    Обнаружил пару опечаток. (... Функция 17h прерывания 90h(по сути просто бертка ...) - наверное обёртка. (... сделю вторую, третью и тд части ...) - наверное сделаю. Спасибо за статью. Побольше бы таких.


    1. aovzerk Автор
      01.06.2022 18:03

      Благодарю!)


  1. longtolik
    01.06.2022 18:27
    -2

    BIOS проверяет...

    А без BIOS?

    Ставите свою микросхему со своим кодом на видеокарту. ( Или в сетевую карту). Процессор (при подаче питания) послушно выставляет на шине адреса адрес ПЗУ, считывает команды и исполняет их же.

    И никто не обязан считывать некий сектор с некого HDD. А если контроллера нету просто?

    Вытащите микросхему BIOS, и работайте на "голом железе". В конце-концов, BIOS тоже кто-то пишет.


    1. aovzerk Автор
      01.06.2022 18:34
      +1

      так ни кто и не говорит делать так, как написанно в этой статье, в начале статьи написанно , что это мои личные наблюдения и тд)


    1. hard2018
      01.06.2022 22:44
      +2

      Где это видано?

      Радуйтесь, что биос есть в каждом нормально работающем компьютере.

      Во первых, лишний слой совместимости вам только поможет

      Во вторых, без биос вам придётся проделывать вручную инициализацию оборудования.

      Если ещё оперативку не сожгёте, выставив неправильно таймауты


  1. NikitaKi2003
    01.06.2022 23:52

    Здравствуйте, прочитал статью. Всегда хотел написать свою собственную OC. Был бы очень благодарен, если бы вы мне подсказали какие книги почитать, чтобы относительно быстро и безболезненно разобраться в этой теме и приступить к созданию собственной OC.


    1. aovzerk Автор
      01.06.2022 23:53

      https://fasmworld.ru/uchebnik/
      https://wiki.osdev.org/Expanded_Main_Page
      http://www.codenet.ru/progr/dos/
      это основные ресурсы из которых я брал инфу


  1. axe_chita
    02.06.2022 05:38

    Продолжайте, будет интересно до каких масштабов дорастет эта ОС