Лисья Нора

Оглавление


§ Устройство MBR

Зачастую возникает желание создать свою ОС, но никак не знаешь, с чего начать? Так вот ответ простой – бутсектор! Если напишешь бутсектор, считай, половина ОС в твоем кармане. Ахаха! Поверил? А вот и нет. Это 0.01% от операционной системы.
Начнем с того, что бутсектор должен располагаться в MBR. Дело в том, что после разметки диска, начиная с 446-го байта и заканчивая 512-м, на диске уже есть разметка 4 primary разделов. По этой причине, код бутсектора для диска (hd) а не флоппи (fd), должен быть не более 446 байт. Это очень важно. Сам же код сектора загружается биосом по адресу 0000h:7C00h (CS=0000h, IP=7C00h) в реальном режиме работы процессора. Это так всегда происходит. Наша задача что-то сделать с этим.
000 Программный код бут загрузчика
1BE Дескриптор 1-го раздела диска (16 байт)
1CE Дескриптор 2-го раздела диска (16 байт)
1DE Дескриптор 3-го раздела диска (16 байт)
1EE Дескриптор 4-го раздела диска (16 байт)
1FE Сигнатура 0xAA55 (или 55h AAh)
Сама же структура раздела такая
00 1 Признак активности раздела
01 1 Начало раздела — головка
02 1 Начало раздела — сектор (биты 0—5), цилиндр (биты 6, 7)
03 1 Начало раздела — цилиндр (старшие биты 8, 9 хранятся в байте номера сектора)
04 1 Код типа раздела
05 1 Конец раздела — головка
06 1 Конец раздела — сектор (биты 0—5), цилиндр (биты 6, 7)
07 1 Конец раздела — цилиндр (старшие биты 8, 9 хранятся в байте номера сектора)
08 4 Смещение первого сектора
0C 4 Количество секторов раздела
В современных дисках головка, сектор и цилиндр вообще ни к чему. К тому же, даже смещение или первого сектора количество секторов на диске, описанных через 4 байта, может быть не таким большим. Ну сколько он может быть? Допустим, сектор занимает 512 байт, а максимально адресуемый размер диска получится 2^32*2^9 = 2^41 байт. Так как 1 гигабайт = 2^30 байт, то тогда 2^41/2^30 = 2^11 гигабайт (2048 Гб), что равно 2 терабайта. Сейчас диски есть на 4 Тб, какой там 2 Тб. И вот как с этим справляется наш герой? Все на просмотр картины второй! (Шутка) На самом деле для больших дисков используется GPT-механизм, который только в UEFI есть, а в старом BIOS этого нет. Никто не знал, что 4Тб диски вообще будут в природе. Но поскольку мои диски более 512 Гб не особо превышают, то мне норм.

§ Обновление сектора

Я думаю, что нужно написать простой makefile для обновления сектора
all:
rm -f *.lock
fasm boot.asm
dd conv=notrunc if=boot.bin of=disk.img bs=446 count=1
bochs -f c.bxrc -q > /dev/null 2>&1
Сначала удаляются лок-файлы, если они были, потом ассемблируется файл boot.asm, далее с помощью утилиты dd заменяются первые 446 байта на диске и запускается bochs.
Сам же код boot.asm максимально прост:
org 7C00h
cld ; На всякий случай
sti ; Чтобы реагировал на CTRL+ALT+DEL
xor ax, ax
mov ds, ax
mov es, ax
mov ss, ax
mov sp, 7C00h
jmp $
В этом коде просто устанавливаются сегментные регистры в 0 и стек ставится под кодом бут-сектора.
Напишу Hello World, используя функцию BIOS INT 10h
mov si, ab ; Ссылка на строку
@@: mov ah, 0Eh ; 0Eh печать символа в терминале
lodsb ; Загрузка символа из DS:SI
and al, al ; Проверка AL на 0
je $ ; Если 0, остановить процессор
int 10h ; Напечатать символ
jmp @b ; Перейти к следующему
 
ab: db "Hello World",0
Чтобы выгрузить данный бут-сектор на реальную флешку, можно воспользоваться такой командой
sudo dd conv=notrunc if=boot.bin of=/dev/sdX bs=446 count=1
Где /sdX – это устройство флешки, например, sdd или sde, это надо узнать через команду df -h, там будет выведен список подключенных устройств.
Я вот проверил свой код, все работает хорошо.

§ Вариант с загрузкой 32Кб

org 7c00h
macro brk { xchg bx, bx }
 
cli
cld
xor ax, ax
mov ds, ax
mov es, ax
mov ss, ax
mov sp, 7C00h
 
