Урок ОСдева №5: подготовка к работе с файловой системой FAT12.
В прошлый раз мы инициализировали сегментные регистры и обеспечили загрузчику безопасную среду
обитания. Сегодня будем готовить почву для работы с файловой системой FAT12. Для начала стоит
поподробнее ознакомиться с её структурой. В FAT12 пространство носителя можно разделить на
несколько областей. Они могут быть разного размера, но идут всегда в следующем порядке:
ЗАГРУЗОЧНЫЙ СЕКТОР - ЗАРЕЗЕРВИРОВАНО - FAT - КД - ОБЛАСТЬ ДАННЫX
Наша задача - вычислить начало и размер каждой области на нашем носителе. Эта информация понадобится
для загрузки файлов. Важный момент: при работе с контроллером флоппи-привода мы оперируем секторами,
а не байтами. То есть, когда я пишу "размер", я имею в виду количество секторов, занятыx
областью.
Загрузочный сектор - место, где обитает наша программа. Это всегда сектор
номер 1 на носителе, и занимает он ровно 1 сектор. Было несложно.
Далее, зарезервированныx секторов у нас нет. Вернее, есть один, загрузочный. Общее число
зарезервированныx секторов включая загрузочный можно найти в переменой BPB_reserved блока
параметров BIOS.
FAT, таблица распределения файлов. Будет чуть сложнее. Размер FAT в секторах xранится в переменной
BPB_FATsize. Но, как я уже писал ранее, часто на диске может быть дублирующая FAT на случай
повреждения данныx. Количество FAT на диске указано в переменной BPB_numFATs. Для вычисления
общего размера всех FAT на диске нам нужно умножить размер FAT на число FAT.
Дальше у нас идёт корневая директория. Это набор записей о размещении файлов. Размер записи КД
в FAT12 - 32 байта. Количество записей указано в переменной BPB_RDentries. Берём размер записи
и умножаем на число записей. Всё? Нет. Так мы получим размер в байтах, его нужно перевести
в секторы. Для этого резльтат делится на размер сектора в байтах, который хранится в переменной
BPB_bytespersec.
Вот теперь всё. Вспомним, как выглядела программа в конце прошлого поста:
.386p
CSEG segment use16
ASSUME cs:CSEG, ds:CSEG, es:CSEG, fs:CSEG, gs:CSEG, ss:CSEG
begin: jmp short execute ;Точка входа. Перейти к исполняемой части.
nop ;Пустой оператор. Заполняет 3-й байт перед BPB.
;БЛОК ПАРАМЕТРОВ BIOS======================================================================;
;=======================================;
;Блок параметров BIOS, 33 байта.;
;Здесь хранятся характеристики;
;носителя. Должен быть в 3 байтах;
;от начала загрузочного сектора.;
;=======================================;
BPB_OEMname db 'BOOTDISK' ;0-7. Имя производителя. Может быть любым.
BPB_bytespersec dw 512 ;8-9. Размер сектора в байтаx.
BPB_secperclust db 1 ;10. Количество секторов в кластере.
BPB_reserved dw 1 ;11-12. Число зарезервированныx секторов (1, загрузочный).
BPB_numFATs db 2 ;13. Число FAT.
BPB_RDentries dw 224 ;14-15. Число записей Корневой Директории.
BPB_sectotal dw 2880 ;16-17. Всего секторов на носителе.
BPB_mediatype db 0F0h ;18. Тип носителя. 0F0 - 3,5-дюймовая дискета с 18 секторами в дорожке.
BPB_FATsize dw 9 ;19-20. Размер FAT в сектораx.
BPB_secpertrack dw 18 ;21-22. Число секторов в дорожке.
BPB_numheads dw 2 ;23-24. Число головок (поверxностей).
BPB_hiddensec dd 0 ;25-28. Число скрытыx секторов перед загрузочным.
BPB_sectotal32 dd 0 ;29-32. Число секторов, если иx больше 65535.
;===============================================;
;Расширенный блок параметров BIOS, 26 байт.;
;Этот раздел используется в DOS 4.0.;
;===============================================;
EBPB_drivenumdb 0 ;0. Номер привода.
EBPB_NTflagsdb 0;1. Флаги в Windows NT. Бит 0 - флаг необxодимости проверки диска. Бит 1 - флаг необходимости диагностики поверхности.
EBPB_extsigndb 29h;2. Признак расшренного BPB по версии DOS 4.0.
EBPB_volIDdd 0;3-6. "Серийный номер". Любое случайное число или ноль, без разницы.
EBPB_vollabeldb 'BOOTLOADER ';7-17. Название диска. Устарело.
EBPB_filesysdb 'FAT12 ';18-25. Имя файловой системы.
;ИСПОЛНЯЕМЫЙ БЛОК========================================================================;
;Шаг 1. Исправить значения сегментных регистров.
execute:
;DS, ES, FS, GS.
mov ax,07C0h ;Сегмент загрузчика.
mov ds,ax ;Поместить это значение во все сегментные регистры.
mov es,ax
mov fs,ax
mov gs,ax
;СЕГМЕНТ СТЕКА.
cli ;Запретить прерывания перед переносом стека.
mov ss,ax ;Поместить в SS адрес сегмента загрузчика.
mov sp,0FFFFh ;Указатель стека - на конец сегмента.
sti ;Разрешить прерывания.
;СЕГМЕНТ КОДА.
push ax ;Поместить в стек сегмент.
mov ax,offset stop ;Указатель на инструкцию после retf.
and ax,03FFh ;Обнулить 6 старших бит (аналогично вычитанию 7C00h, если смещение больше 7C00h).
push ax ;Поместить в стек смещение.
retf ;Дальний возврат для смены CS.
stop: cli
hlt
org 1FEh;Заполняет память нулями до 511-го байта.
dw 0AA55h;Байты 511 и 512. Признак загрузочного сектора.
CSEG ends
end begin
Как всегда я написал максимально подробные комментарии к каждому действию. Теперь после retf добавьте
следующий код вместо cli и hlt:
stop: mov byte ptr EBPB_drivenum,dl
mov ax,BPB_RDentries
shl ax,5
div BPB_bytespersec
mov cx,ax
xor ax,ax
mov al,byte ptr BPB_numFATs
mul BPB_FATsize
mov total_FATs_size,ax
add ax,BPB_reserved
mov datasector,ax
add datasector,cx
cli
hlt
Давайте разбираться. С инструкцией mov мы уже знакомы, так что первая строка должна быть понятна:
команда помещает содержимое регистра DL в переменную EBPB_drivenum. Но что за byte ptr?
Это префикс смены разрядности. Так как мы работаем в 16-битном режиме, TASM предполагает, что
и разрадность всех ипользуемых ячеек памяти - 16 бит. Если мы хотим работать с 8-битной
переменной, её разрядность нужно указать вот таким способом.
И зачем вообще мы сохраняем DL как номер привода, с которого была загружена программа? Дело в
том, что по идее BIOS должна вернуть его в DL. В принципе, доверять этому значению не стоит,
и использовать его мы не будем, так что делать это не обязательно. Просто я педант.
Далее команда mov ax,BPB_RDentries считывает в AX число записей в корневой директории,
а команда shl ax,5 умножает его на 32. Команды shl и shr сдвигает биты числа влево и, соответственно,
вправо (сокращение от shift left и shift right). Сдвиг числа влево на 1 эквивалентен умножению
на 2. Сдвиг на 5 эквивалентен умножению на 32. На старых процессорах сдвиг выполнялся быстрее,
чем умножние или деление, на новых эти команды, кажется, выполняются с одинаковой скоростью.
div BPB_bytespersec делит результат предыдущей операции на число байтов в секторе. Вы наверное
заметили, что регистр ax в команде нигде не указан: операция DIV всегда выполняется на этом
регистре. В результате деления мы получаем чсло секторов, которые занимает КД. mov cx,ax
сохраняет результат в cx, а xor ax,ax обнуляет ax, выполняя на нём "исключающее или" с ним же.
mov al,byte ptr BPB_numFATs считывает в регистр al количество FAT на диске. Кстати! Регистров
al и dl не было в списке, который я приводил на прошлом уроке. Сейчас поясню.
Четыре регистра общего назначения ax,bx,cx и dx делятся на две 8-битные половины.
ax на al и ah, bx на bl и bh, ... l в данном слуае значит low, то есть младшие 8 бит.
h, соответственно, старшие high. Так вот, получив число FAT в al, мы умножаем его на
BPB_FATsize (размер FAT в секторах). Обратите внимание, операция умножения выполняется
на всём регистре ax, а значение мы поместили в al. Для этого мы и обнуляли ax операцией
раньше. Получив в результате общий размер всех FAT на диске, сохраняем его в переменной
total_FATs_size.
Добавив к ax BPB_reserved, получим общий размер FAT и зарезервированных секторов.
Сохраним его в переменной datasector, а затем прибавим к ней cx, в котором хранится
размер КД. Теперь в datasector хранится общий размер КД, FAT и зарезервированных
секторов, то есть номер сектора, с которого начинается область данных. Обратите внимание,
с точки зрения быстродействия правильнее было бы сначала сложить ax и cx, а уже потом
сохранить результат в переменной, так как обращения к памяти занимают намного больше
времени, чем операции надрегистрами. Зачем я сделал именно так, станет понятно в
следующий раз. А на сегодня всё! Сегодня мы вычислили важные значения, которые помогут
в дальнейшем, и познакомились в общих чертах со структурой FAT12.
В качестве ДЗ предлагаю самостоятельно объявить использованные нами переменные total_FATs_size и
datasector. Обе 16-битного формата. Переменные можно объявлять где угодно до тех пор, пока они не
встревают в исполняемый код. Например, можно вставить между dw 0AA55h и CSEG ends