Доработайте real_kernel из подсказки, чтобы ваше ядро работало как в предыдущем задании.
Основы разработки ядра ОС [можно не делать] / подсказка к 3 задачке
Сейчас у нас система работает ровно с одним сектором памяти, потому что BIOS умеет грузить только один сектор в 512байт и по сути наш код ограничен этим размером. То есть наша программа может содержать не более 512 байт кода и это с учетом данных.
Это весьма существенное ограничение и было бы не плохо иметь возможность грузить данные, например, не из программы, а с диска.
Для этого в bios имеются прерывания для запроса данных с диска. Наш bin файлик, который мы делаем по сути представляет собой своего рода жесткий диск очень небольшого размера, в котором заполнен только первый сектор.
Давайте добавим в него пару секторов. Это будет выглядеть немного странно, но по сути наш код сейчас будет описывать содержимое жесткого диска. В общем пишем
; ...
cli
hlt
%include "print.s"
hello_str:
db "If you gaze\nlong into abyss\nthe abyss will gaze\nback into you =O", 0
times 510 - ($-$$) db 0
dw 0xaa55
; ... то что выше не трогаем
; начало второго сектора, запихаем туда строку
db 'Hello, this is start of sector2\n', 0
times 512 * 2 - ($-$$) db 0 ; забиваем нулями оставшееся пространство сектора
; начало третьего сектора, и сюда тоже запихаем строку
db 'Bueno, this is start of sector3 =)\n', 0
times 512 * 3 - ($-$$) db 0 ; забиваем нулями оставшееся пространство сектора
если запустить, то работать в принципе будет как обычно. Попробуем теперь воспользоваться возможностью bios читать данные с диска.
Для этого используется 13 прерывание, функция 0x02 значение которой надо положить в регистр ah
и так, пишем код
org 0x7c00
mov bp, 0x8000
mov sp, bp
mov bx, hello_str
call print
mov bx, 0x9000 ; указываем адрес куда записать считанные данные
mov ah, 0x02 ; указываем функцию чтению
mov al, 2 ; будем читать два сектора
mov cl, 2 ; начиная со второго сектора
mov ch, 0 ; номер дорожки, ставим на 1
mov dh, 0 ; сторона диска, низ или верх, ставим 0
int 0x13 ; вызываем прерывание
; после прерывания по адресу 0x9000 окажутся данные с диска
; так как там у нас строка текста, можно ее вывести
mov bx, 0x9000
call print
; в началае второго сектора тоже строка текста
; выведем и ее
mov bx, 0x9000 + 512
call print
; ... дальше не трогаем
запускаем
может показаться, что мы сделали какую-то несусветную глупость, почему нельзя было просто взять и создать строку и сослаться на нее как мы сделали через hello_str.
Дело в том bios грузит всегда ровно один сектор диска, то есть ровно 512 байт. Весь остальной код, включая данные недоступен. И собственно его мы вынуждены читать вручную, уже обращаясь к жесткому диску.
Но самое классное на самом деле то, что мы можем загонять не только данные но и код в отдельные сектора (то есть по сути делать ядро как отдельный файл) читать его оттуда и запускать на исполнение.
Давайте попробуем так сделать. Пусть наш загрузчик будет просто читать данные второго и третьего сектора и сохранять его в область памяти 0x9000
, вот так:
org 0x7c00
mov bp, 0x8000
mov sp, bp
mov bx, 0x9000
mov ah, 0x02
mov al, 2
mov cl, 2
mov ch, 0
mov dh, 0
int 0x13
cli
hlt
times 510 - ($-$$) db 0
dw 0xaa55
если запустить увидим систему, зависшую в процессе загрузки
давайте теперь создадим отдельный ассемблер файлик, назовем его real_kernel.s, и в него загоним реальный код, который будет выдавать приветственное сообщение:
mov bx, message
call print
cli
hlt
%include "print.s"
message:
db "I'm real boot loader", 0
обратите внимание что я не указываю вверху org 0x7c00
, то есть этот файл как будто думает, что работает с нулевого адреса в памяти
теперь скомпилируем его:
nasm real_kernel.s
глянем что получилось
hd real_kernel
то есть файлик совсем маленький, плюс нет необходимости забивать его нулями, чтобы указать магические байты в конце сектора.
Теперь подкорректируем файл kernel.s
org 0x7c00
mov bp, 0x8000
mov sp, bp
mov bx, 0x9000
mov ah, 0x02
mov al, 2
mov cl, 2
mov ch, 0
mov dh, 0
int 0x13
jmp 0x9000 ; <<< добавим строчку, перейти на адрес 0x9000 и начать выполнять его код
times 510 - ($-$$) db 0
dw 0xaa55
скомпилируем теперь и это файл
nasm kernel.s
и теперь склеим два скомпилированных файла в один файл который по сути мини образ жесткого диска
cat kernel real_kernel > disk.bin
и запустим его
qemu-system-i386 disk.bin
вроде что-то вывелось
правда нашего сообщения нет почему-то…
Упрощаем процесс сборки
Рекламная пауза, сейчас у нас уже становится сильно много действий которые надо все выполнять и контролировать, что не очень удобно.
Специально для этого в linux встроен механизм Makefile`ов, создадим специальный файлик назовем его Makefile
и пропишем в него
all: build run
build:
nasm kernel.s
nasm real_kernel.s
cat kernel real_kernel > disk.bin
run:
qemu-system-i386 disk.bin
теперь если написать make build
то выполнятся команды которые соберут все файлы, если написать make run
запустится виртуальная машина, а если написать просто make
то выполнится оба действия последовательно.
Определяем сдвиг
Почему же нет нашего сообщения?
Тут дело в том, что наш код который находится в real_kernel, когда печатает текст, то его адрес берет относительно файла real_kernel, если запустить дизассемблер это файла
ndisasm real_kernel
то мы увидим, что адрес строки, которая будет печататься находится по адресу
а сама строка после загрузки из сектора будет находится на самом деле по адресу 0x9000 + 0x31
так вот, чтобы процессору указать реальный сдвиг данных надо передать сдвиг в специальный сегментный регистр ds который отвечает за сдвиг данных делается это так:
; ...
int 0x13
; ds нельзя изменять напрямую, поэтому сначала положим значение в bx
; вместо 0x9000 я пишу 0x900,
; потому что сегментные регистры считают сдвиг по формуле (ds * 0x10) + ptr
mov bx, 0x900
mov ds, bx ; сдвиг данных
jmp 0x9000 ; остальное не трогаем
times 510 - ($-$$) db 0
dw 0xaa55
запускаем
make
ура, вы написали загрузчик который теперь грузит код с диска и теоретически теперь не ограничен одним сектором! =)