Это перенос моей публикации с Хабра.
Встретил на Хабре очередную статью об написании “простой операционной системы с нуля” и решил поделится своими потугами на эту тему.

Немного предыстории

В далеком уже 2011 мне в руки каким-то чудом попала книга “Ассемблер. Экспресс курс” за авторством Александра Панова. После Паскаля ,изучаемого в школе, ассемблер показался мне языком неограниченных возможностей. После того как я вдоволь наигрался со всякими огоньками и прочими бегущими символами мне захотелось создать что-то крутое. А что может быть круче чем собственная ОС? Так в новогодние каникулы с 2011-2012 гг появилась Ozon Os.

В этих статьях я хочу ознакомить вас с Ozon OS и фактически переписать её заново , исправив страшные косяки 🙂

И начнем конечно же с начального загрузчика.

OzonOS предполагается размещать на флоппи-диске (если кто-то еще помнит что это такое), потому что загрузка с флоппи наиболее проста – нам доступны функции BIOS для чтения секторов, а fat12 наиболее простая файловая система.

Здесь и далее будем использовать ассемблер fasm и эмулятор/отладчик bochs.

Процесс загрузки OzonOs выглядит так

Начальный загрузчик читает с дискеты таблицы ROOT и FAT, загружает с дискеты “дисковый драйвер” и ядро ос, устанавливает для 0x25 прерывания вектор “дискового драйвера” и передает управление ядру.

И первая сложность при работе с дискетой – режим адресации секторов:
Для прерывания BIOS нужно указывать адрес в виде CHS (Cylinder, Head, Sector — цилиндр, головка, сектор), А в таблице FAT адреса секторов указываются в формате LBA (Logical block addressing), поэтому для начала нам нужно написать процедуру чтения сектора по LBA которая будет пересчитывать LBA в CHS и читать сектор куда нам нужно.

;Читает сектор по LBA 
;BX = номер сектора в LBA 
;ES:DI буффер 
readsect: 
push    es 
push    ds 
pusha 
mov     ax,bx 
mov     cx, 18 
mov     bx, di 
xor     dx, dx div     
cx          ;Делим ax на 18 
mov     ch, al      ;ch = ax div 18 
shr     ch, 1       ;сh = ch div 2 
mov     cl, dl 
inc     cx 
mov     dh, al 
and     dh, 1 
mov     ax, 0x201 
mov     dl, [ADrive] 
int     0x13 
popa 
pop     ds 
pop     es 
ret

Процедура чтения секторов у нас есть , теперь можем прочитать таблицы FAT и ROOT.
На флоппи они идут сразу после boot сектора и занимают 34 сектора.

Читать будем по адресу (физическому) 0x500 или сегментный адрес 0050:0000 (смотрите метод адресации памяти в реальном режиме) – там как раз заканчивается область данных BIOS

;Читаем таблицы FAT и ROOT 
push 0x050 
pop es 
xor di,di 
mov bx,1 
mov cx,18+15+1 
@@: 
call readsect 
add di,0x200 
dec cx 
inc bx 
cmp cx,0 jne @b

Хорошо – таблицы загружены.

Теперь нам нужно загрузить файлы ядра и “драйвера диска”.

Для этого нужно:

  • В таблице ROOT найти по имени файла соответствующую запись и извлечь номер первого кластера
  • Прочитать соответствующую цепочку кластеров

Кластер – это группа секторов , для fat12 на флоппи справедливо 1 кластер=1 сектор. , только нумерация кластеров смещена на 0x1f относительно нумерации секторов

Структура записи корневого каталога выглядит так:

Процедура поиска и извлечения номера первого кластера в загрузчике OzonOs выглядит так:

;Поиск первого кластера файла 
;ES:DI = указатель на имя файла
;Вывод - BX - первый кластер 
Get_First_Claster: 
push    ds 
pusha 
push    es 
push    0x0050 
pop     ds 
mov     si,0x2400 
@@: mov     cx,11 
push    di 
push    si 
repe    cmpsb 
pop     si 
pop     di 
add     si,0x20 
cmp     si,0x5000 
ja      ErrorGFC ;Если корневой каталог кончился 
test    cx,cx 
jnz     @b 
sub     si,06 
push    word [ds:si] 
pop     ds 
add     si,2 
push    word [ds:si] 
pop es 
popa 
mov     bx,ds 
mov     cx,es 
pop     ds 
pop     es 
ret

