Разработка OS на Go+asm Part 0x00

Доброго времени суток %username%.Захотелось мне пописать что-то ненормальное. Выбор пал на ОС, в конце-концов каждый программист должен написать свою ОС, пусть хотя бы учебную.

Как некоторым известно, я очень люблю язык Go ну, и решил попробовать написать на нем. Что из этого получилось — под хабракатом.

Писать свой загрузчик я не буду, не для того умные люди придумывали спеку multiboot.Для начала напишем код первоначальной загрузки (файл multiboot.s)

MBOOT_PAGE_ALIGN equ 1<<0 MBOOT_MEM_INFO equ 1<<1 MBOOT_HEADER_MAGIC equ 0x1BADB002 MBOOT_HEADER_FLAGS equ MBOOT_PAGE_ALIGN | MBOOT_MEM_INFO MBOOT_CHECKSUM equ -(MBOOT_HEADER_MAGIC + MBOOT_HEADER_FLAGS)

[BITS 32]

[GLOBAL mboot] [EXTERN code] [EXTERN bss] [EXTERN end]

mboot: dd MBOOT_HEADER_MAGIC dd MBOOT_HEADER_FLAGS dd MBOOT_CHECKSUM dd mboot dd code dd bss dd end dd start

[GLOBAL start] extern go.kernel.Load; Указываем на то, что у нас есть внешняя функция на Go

start: push ebx cli call go.kernel.Load; Вызываем внешнюю функцию, которая содержит основной код ядра jmp $ теперь создадим файл kernel.go следующего содержания:

package kernel

function Load (){ //Как видим наше ядро пока ничего не делает } Создадим файл link.ld

ENTRY (start) SECTIONS {

.text 0×100000: { code = .; _code = .; __code = .; *(.text) . = ALIGN (4096); }

.data: { data = .; _data = .; __data = .; *(.data) *(.rodata) . = ALIGN (4096); }

.bss: { bss = .; _bss = .; __bss = .; *(.bss) . = ALIGN (4096); }

end = .; _end = .; __end = .; } и Makefile

SOURCES=multiboot.o kernel.go.o

GOFLAGS= -nostdlib -nostdinc -fno-stack-protector -fno-split-stack -static -m32 -g -I. GO=gccgo ASFLAGS= -felf NASM= nasm $(ASFLAGS) OBJCOPY=objcopy

LDFLAGS=-T link.ld -m elf_i386

all: $(SOURCES) link

clean: rm *.o kernel

link: ld $(LDFLAGS) -o kernel $(SOURCES)

%.go.o: %.go $(GO) $(GOFLAGS) -o $@ -c $<

%.o: %.s $(NASM) $< Теперь, выполнив make в дирректории проекта вы получите на выходе файл kernel, который можно загрузить с помощью qemu:

qemu-system-i386 -kernel ./kernel

Ядро успешно загрузится и ничего не будет делать)

Настало время поздороваться с миром.

Для начала добавим в multiboot.s следующие строки:

global __go_runtime_error global __go_register_gc_roots global __unsafe_get_addr

__unsafe_get_addr: push ebp mov ebp, esp mov eax, [ebp+8] mov esp, ebp pop ebp ret

__go_register_gc_roots: __go_runtime_error: ret функция __unsafe_get_addr нужна для того, что бы мы могли конвертировать uint32 в указатели внутри Go

Другие две функции — просто затычки для компилятора

Теперь создадим файл screen.go

package screen

var ( frameBuffer *[totalMax]uint16 //Старшие 8 бит — символ, младшие — его атрибуты cursorX, cursorY uint8 )

const ( frameBufferAddr = 0xB8000 maxX = 80 maxY = 25 totalMax = maxX * maxY whiteOnBlack = 0×07 ) //Ниже мы создаем обертку для Go над нашей функцией __unsafe_get_addr //extern __unsafe_get_addr func getAddr (addr uint32) *[totalMax]uint16

func Init () { cursorX = 0 cursorY = 0 frameBuffer = getAddr (frameBufferAddr) //Получаем доступ к видеобуферу }

//Очистка экрана, просто заполняем весь видеобуфер нулями func Clear () { for i:= 0; i < totalMax; i++ { frameBuffer[i] = 0 } cursorX = 0 cursorY = 0 }

//Меняем позицию курсора func SetCursor (x, y uint8) { cursorX = x cursorY = y }

//Скроллим экран если он заполнен func scroll () { if cursorY >= maxY { for i:= 0; i < 24*maxX; i++ { frameBuffer[i] = frameBuffer[i+80] //Смещаем все строки на одну вверх } for i := 24 * 80; i < totalMax; i++ { //Очищаем нижнюю строку frameBuffer[i] = 0x20 | (((0 << 4) | (15 & 0x0F)) << 8) frameBuffer[i] = 0 } cursorY = 24 cursorX = 0 } }

//Вывод символов func putChar (c byte) { switch c { case 0×08: //backspace if cursorX > 0 { cursorX-- } case 0×09: //tab cursorX = (cursorX + 8) & (8 — 1) case '\r': //return cursorX = 0 case '\n': //new line cursorX = 0 cursorY++ default: if c >= 0×20 { //Все печатные символы frameBuffer[cursorY*80+cursorX] = uint16© | (((0 << 4) | (15 & 0x0F)) << 8) cursorX++ } } if cursorX >= 80 { //Если надо перемещаем курсор cursorX = 0 cursorY++ } scroll () }

//Выводим строку func PrintStr (s string) { for i:= 0; i < len(s); i++ { putChar(s[i]) } } Теперь надо подключить наш модуль screen к ядру — в kernel.go добавляем import «screen», там же, в функци Load() пишем:

screen.Init () screen.Clear () screen.PrintStr («Hello Habrahar!») Теперь надо указать компилятору как все это дело собирать нам понадобится добавить в Makefile следующие строки:

%.gox: %.go.o $(OBJCOPY) -j .go_export $< $@ И там же, в переменную SOURCES между multiboot.o и kernel.go.o добавить screen.go.o и screen.goxПосле проведения всех манипуляций вызываем команду make и запускаем qemu с нашим ядром. Дожидаемся загрузки и радуемся

P.S. Прошу простить меня за опечатки, если они есть. Обязуюсь исправить.P.P. S. На днях код будет выложен на github

Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.

© Habrahabr.ru