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

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

В далеком уже 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 с помощью которой мы сохраняем на нашу виртуальную дискету необходимые для загрузки файлы.

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

Комментарии (9)


  1. SIISII
    09.11.2024 19:22

    Вообще, в FAT содержатся не номера секторов, а номера кластеров, и не в формате LBA, поскольку последний относится к адресации секторов на диске, а не кластеров на томе FAT.


    1. kailot2 Автор
      09.11.2024 19:22

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

      Там есть немного об этом


      1. SIISII
        09.11.2024 19:22

        Ага, но позже. Ну а я вообще люблю придираться к терминологии и т.п. -- а то иногда пишут уж очень вольно (до такой степени, что перестаёшь понимать, о чём речь).


      1. saboteur_kiev
        09.11.2024 19:22

        А если грузиться не с флоппи? Или ОС не планирует это делать?
        Выходит не полноценная поддержка fat12, куда жесткие диски завезли достаточно давно.


        1. kailot2 Автор
          09.11.2024 19:22

          1) Это начальный загрузчик именно для флоппи, поэтому здесь не идет речь о полноценной поддержке.
          2) Не , она не планирует это делать. До полноценной поддержки тут ну очень далеко ( даже в "драйвере" диска реализованы только минимальные функции типа чтения файла с диска, где-то там я еще когда-то писал функции для записи файла, но не помню дописал или нет.


        1. FanatPHP
          09.11.2024 19:22

          Мне почему-то кажется, что на реальном железе никто загружаться в эту ОС не планирует ;-)


          1. kailot2 Автор
            09.11.2024 19:22

            Я грузил на реальном железе))


          1. saboteur_kiev
            09.11.2024 19:22

            Так тем более интереснее грузить с виртуального жесткого диска, а не виртуального флоппи =)


  1. FanatPHP
    09.11.2024 19:22

    Хорошо, но мало! Как-то очень быстро кончилось :)