Встретил на Хабре очередную статью об написании "простой операционной системы с нуля" и решил поделится своими потугами на эту тему.
Немного предыстории
В далеком уже 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 с помощью которой мы сохраняем на нашу виртуальную дискету необходимые для загрузки файлы.
SIISII
Вообще, в FAT содержатся не номера секторов, а номера кластеров, и не в формате LBA, поскольку последний относится к адресации секторов на диске, а не кластеров на томе FAT.
kailot2 Автор
Там есть немного об этом
SIISII
Ага, но позже. Ну а я вообще люблю придираться к терминологии и т.п. -- а то иногда пишут уж очень вольно (до такой степени, что перестаёшь понимать, о чём речь).
saboteur_kiev
А если грузиться не с флоппи? Или ОС не планирует это делать?
Выходит не полноценная поддержка fat12, куда жесткие диски завезли достаточно давно.
kailot2 Автор
1) Это начальный загрузчик именно для флоппи, поэтому здесь не идет речь о полноценной поддержке.
2) Не , она не планирует это делать. До полноценной поддержки тут ну очень далеко ( даже в "драйвере" диска реализованы только минимальные функции типа чтения файла с диска, где-то там я еще когда-то писал функции для записи файла, но не помню дописал или нет.
FanatPHP
Мне почему-то кажется, что на реальном железе никто загружаться в эту ОС не планирует ;-)
kailot2 Автор
Я грузил на реальном железе))
saboteur_kiev
Так тем более интереснее грузить с виртуального жесткого диска, а не виртуального флоппи =)