; Задача boot-сектора это загрузить и запустить программу
; Конечно же, лучше чтобы программа была в FAT, но пока что так
 
mov [7C00h], dl ; Сохранить DL (Drive Letter ;)
mov ah, 41h ; Проверка на поддержку режима DAP
mov bx, 55AAh
int 13h
jc error
mov ah, 42h ; Загрузить сектор в память
mov si, DAP
mov dl, [7C00h]
int 13h
jc error
jmp 0 : 8000h ; Передача управления
 
; Ничего не делать
error: jmp $
 
; ------------------------------------------------------------------------------
DAP: dw 0010h ; 0 | размер DAP = 16
dw 0040h ; 2 | читать 64 секторов (32 кб)
dw 0000h ; 4 | смещение (=0)
dw 0800h ; 6 | сегмент (=800h) * 10h = 0000:8000
dq 1 ; 8 | номер сектора от 0 до N-1 (1 = второй сектор)

§ Вариант I: Простой

Качается 64 сектора (32Кб) с диска в память и запуск.
macro brk { xchg bx, bx }
org 7c00h
 
jmp short start
db 00h
 
; ----------------------------------------------------------------------
; BPB: Bios Parameter Block
; ----------------------------------------------------------------------
db "FLOPPY12" ; 03 Signature
dw 200h ; 0B Bytes in sector
db 1 ; 0D Sectors by cluster
dw 65 ; 0E Count reserver sectors (32K+Boot)
db 2 ; 10 Count of FAT
dw 00E0h ; 11 Count of Root Entries (224)
dw 0B40h ; 13 Total count of sectors
db 0F0h ; 15 Media
dw 9 ; 16 Sectors in FAT
dw 12h ; 18 Sectors on track
dw 2 ; 1A Count of heads
dd 0 ; 1C Hidden Sectors (large)
dd 0 ; 20 Total Sectors
db 0 ; 24 Number of Phys.
db 1 ; 25 Flags
db 29h ; 26 Ext Sig
dd 07E00000h ; 27 Serial Numbers ES:BX
db 'CORE BIN' ; 2B Label / Exec File
db 'FAT12 ' ; 36 Type of FS
; ----------------------------------------------------------------------
 
start: cli
cld
xor ax, ax
mov ds, ax
mov es, ax
mov ss, ax
mov esp, 7c00h
mov ax, 1
mov bx, $8000
mov cx, 64
@@: call ReadSector
add bx, 512
inc ax
loop @b
jmp 0 : $8000
ret
 
; ----------------------------------------------------------------------
; AX - number of sector, ES:BX pointer to data place
; ----------------------------------------------------------------------
 
ReadSector:
 
push ax cx
mov cx, 12h
cwd
div cx
xchg ax, cx
mov dh, cl
and dh, 1
shr cx, 1
xchg ch, cl
shr cl, 6
inc dx
or cl, dl
mov dl, 0
mov ax, 0201h
int 13h ; es:bx, cx/dx
pop cx ax
ret
 
; ----------------------------------------------------------------------
; ESTIMATED FILL ZERO
; ----------------------------------------------------------------------
 
times 7c00h + (512 - 2) - $ db 0x00
dw 0xAA55

§ Вариант II: Поиск загрузочного файла

Расширенный код, загрузка из FAT12 файла. Код написан с использованием fasm.
macro brk { xchg bx, bx }
org 7c00h
 
jmp short start
db 00h
 
; ----------------------------------------------------------------------
; BPB: Bios Parameter Block
; ----------------------------------------------------------------------
db "FLOPPY12" ; 03 Signature
dw 200h ; 0B Байт в секторе
db 1 ; 0D Секторов на кластер
dw 1 ; 0E Количество зарезервированных секторов
db 2 ; 10 Количество FAT
dw 00E0h ; 11 Количество Root Entries (224)
dw 0B40h ; 13 Всего секторов
db 0F0h ; 15 Параметр media
dw 9 ; 16 Секторов в FAT
dw 12h ; 18 Секторов на дорожке (18)
dw 2 ; 1A Количество дорожек
dd 0 ; 1C Скрытых секторов (large mode)
dd 0 ; 20 Всего секторов (large mode)
db 0 ; 24 Номер физического девайса
db 1 ; 25 Флаги
db 29h ; 26 Расширенная сигнатура
dd 07E00000h ; 27 Серийный номер, но используется для ES:BX
db 'DEMO BIN' ; 2B Лейбл, используется для файла загрузки
db 'FAT12 ' ; 36 Тип файловой системы
; ----------------------------------------------------------------------
 
