Средства разработки приложений

Загрузочный сектор (boot.S)


Сознательно не буду приводить листингов программ. Так станут понятней основные идеи, да и вам будет намного приятней, если все напишите своими руками.

Для начала определимся с основными константами.

START_HEAD = 0 - Головка привода, которою будем использовать.

START_TRACK = 0 - Дорожка, откуда начнем чтение.

START_SECTOR = 2 - Сектор, начиная с которого будем считывать наше ядрышко.

SYSSIZE = 10 - Размер ядра в секторах (каждый сектор содержит 512 байт)

FLOPPY_ID = 0 - Идентификатор привода. 0 - для первого, 1 - для второго

HEADS = 2 - Количество головок привода.

SECTORS = 18 - Количество дорожек на дискете. Для формата 1.44 Mb это количество равно 18.

В процессе загрузки будет происходить следующее. Загрузчик BIOS считает первый сектор дискеты, положит его по адресу 0000:0x7c00 и передаст туда управление. Мы его получим и для начала переместим себя пониже по адресу 0000:0x600, перейдем туда и спокойно продолжим работу. Собственно вся наша работа будет состоять из загрузки ядра (сектора 2 - 12 первой дорожки дискеты) по адресу 0x100:0000, переходу в защищенный режим и скачку на первые строки ядра. В связи с этим еще несколько констант:

BOOTSEG = 0x7c00 - Сюда поместит загрузочный сектор BIOS.

INITSEG = 0x600 - Сюда его переместим мы.

SYSSEG = 0x100 - А здесь приятно расположится наше ядро.

DATA_ARB = 0x92 - Определитель сегмента данных для дескриптора

CODE_ARB = 0x9A - Определитель сегмента кода для дескриптора.

Первым делом произведем перемещение самих себя в более приемлемое место. cli xor ax, ax mov ss, ax mov sp, #BOOTSEG mov si, sp mov ds, ax mov es, ax sti cld mov di, #INITSEG mov cx, #0x100 repnz movsw jmpi go, #0 ; прыжок в новое местоположение загрузочного сектора на метку go

Теперь необходимо настроить как следует сегменты для данных (es, ds) и для стека. Это конечно неприятно, что все приходится делать вручную, но что делать. Ведь нет никого в памяти компьютера, кроме нас и BIOS. go: mov ax, #0xF0 mov ss, ax mov sp, ax ; Стек разместим как 0xF0:0xF0 = 0xFF0 mov ax, #0x60 ; Сегменты для данных ES и DS зададим в 0x60 mov ds, ax mov es, ax

Наконец можно вывести победное приветствие.
Пусть мир узнает, что мы смогли загрузиться. Поскольку у нас есть все-таки еще BIOS, воспользуемся готовой функцией 0x13 прерывания 0x10. Можно конечно презреть его и написать напрямую в видеопамять, но у нас каждый байт команды на счету, а байт таких всего 512. Потратим их лучше на что-нибудь более полезное. mov cx,#18 mov bp,#boot_msg call write_message Функция write_message выгдядит следующим образом write_message: push bx push ax push cx push dx push cx mov ah,#0x03 ; прочитаем текущее положение курсора, дабы не выводить сообщения где попало. xor bh,bh int 0x10 pop cx mov bx,#0x0007 ; Параметры выводимых символов : видеостраница 0, аттрибут 7 (серый на черном) mov ax,#0x1301 ; Выводим строку и сдвигаем курсор. int 0x10 pop dx pop cx pop ax pop bx ret А сообщение так


boot_msg: .byte 13,10 .ascii "Booting data ..." .byte 0 К этому времени на дисплее компьютера появится скромное "Booting data ..." . Это в принципе уже "Hello World", но давайте добьемся чуточку большего. Перейдем в защищенный режим и выведем этот "Hello" уже из программы написаной на C. Ядро 32-разрядное. Оно будет у нас размещаться отдельно от загрузочного сектора и собираться уже gcc и gas. Синтаксис ассемблера gas соответсвует требованиям AT&T, так что тут уже все проще. Но для начала нам нужно прочитать ядро. Опять воспользуемся готовой функцией 0x2 прерывания 0x13. recalibrate: mov ah, #0 mov dl, #FLOPPY_ID int 0x13 ; производим переинициализацию дисковода. jc recalibrate call read_track ; вызов функции чтения ядра jnc next_work ; если во время чтения не произошло ничего плохого то работаем дальше bad_read: ; если чтение произошло неудачно то выводим сообщение об ошибке mov bp,#error_read_msg mov cx,7 call write_message inf1: jmp inf1 ; и уходим в бесконечный цикл. Теперь нас спасет только ручная перезагрузка Сама функция чтения предельно простая: долго и нудно заполняем параметры, а затем одним махом считываем ядро. Усложнения начнуться, когда ядро перестанет помещаться в 17 секторах ( то есть 8.5 kb), но это пока только в будущем, а пока вполне достаточно такого молниеносного чтения.


