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

в 2:42, , рубрики: C, ассемблер, загрузка ос, загрузчик, операционная система, Си, системное программирование

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

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

Когда вы нажимаете кнопку старта на компьютере(или замыкаете контакты на материнке) BIOS проверяет оборудование и загружает первый сектор жесткого диска(512 байт), который помечен как загрузочный, по адресу 7C00h (h - hex) и начинает выполнять программу которая лежит в этих 512 байтах. От сюда следует, что у нас в распоряжеии есть только 512 байт.

В конце нашей программы (именуемой загрузчиком) должна быть сигнатура загрузчика - это два байта 55h и AAh, по этим двум байтам BIOS определяет, является ли эта программа загрузчиком. В загрузчике мы должны написать загрузку с жесткого диска либо второго загрузчика, либо сразу ядра ОС, в нашем случае сразу ядра ОС.

Ядро ОС будет располагаться по адресу 0500h, программы по адресу 7E00h, вершина стека 7DFFh.

Структура памяти при запуске компьютера.

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

Ядро располагается на 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 'INCLUDESMACROS.INC'
    include 'INCLUDESBASE_FUNCTIONS.INC'
    include 'INCLUDESINTERRUPT_HANDLER_RETURN.INC'
    include 'INCLUDESMAIN_INTERRUPT_HANDLER.INC'
    include 'INCLUDESKEYBOARD.INC'
    include 'INCLUDESCONST.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

Автор:
aovzerk

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js