Подведу некоторый итог процессу загрузки. В предыдущих выпусках я уже писал
про файловые системы, про форматы файлов, и про защищенный режим тоже писал.
Сейчас я это немного обобщу.
То, что я до сих пор сделал пока рассчитано только на работы с дисками 1,4Мб,
то есть с флопами. Это конечно ограничение в некоторой степени, но пока система
еще далеко не готова, этого достаточно. Естественно это еще не окончательный
вариант. Да и можно ли говорить об окончательности программных продуктов? Нет
предела совершенству. :)
В обязанности бутсектора входит следующее:
Если с первым и двумя последними пунктами все просто и компактно, то второй и
третий пункт требуют возможности работы с файловой системой, а третий пункт
помимо этого должен знать структуру бинарных форматов. На все это не хватает 512
байт, отводимых для бутсектора. Наш бутсектор занимает больше - один
килобайт.
В файловой системе EXT2 с этим не возникает никаких проблем,
поскольку первый килобайт файловой системы не используется.
В FAT это немного
сложнее. Служебная структура, именуемая Boot Sector Record (BSR), содержит в
себе все необходимые поля для выделения для загрузочного сектора места более чем
512 байт. Но как это сделать при форматировании, стандартными средствами, я не
нашел. И если формат диска не соответствует каким-то внутренним представлениям
Windows, то содержимое такого нестандартного диска может быть испорчено. Выход
был найден случайно. Как оказалось утилита format хоть и не имеет таких
параметров командной строки, но перед форматированием берет информацию из BSR. И
если предварительно заполнить эту структуру (с нужными нам параметрами), а потом
уже форматировать, то все получается так, как хочется нам. Таким образом, у меня
получилось сделать диск, у которого два сектора зарезервированы (там будет
размещаться boot), и одна копия FAT.
Ну теперь давайте по порядку рассмотрим все этапы работы бутсектора.
Бутсектор загружается БИОСом по адресу 0:7c00h занимает он 512 байт. Память
начиная с адреса 0:7e00h свободна. но в эту память мы загрузим второй сектор
бута. Одновременно загружается информация необходимая для обслуживания файловой
системы. Для EXT2 дополнительно необходимо загрузить два килобайта (суперблок и
дескрипторы групп), для FAT немного больше - 4,5 килобайта (первая копия
FAT).
mov ax, 0x7e0 mov es, ax
Адрес 0:7e00h идентичен адресу 7e0h:0. Вторым вариантом мы и будем
пользоваться, потому что наша процедура загрузки секторов размещает их по
сегментному адресу, хранящемуся в es.
mov ax, 1
В ax номер сектора, с которого начинается чтение (первый сектор является нулевым (каламбур :). И далее все зависит от файловой системы.
%ifdef EXT2FS mov cx, 5
Для EXT2 загружается 5 секторов - второй сектор бутсектора (1 сектор),
суперблок файловой системы (2 сектора) и дескрипторы групп (2 сектора).
%elifdef FATFS mov cx, 10
Для FAT загружается 10 секторов - второй сектор бутсектора (1 сектор),
таблица FAT - 9 секторов (такой размер она имеет на floppy дисках).
%else %error File system not specified %endif call load_block
Все. первый пункт загрузки выполнен.
Функции обслуживания файловых систем имеют одинаковый интерфейс. Cобственно
их всего две fs_init и fs_load_file. Естественно у них различаются реализации,
но в процессе компиляции выбирается используемая файловая система. Для
совместного использования нам никак не хватит одного килобайта, да и не за чем
это.
Из-за сложности VFAT (FAT с длинными именами) он не реализован. Все имена
на диске FAT должна иметь формат 8.3
В файловой системе FAT я не оперирую
принятыми в MS системах именами дисков и при указании пути использую путь
относительно корневой директории диска (как это делается в юникс
системах).
Файл конфигурации у нас пока называется boot.rc и находится в каталоге /etc.
Формат у этого файла достаточно нестрогий. Из-за нехватки места в boot секторе
там сделана реакция только на ключевые слова, которыми являются:
Предварительно проинициализировав файловую систему
call fs_init
Мы загружаем этот файл с диска.
mov si, boot_config call fs_load_file ... boot_config: db '/etc/boot.rc', 0
Содержимое файла конфигурации такое:
kernel /boot/kernel
#end
Модулей у нас пока никаких нет, да и ядро еще в зачаточном состоянии. Но речь
сейчас не об этом.
Про этот момент я не буду особо расписывать, желающие могут посмотреть в
исходниках, которые в скором времени появятся на сайте.
Скажу только, что
программа анализирует файл конфигурации, в соответствии с ключевыми словами
загружает ядро (которое может быть в единственном экземпляре) и любое количество
модулей. Общий объем ядра и модулей ограничен свободным размером базовой памяти
(около 600к).
Перейдем к предпоследнему пункту.
Бутсектор не особо беспокоится об организации памяти в системе - это забота
ядра. Для перехода в защищенный режим он описывает всего два сегмента: сегмент
кода и сегмент данных. оба сегмента имеют базовый адрес - 0 и предел в 4
гигабайта (это нам пригодиться для проверки наличия памяти).
Перед переходом в защищенный режим нам необходимо включить адресную линию
A20. По моим сведениям этот механизм ввели в пору 286 для предотвращения
несанкционированных обращений к памяти свыше одного мегабайта (непонятно
зачем?). Но поскольку это имеет место быть - нам это нужно обрабатывать, иначе
каждый второй мегабайт будет недоступен. Делается это почему-то через контроллер
клавиатуры (еще одна загадка).
mov al, 0xd1 out 0x64, al mov al, 0xdf out 0x60, al
После этого можно переходить в защищенный режим.
lgdt [gd_desc]
В регистр gdtr загружается дескриптор GDT.
push byte 2 popf
Очищается регистр флагов.
mov eax, cr0 or al, 1 mov cr0, eax
Включается защищенный режим. Не смотря на то, что процессор уже находится
в защищенном режиме, мы пока работаем в старых адресах. Чтобы от них уйти нам
нужно сделать дальный переход в адреса защищенного режима.
jmp 16:.epm BITS 32 .epm:
16 в этом адресе перехода - это не сегмент. Это селектор сегмента
кода.
mov ax, 8 mov ds, ax mov es, ax ; Ставим стек. mov ss, ax movzx esp, sp
После всего этого мы инициализируем сегментные регистры соответствующими
селекторами, в том числе и сегмент стека, но указатель стека у нас не меняется,
только теперь он становится 32-х битным.
... gd_table: ; пеpвый дескpиптоp - данные и стек istruc descriptor at descriptor.limit_0_15, dw 0xffff at descriptor.base_0_15, dw 0 at descriptor.base_16_23, db 0 at descriptor.access, db 0x92 at descriptor.limit_16_19_a, db 0xcf at descriptor.base_24_31, db 0 iend ; втоpой дескpиптоp - код istruc descriptor at descriptor.limit_0_15, dw 0xffff at descriptor.base_0_15, dw 0 at descriptor.base_16_23, db 0 at descriptor.access, db 0x9a ; 0x98 at descriptor.limit_16_19_a, db 0xcf at descriptor.base_24_31, db 0 iend
Это GDT - Глобальная таблица дескрипторов. Здесь всего два дескриптора,
но во избежание ошибок в адресации обычно вводится еще один дескриптор -
нулевой, который не считается допустимым для использования. Мы не будем
резервировать для него место специально, просто начало таблицы сместим на 8 байт
выше.
gd_desc: dw 3 * descriptor_size - 1 dd gd_table - descriptor_size
А это содержимое регистра GDTR. Здесь устанавливается предел и базовый
адрес дескриптора. обратите внимание на базовый адрес, здесь происходит
резервирование нулевого дескриптора.
Теперь процессор находится в защищенном режиме и уже не оперирует сегментами,
а оперирует селекторами. Селекторов у нас всего три. Нулевой - недопустим.
восьмой является селектором данных и шестнадцатый - селектором кода.
После этого управление можно передать ядру. дальше со всем этим будет
разбираться оно.
Здесь вообще все просто. Когда мы загрузили ядро, в файле ядра мы определили
адреса сегмента кода и сегмента данных. Не смотря на то, что ядро имеет вполне
конкретные смещения в сегменте (которые задаются при компиляции), код
инициализации ядра рассчитан на работу без привязки к адресам. Это нужно для
определения количества памяти, после перевода ядра на свои адреса доступ ко всей
памяти будет для ядра затруднен в связи с включением механизма страничного
преобразования.
Итак, переходим к выполнению кода ядра.
mov ebx, kernel_data mov eax, [ebx + module_struct.code_start] jmp eax
В этом фрагменте в eax записывается адрес начала кодового сегмента
ядра.
Так как сегмент кода у нас занимает всю виртуальную память, нам не
важно где находится ядро (хотя мы знаем, что оно было загружено в базовую
память). Мы просто передаем ему управление.
О ядре мы начнем говорить в следующем выпуске. С загрузчиком мы практически
закончили. Он уже обладает достаточной для нас функциональностью.
Как всегда
вы можете задавать свои вопросы, высказывать пожелания по адресу, указанному
ниже.
Хочу принести свои извинения читателям рассылки в текстовом формате.
Что-то на субскрайбе с конвертацией не то.
Отправлено 2001-11-29 для 5592 подписчиков.
ведущий рассылки Dron
Сайт проекта
Архив
Рассылки
При поддержке Kalashnikoff.ru