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

от автора

Доброго времени суток %username%.

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

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

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  function 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 нужна для того, что бы мы могли конвертировать uin32 в указатели внутри 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 с нашим ядром. Дожидаемся загрузки и радуемся

Стоит ли развивать тему?

Никто ещё не голосовал. Воздержавшихся нет.

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

ссылка на оригинал статьи http://habrahabr.ru/post/259719/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *