Приветствую, %username%.

Перед вами вторая статья из цикла о разработке ОС на Go + asm.

Part 0x00
Part 0x01

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

Кода к этой статье на гитхабе не будет (забыл вовремя сделать коммит, а теперь банально лень специально восстанавливать, если кто пришлет пулл реквест — буду благодарен).

Слабонервным просьба закрыть статью — все, что может быть написанно на Go будет написанно на нем!

Помните в multiboot.s были функции заглушки для компилятора? Перенесите их в файл runtime.s (создайте его).

Step 0x00


Для начала давайте научимся выделять память — создадим файл memory.go:

package memory

//extern end
var end uint32 //берется из link.ld

var placement_address uint32 //Текущий адрес

//extern __unsafe_get_addr 
func pointer2uint32(pointer *uint32) uint32 //Все как в прошлый раз, разве что тип аргумента изменился, но это не существенно

func Init() {
	placement_address = pointer2uint32(&end) //получаем начальный адресс
}

func kmalloc(size uint32, align int, phys *uint32) uint32 { //выделение памяти
	if align == 1 && (placement_address&0xFFFFF000) != uint32(0) { //Если адрес не выровнен по границе - то выравниваем.
		placement_address &= 0xFFFFF000
		placement_address += 0x1000
	}
	if phys != nil { //если необходимо - возвращаем физический адрес
		*phys = placement_address
	}
	res := placement_address //возвращаем текущий адрес
	placement_address += size //изменяем текущий адрес
	return res
}

func Kmalloc(size uint32) uint32 { //Простое выделение памяти
	return kmalloc(size, 0, nil)
}

Step 0x01


Приступим к рантайму. Создаем файл runtime.go

package runtime

import (
	"memory"
)

//extern __unsafe_get_addr
func pointer2byteSlice(ptr uint32) *[]byte

//extern __unsafe_get_addr
func pointer2uint32(ptr interface{}) uint32

func memset(buf_ptr uint32, value byte, count uint32) { //Примитивная реализация memset
	var buf *[]byte
	buf = pointer2byteSlice(buf_ptr)
	for i := uint32(0); i < count; i++ {
		(*buf)[i] = value
	}
}

func memcpy(dst, src uint32, size uint32) { //Так же примитивная реализация memcpy
	var dest, source *[]byte
	dest = pointer2byteSlice(dst)
	source = pointer2byteSlice(src)
	for i := uint32(0); i < size; i++ {
		(*dest)[i] = (*source)[i]
	}
}

func New(typeDescriptor uint32, size uint32) uint32 { //А вот и будущая new()
        //На данном этапе typeDescriptor не нужен
	buf_ptr := memory.Kmalloc(size) //Выделяем память
	memset(buf_ptr, 0, size) //забиваем выделенную память нулями
	return buf_ptr //Возращаем указатель
}

Читатель, изучивший комментарии к предыдущей статье, задаст вопрос: «Как же так, new() это __go_new, а не go.runtime.New?»

Ответ в файле runtime.s (помните я просил перенести функции из multiboot.s в него?):

;gccgo compability
global __go_runtime_error 
global __go_register_gc_roots 



__go_register_gc_roots:
__go_runtime_error:
  ret

global __unsafe_get_addr ;convert uint32 to pointer

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

extern go.runtime.New

global __go_new
global __go_new_nopointers

__go_new: ;прокидываем go.runtime.New как __go_new
__go_new_nopointers:
  call go.runtime.New
  ret

Теперь приведем файл kernel.go к следующему виду:

package kernel

import (
	"memory"
	"screen"
)

func Load() {
	memory.Init()
	screen.Init()
	screen.Clear()
	str := new(string)
	*str = "Habrahabr"
	screen.PrintStr(*str)
}

После компиляции и запуска мы увидим надпись «Habrahabr».

ПРЕДУПРЕЖДЕНИЕ: На самом деле пока не стоит создавать вектора на слайсы, строки и другие типы переменной длинны, иначе возможно наложение.

_____________________________________

Обсуждение на osdev.ru:
http://osdev.ru/viewtopic.php?f=4&t=1100
Чат в слаке (в golang-ru):
https://golang-ru.slack.com/messages/daria/

Комментарии (7)


  1. t0pep0 Автор
    09.06.2015 12:11

    Не буду дописывать в статью, но принимаются Ваши версии memset, memcmp и memcpu, как указанно в статье — мои слишком примитивны


    1. t0pep0 Автор
      09.06.2015 12:41

      memcpy*


    1. StrangerInRed
      09.06.2015 13:57
      +1

      В стандартных реализация они всего лишь буферезированы по size_t. Вот вам memset только на С.

      typedef uint8_t byte; #define forAll(iterator, count) for(iterator = 0; iterator < (count); ++iterator) byte* flushAllToByte(pointer array, size_t size, byte symbol) { size_t iterator; forAll(iterator, size / sizeof(size_t)) { ((size_t*) array)[iterator] = symbol; } for(iterator *= sizeof(size_t); iterator < size; ++iterator){ ((byte*) array)[iterator] = symbol; } return array; }
      К сожалению форматирование кода недоступно при отрицательной карме.


      1. t0pep0 Автор
        09.06.2015 14:07

        Спасибо, несколько позже изучу

        typedef uint8_t byte;
        
        #define forAll(iterator, count) for(iterator = 0; iterator < (count); ++iterator) 
        
        byte* flushAllToByte(pointer array, size_t size, byte symbol) { 
            size_t iterator; 
            forAll(iterator, size / sizeof(size_t)) {
              ((size_t*) array)[iterator] = symbol; 
          } 
            for(iterator *= sizeof(size_t); iterator < size; ++iterator) { 
              ((byte*) array)[iterator] = symbol; 
          }
          return array; 
        } 
        

        Вроде правильно отформатировал


    1. Gorthauer87
      09.06.2015 16:52

      А что мешает утащить какую-нибудь реализацию хипа для bare metal? Из newlib, например.



      1. t0pep0 Автор
        09.06.2015 19:52

        А здесь еще и нет хипа, по большому счету, так, выделение памяти и только. Но будет, скоро