Pull to refresh

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

Reading time 3 min
Views 26K
Доброго времени суток %username%.

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

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

Part 0x00
Part 0x01


Step 0x00


Писать свой загрузчик я не буду, не для того умные люди придумывали спеку 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

func Load(){
//Как видим наше ядро пока ничего не делает
}


Создадим файл link.ld
ENTRY(start)
SECTIONS
{

    .text 0x100000 :
    {
        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

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

Step 0x01



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

Для начала добавим в 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    = 0x07
)
//Ниже мы создаем обертку для 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 0x08: //backspace
		if cursorX > 0 {
			cursorX--
		}
	case 0x09: //tab
		cursorX = (cursorX + 8) & (8 - 1)
	case '\r': //return
		cursorX = 0
	case '\n': //new line
		cursorX = 0
		cursorY++
	default: 
		if c >= 0x20 { //Все печатные символы
			frameBuffer[cursorY*80+cursorX] = uint16(c) | (((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
github.com/t0pep0/Daria/tree/Part0x00

_____________________________________

Обсуждение на osdev.ru:
http://osdev.ru/viewtopic.php?f=4&t=1100
Чат в слаке (в golang-ru):
https://golang-ru.slack.com/messages/daria/
Only registered users can participate in poll. Log in, please.
Стоит ли развивать тему?
90.19% Да 524
9.81% Нет 57
581 users voted. 138 users abstained.
Tags:
Hubs:
If this publication inspired you and you want to support the author, do not hesitate to click on the button
+49
Comments 27
Comments Comments 27

Articles