Основы разработки ядра ОС [можно не делать] / подсказка к 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

ура, вы написали загрузчик который теперь грузит код с диска и теоретически теперь не ограничен одним сектором! =)

3.3

Доработайте real_kernel из подсказки, чтобы ваше ядро работало как в предыдущем задании.