Создание операционной системы на ассемблере

         

А теперь давайте разбираться, как это все работает.


%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



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