Для чтения цепочки нужна процедура извлечения номера следующего кластера. Сложность здесь в том что элемент FAT12 занимает 12 бит ,  или 1.5 байта, соответственно для определения смещения нужны некоторые вычисления.

;Выдает следующий за BX кластер 
Get_next_claster: 
push    es 
push    ds 
pusha 
mov     ax,bx   ;копируем значение в ах 
shr     bx,1    ;если кластер не четный то в CF=1 
sbb     cx,cx   ;Если CF=1 то CX=-1 
add     bx,ax   ;Умножаем на три 
push    0050h   ;Сегмент FAT 
pop     es 
xor     si,si   ;Смещение FAT 
add     si,bx 
mov     ax,word [es:si] 
and     cl,4 
shr     ax,cl 
and     ax,0FFFh 
push    ax 
pop     ds 
popa 
mov     bx,ds 
pop     ds 
pop     es 
ret

Теперь наконец то сможем прочитать файл “драйвера диска” и установить соответствующий вектор прерывания

;Ищем первый кластер 
push cs 
pop es 
mov di,FileName_1 
call Get_First_Claster 
;Если bx=-1 значит не нашли 
cmp bx,-1 
je ErrorMesg          ;Сообщение что не нашли
push bx 
mov di,OkMesg 
call print 
pop bx
push 0x060            ;Дисковый драйвер 
pop es                ;по адресу 
xor di,di             ;0060h:0000h 
;читаем файл 
@@: 
push bx 
add bx,1fh            ;LBA = CLASTER + 1Fh 
push es 
push di 
call readsect         ;читаем сектор 
pop di 
pop es 
pop bx 
add di,0x200 
call Get_next_claster ;Ищем следующий кластер 
cmp bx,0FFFh          ;Если кластер не последний 
jne @b                ;То читаем его 
;Установим обработчик прерывания 25h 
cli                   ;Запретим прерывания 
pushf                 ;Сохраним флаги 
push 0 
pop es 
mov di,0x25*4 
mov [es:di],word 0    ;Смещение 0000h 
mov ax,0x060 
mov di,0x25*4+2 
mov [es:di],word ax   ;Сегмент 0060h 
popf                  ;Восстановим регистр флагов 
sti                   ;Разрешим прерывания

И здесь кроется первый серьезный косяк – таблицы ROOT и FAT были загружены по адресу 0050:0000 а дисковый драйвер грузится по адресу 00060:0000.
То есть дисковый драйвер перекрывает системные таблицы , ведь они занимают 34 сектора, т.е. 17 Кб, а если перевести сегментные адреса в физические то разница между ними:
0x600 – 0x500 = 0x100 = 256 байт.
Что бы исправить это необходимо грузить дисковый драйвер по адресу
0x500 + 0x22*0x200 = 0x4900
что соответствует сегментному адресу 0490:0000, так что заменим в коде 0x060 на 0x0490.

Теперь можем прочитать ядро. В изначальном виде оно загружалось по статическому сегментному адресу 0070:0000 , что опять же затирало системные таблицы ( да и дисковый драйвер).
Для простоты сместим ядро на 4кб выше дискового драйвера, т.е. на сегментный адрес 0590:0000.

Так как к этому моменту у нас уже загружен дисковый драйвер то можем воспользоваться его функциями :

mov ax,0x590 
mov ds,ax 
xor si,si 
mov ax,cs 
mov es,ax 
mov di,kernelname 
mov ah,08h            ;Функция 0x8 - чтение файла 
int 0x25

и далее остается только настроить регистры и передать управление ядра

;Настроим Регистры
mov ax,0x590 
mov ds,ax 
mov es,ax 
mov ss,ax 
mov sp,0FFFEh 
call 0x590:0000

И получаем :

После компиляции исходника получается файл fddboot.img который отлично открывается программой ultraiso с помощью которой мы сохраняем на нашу виртуальную дискету необходимые для загрузки файлы.

Ссылка на архив с исходником и загрузочным образом

От kailot2

Добавить комментарий