Как и обещал, сегодня мы начнем писать свой загрузочный сектор (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