start:
cli ; IF=0
cld ; DF=0
xor ax, ax ; Очистка DS: ES: SS: сегментов
mov ds, ax
mov es, ax
mov ss, ax
mov sp, 7c00h ; Установка стека
mov ax, 19 ; Сектор 19-й содержит первый сектор
dir: les bx, [7c27h] ; root-каталога и он читается в $7E00
call ReadSector ; с диска, сектор задается через LBA (AX)
mov di, bx ; ES:DI - Начало записей в ROOT
mov bp, 16 ; 16 файлов в секторе из 512 байт
item: mov si, 7c2bh ; DS:SI - Проверка имени формата 8.3
mov cx, 12 ; 11 + 1 - Сравниваем 11 символов (+1)
push di
repe cmpsb ; Сравнение строк
pop di
jcxz file_found ; Файл был найден, перейти к загрузке
add di, 32 ; Иначе искать следующую строку
dec bp
jne item ; Если просмотр в секторе закончился
inc ax ; Перейти к следующему сектору
sub word [7c11h], 16 ; Кол-во оставшихся записей
jne dir ; Сектора еще не закончились в ROOT?
int 18h ; Нет запускного файла
 
; ----------------------------------------------------------------------
; Загрузка файла по адресу 8000h
; ----------------------------------------------------------------------
 
file_found:
 
mov ax, [es: di + 1Ah] ; Номер кластера
mov [7c22h], word 800h ; Адрес, где начинатся программа
 
next: push ax ; В AX содержится номер читаемого кластера
add ax, 31 ; 33-сектор находится DATA-секция
les bx, [7c20h] ; Область памяти, куда будет читать
call ReadSector
add [7c22h], word 20h ; +20h сегментов (32*16=512 байт)
pop ax ; Искать следующий кластер по текущему
mov bx, 3 ; Умножить на 3 полубайта (по 4 бита)
mul bx ; Поскольку FAT12 -- 12-битная
push ax
shr ax, 1 + 9 ; Найти номер сектора FAT
inc ax ; Пропуск +1 BPB
mov si, ax ; Сохранить номер этого сектора на потом
les bx, [7c27h] ; Скачать сюда ES:BX=07e0:0000
call ReadSector
pop ax ; Восстановить номер кластера
mov bp, ax ; Теперь он в BP
mov di, ax
shr di, 1 ; Найти кластер в загруженном секторе FAT
and di, 0x1FF ; Срезать лишние биты
mov ax, [es: di] ; Загрузка в AX указателя на следующий кластер
cmp di, 0x1FF ; Если не хватает данных (конец сектора FAT)
jne @f
push ax ; Сохраняем AX
xchg ax, si
inc ax ; Следующий сектор FAT
call ReadSector ; Читается в 7E00:0000
pop ax
mov ah, [es: bx] ; И дополняем AH (которого не хватало)
@@: test bp, 1 ; Если младший бит кластера равен 0
jz @f ; То ничего не делать
shr ax, 4 ; Иначе, нечетные кластеры сдвинуть вправо на 4 бита
@@: and ax, 0x0FFF ; Ограничить от 0 до 4095
cmp ax, 0x0FF0 ; Если следующий кластер < 4080
jb next ; Продолжить чтение, если так
jmp 0 : $8000 ; Иначе запускает программу
 
; ----------------------------------------------------------------------
; AX - номер сектора от 0 до n, ES:BX указывает на данные
; ----------------------------------------------------------------------
 
ReadSector:
 
push ax bx ; Сохранить AX, BX
mov cx, 12h ; 18 секторов на дорожку
cwd ; DX=0
div cx ; Разделить LBA на 18
xchg ax, cx ; Результат в CX
mov dh, cl ; Сохранить CL в DH
and dh, 1 ; В DH будет номер головки (0 или 1)
shr cx, 1 ; В CX будет номер цилиндра
xchg ch, cl ; В CL будет старший цилиндр, в CH младший
shr cl, 6 ; В старших 2-х битах старшие 2 бита цилиндра
inc dx ; Номер головки 1 или 2
or cl, dl ; Объединить биты [5:0] сектора и старшего цилиндра
mov dl, 0 ; Номер диска 00 - Floppy
mov ax, 0201h ; Чтение данных
int 13h ; ES:BX, параметры CHS в CX/DX
pop bx ax ; Восстановить AX, BX
ret
 
; ----------------------------------------------------------------------
; Оставшееся пространство заполнить нулями
; ----------------------------------------------------------------------
 
times 7c00h + (512 - 2) - $ db 0x00
dw 0xAA55