16-битная ОС на fasm + Cи. Часть 1

Моя цель - предложение широкого ассортимента товаров и услуг на постоянно высоком качестве обслуживания по самым выгодным ценам.

Прежде чем перейти к статье, хочу вам представить, экономическую онлайн игру Brave Knights, в которой вы можете играть и зарабатывать. Регистируйтесь, играйте и зарабатывайте!

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

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

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

Источник: https://habr.com/ru/post/668926/


Интересные статьи

Интересные статьи

- Скажи, пожалуйста, когда будет готово описание и функционал по нашим it-продуктам?- Ну, слууууууушай, сейчас очень большой объем работы. Быстро точно не сделаю. Давай в понедельник? Или лучше в...
В моем предыдущем посте я рассмотрел код WebApplicationBuilder, включая некоторые из его вспомогательных классов, таких как ConfigureHostBuilder и BootstrapHostBuilder. В конце поста мы создали экземп...
Джош Эванс рассказывает о хаотичном и ярком мире микросервисов Netflix, начиная с самых основ — анатомии микросервисов, проблем, связанных с распределенными системами и их преимуществ...
Всем привет! Если вы слышали о Blazor, но до сих пор не понимаете, что это такое. То вы по адресу. Это первая статья из цикла 12 статей, которая проведет вас через все круги ада весь процесс созд...
Это заключительная часть расследования о Scala-движении в России. В первой части я узнал от Романа Гребенникова о воронежском бомонде, C++ и Erlang, а от Романа Тимушева о первой Akka и рождении ...