А теперь давайте разбираться, как это все работает.
%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 Содержимое регистров при старте таково:
- cs содержит 0
- ip содержит 7с00h
Прерывания запрещены! Про содержание остальных регистров мне ничего не известно, если кто-то, что-то знает, напишите мне. Остальные регистры мы будем инициализировать самостоятельно.
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
В этой функции, по-моему, ничего сложного нет. Обыкновенный цикл.
А вот следующая функция загружает с диска отдельный сектор, при этом оперируя его линейным адресом.
Есть так называемое int13 extension, разработанное совместно фирмами MicroSoft и Intel. Это расширение BIOS работает почти аналогичным образом, Считывая сектора по их линейным адресам, но оно поддерживается не всеми BIOS, имеет несколько разновидностей и работает в основном для жестких дисков. Поэтому нам не подходит.
В своей работе мы пока ориентируемся только на чтение с floppy диска, размером 1,4 мегабайта. Поэтому будем использовать старомодную функцию, которой в качестве параметров задается номер дорожки, головки и сектора.
; Загрузка сектора ; 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
Содержание раздела