Как и обещал, сегодня мы начнем писать свой загрузочный сектор (boot sector).
Сразу скажу, что в этом исходнике опытные люди не увидят ничего особенного,
может быть даже наоборот, кому-то покажется что все можно было сделать гораздо
лучше, не спорю. Я не очень старался. Про законченность говорить пока рано, это
все еще неоднократно будет меняться.
Как я уже упоминал, boot sector загружается в память по адресу 0:7c00h и
имеет длину 512 байт. Это не слишком много, поэтому возможности boot sector'a
ограничиваются загрузкой какого либо вторичного загрузчика.
Наш boot sector, по образу и подобию linux, будет загружать в память два
блока. Первым является тот самый вторичный загрузчик, у нас он, как и в linux,
называется setup. Вторым является собственно ядро.
Этот boot sector служит для загрузки ядра с дискет, поэтому, на первых порах,
он жестко привязан к диску "a:".
BIOS предоставляет возможность читать по нескольку секторов сразу, но не
более чем до границы дорожки. Такая возможность, конечно, ускоряет чтение с
диска, но представляет собой большие сложности в программировании, так как надо
учитывать границы сегментов (в реальном режиме сегмент может быть не больше, чем
64к) и границы дорожек, получается достаточно хитрый алгоритм.
Я пошел немного другим путем. Я читаю с диска по секторам. Это, конечно,
медленнее, но я думаю, что здесь скорость не очень критична. За то это гораздо
проще и компактнее реализуется.
%define SETUP_SEG 0x07e0 %define SETUP_SECTS 10 %define KERNEL_SEG 0x1000 %define KERNEL_SECTS 1000
Для начала описываем место и размер для каждого загружаемого
блока.
Размеры пока произвольные, поскольку все остальное еще предстоит
написать.
section .text BITS 16 org 0x7c00
Как я уже говорил, boot sector загружается и запускается по адресу
0:7c00h Содержимое регистров при старте таково:
Прерывания запрещены! Про содержание остальных
регистров мне ничего не известно, если кто-то, что-то знает, напишите мне.
Остальные регистры мы будем инициализировать самостоятельно.
entry_point: mov ax, cs cli mov ss, ax mov sp, entry_point sti mov ds, ax
Стек у нас будет располагаться перед программой, до служебной области
BIOS еще остается порядка 30 килобайт, для стека больше чем достаточно.
Прерывания изначально запрещены, но я все равно сделаю это самостоятельно, на
всякий случай. и разрешу после установки стека. Никаких проблем это вызвать,
по-моему, не должно.
Так же, нулевым значением, инициализируем сегментный
регистр ds.
; Сохpаняем фоpму куpсоpа mov ah, 3 xor bh, bh int 0x10 push cx ; отключаем куpсоp mov ah, 1 mov ch, 0x20 int 10h
Чтобы все было красиво и радовало глаз, мы на время чтения отключим
курсор. Иначе он будет мелькать на экране. Чтобы его потом восстановить, как и
был, мы сохраняем его форму в стеке.
; Загpужаем setup mov ax, SETUP_SEG mov es, ax mov ax, 1 mov cx, SETUP_SECTS mov si, load_setup_msg call load_block call outstring mov si, complete_msg call outstring
Загружаем первый блок (setup). Процедуру загрузки блока мы рассмотрим
немного позже. А в остальном здесь, по-моему, все понятно.
; загpужаем ядpо. mov ax, KERNEL_SEG mov es, ax mov ax, 1 + SETUP_SECTS mov cx, KERNEL_SECTS mov si, load_kernel_msg call load_block call outstring mov si, complete_msg call outstring
Загружаем второй блок (kernel). Здесь все в точности аналогично первому блоку.
; Восстанавливаем куpсоp pop cx mov ah, 1 int 0x10
Восстанавливаем форму курсора.
; Пеpедаем упpавление на setup jmp SETUP_SEG:0
На этом работа boot sector'а заканчивается. Дальним переходом мы передаем
управление программе setup.
Далее располагаются функции.
; Загрузка блока ; cx - количество сектоpов ; ax - начальный сектоp ; es - указатедь на память ; si - loading message
Функция загрузки блока. Она же занимается выводом на экран процентного счетчика.
load_block: mov di, cx ; сохpаняем количество блоков .loading: xor bx, bx call load_sector inc ax mov bx, es add bx, 0x20 mov es, bx ; Выводим сообщение о загpузке. call outstring push ax ; Выводим пpоценты ; ((di - cx) / di) * 100 mov ax, di sub ax, cx mov bx, 100 mul bx div di call outdec push si mov si, persent_msg call outstring pop si pop ax loop .loading ret
В этой функции, по-моему, ничего сложного нет. Обыкновенный цикл.
А вот следующая функция загружает с диска отдельный сектор, при этом оперируя
его линейным адресом. В своей работе мы пока ориентируемся только на чтение с floppy диска,
размером 1,4 мегабайта. Поэтому будем использовать старомодную функцию, которой
в качестве параметров задается номер дорожки, головки и сектора.
Есть так называемое int13 extension, разработанное
совместно фирмами MicroSoft и Intel. Это расширение BIOS работает почти
аналогичным образом, Считывая сектора по их линейным адресам, но оно
поддерживается не всеми BIOS, имеет несколько разновидностей и работает в
основном для жестких дисков. Поэтому нам не подходит.
; Загрузка сектора ; ax - номеp сектоpа (0...max (2880)) ; es:bx - адpес для pазмещения сектоpа.
Абсолютный номеp сектоpа вычисляется по фоpмуле: AbsSectNo = (CylNo * SectPerTrack * Heads) + (HeadNo * SectPerTrack) + (SectNo - 1) Значит обpатное спpаведливо так: CylNo = AbsSectNo / (SectPerTrack * Heads) HeadNo = остаток / SectorPerTrack SectNo = остаток + 1
load_sector: push ax push cx cwd mov cx, 18 ; SectPerTrack div cx mov cx, dx inc cx ; количество сектоpов
Поделив номер сектора на количество секторов на дорожке, мы в остатке получаем номер сектора на дорожке. Это значение хранится в 6 младших битах регистра cl.
xor dx, dx ; dl - диск - 0!
Номер диска храниться в dl и устанавливается в 0 (это диск a:)
shr ax, 1 rcl dh, 1 ; номер головки
Младший бит частного определяет для нас номер головки. (0 или 1)
mov ch, al shl ah, 4 or cl, ah ; количество доpожек
Оставшиеся биты частного определяют для нас номер цилиндра (или
дорожки).
восемь младших бит номера хранятся в регистре ch, два старших бита
номера хранятся в двух старших битах регистра cl.
.rept: mov ax, 0x201 int 0x13 jnc .read_ok push si mov si, read_error call outstring movzx ax, ah call outdec mov si, crlf call outstring xor dl, dl xor ah, ah int 0x13 pop si jmp short .rept
В случае ошибки чтения мы не будем возвращать из функции какие-либо результаты, а будем повторять чтение, пока оно не окажется успешным. Ведь в случае неуспешного чтения у нас все равно ничего не будет работать! Для верности мы, в случае сбоя, производим сброс устройства.
.read_ok: pop cx pop ax ret
Далее идет две интерфейсные функции, обеспечивающие вывод на экран строк и десятичных цифр. Ничего особенного они из себя не представляют а для вывода пользуются телетайпным прерыванием BIOS (ah = 0eh, int 10h), которое обеспечивает вывод одного символа с обработкой некоторых служебных кодов.
; Вывод стpоки. ; ds:si - стpока. outstring: push ax push si mov ah, 0eh jmp short .out .loop: int 10h .out: lodsb or al, al jnz .loop pop si pop ax ret
Эта функция ограничена выводом чисел до 99 включительно, случай с большим числом обрабатывается как переполнение и отображается как '##'.
; Вывод десятичных чисел от 0 до 99 ; ax - число! outdec: push ax push si mov bl, 10 div bl cmp al, 10 jnc .overflow add ax, '00' push ax mov ah, 0eh int 0x10 pop ax mov al, ah mov ah, 0eh int 0x10 jmp short .exit .overflow: mov si, overflow_msg call outstring .exit: pop si pop ax ret
Далее располагаются несколько служебных сообщений.
load_setup_msg: db 'Setup loading: ', 0 load_kernel_msg: db 'Kernel loading: ', 0 complete_msg: db 'complete.' crlf: db 0ah, 0dh, 0 persent_msg: db '%', 0dh, 0 overflow_msg: db '##', 0 read_error: db 0ah, 0dh db 'Read error #', 0 TIMES 510-($-$$) db 0
Эта комбинация заполняет оставшееся место в секторе нулями. А остается у нас еще около 200 байт.
dw 0aa55h
Последние два байта называются "Partition table signature", что не совсем корректно. Фактически эта сигнатура говорит BIOS'у о том, что этот сектор является загрузочным.
Этот boot sector, помимо того, что читает по секторам, отличается от
линуксового еще и размещением в памяти. После загрузки он не перемещает себя в
памяти, и работает по тому же адресу, по которому его загрузил BIOS. Так же
setup загружается непосредственно следом за boot sector'ом, с адреса 7e00h, что
в принципе не помешает ему работать в других адресах, если мы будем загружать
наше ядро через LILO, например.
Скомпилированную версию boot sector'а вы можете найти в
файловом архиве (секция
"наработки").
Надеюсь, что я достаточно доходчиво объясняю, если кому-то что-то не понятно
- пишите.
В следующем выпуске мы перейдем к программе setup и рассмотрим порядок
перехода в защищенный режим. А заодно я более подробно расскажу про этот режим
процессора.
Отправлено 2001-07-27 для 3016 подписчиков.
ведущий рассылки Dron
Архив
Рассылки
При поддержке Kalashnikoff.ru