read_track: pusha push es push ds mov di, #SYSSEG ; Определяем mov es, di ; адрес буфера для данных xor bx, bx mov ch, #START_TRACK ;дорожка 0 mov cl, #START_SECTOR ;начиная с сектора 2 mov dl, #FLOPPY_ID mov dh, #START_HEAD mov ah, #2 mov al, #SYSSIZE ;считать 10 секторов int 0x13 pop ds pop es popa ret Вот и все. Ядро успешно прочитано и можно вывести еще одно радостное сообщение на экран. next_work: call kill_motor ; останавливаем привод дисковода mov bp,#load_msg ; выводим сообщение mov cx,#4 call write_message Вот содержимое сообщения load_msg: .ascii "done" .byte 0 А вот функция остановки двигателя привода. kill_motor: push dx push ax mov dx,#0x3f2 xor al,al out dx,al pop ax pop dx ret На данный момент на экране выведено "Booting data ...done" и лампочка привода флоппи-дисков погашена. Все затихли и готовы к смертельному номеру - прыжку в защищенный режим. Для начала надо включить адресную линию A20. Это в точности означает, что мы будем использовать 32-разрядную адресацию к данным. mov al, #0xD1 ; команда записи для 8042 out #0x64, al mov al, #0xDF ; включить A20 out #0x60, al Выведем предупреждающее сообщение, о том, что переходим в защищенный режим. Пусть все знают, какие мы важные. protected_mode: mov bp,#loadp_msg mov cx,#25 call write_message (Сообщение: loadp_msg: .byte 13,10 .ascii "Go to protected mode..." .byte 0 ) Пока еще у нас жив BIOS, запомним позицию курсора и сохраним ее в известном месте ( 0000:0x8000 ). Ядро позже заберет все данные и будет их использовать для вывода на экран победного сообщения. save_cursor: mov ah,#0x03 ; читаем текущую позицию курсора xor bh,bh int 0x10 seg cs mov [0x8000],dx ;сохраняем в специальном тайнике Теперь внимание, запрещаем прерывания (нечего отвлекаться во время такой работы) и загружаем таблицу дескрипторов cli lgdt GDT_DESCRIPTOR ; загружаем описатель таблицы дескрипторов. У нас таблица дескрипторов состоит из трех описателей: Нулевой (всегда должен присутствовать), сегмента кода и сегмента данных .align 4 .word 0 GDT_DESCRIPTOR: .word 3 * 8 - 1 ; размер таблицы дескрипторов .long 0x600 + GDT ; местоположение таблицы дескрипторов .align 2 GDT: .long 0, 0 ; Номер 0: пустой дескриптор .word 0xFFFF, 0 ; Номер 8: дескриптор кода .byte 0, CODE_ARB, 0xC0, 0 .word 0xFFFF, 0 ; Номер 0x10: дескриптор данных .byte 0, DATA_ARB, 0xCF, 0 Переход в защищенный режим может происходить минимум двумя способами, но обе ОС , выбранные нами для примера (Linux и Thix) используют для совместимости с 286 процессором команду lmsw.Мы будем действовать тем же способом mov ax, #1 lmsw ax ; прощай реальный режим. Мы теперь находимся в защищенном режиме. jmpi 0x1000, 8 ; Затяжной прыжок на 32-разрядное ядро. Вот и вся работа загрузочного сектора - немало, но и немного. Теперь мы попрощаемся с ним и направимся к ядру. В конце ассемблерного файла полезно добавить следующую инструкцию. .org 511 end_boot: .byte 0 В результате скомпилированный код будет занимать ровно 512 байт, что очень удобно для подготовки образа загрузочного диска.

Содержание раздела