OS Barebone with Go

잡담

아주 오랜만에 블로그 글을 써보기 시작했다. 사실 방학기간동안 ASGI나 JIT에 관해서 글을 쓰고 지우고 쓰고 지우는 바람에 글을 완벽하게 완성하지 못하고 Go에 대한 관심이 최근에 다시 갖게되어서 어떤것을 만들 수 있을까 생각해보던 중 Go를 이용해서 간단한 운영체제 틀을 제작할 수 있다는 글을 읽어보고 직접해보면서 블로깅을 해보고자 결심했다. (기회가 되면 RPython에 관해서도 완성되는 대로 올리겠다.)

Getting started

일단 컴파일러가 필요하다. 우리가 일반적으로 사용하는 Golang 컴파일러는 Gc라고 불리는 컴파일러다. 이는 구글에 제작해왔던 그 컴파일러이고 빠른 컴파일 성능을 보여준다. 하지만 위 글에서는 Gccgo를 사용하는것을 권장하고 있다. 그래서 이 두 컴파일러의 차이점에 대해서 알아봤다. 일단 Gccgo는 Golang을 위해서 GCC용 프론트엔드를 작성한 컴파일러다. 우선 Gccgo는 Gc 컴파일러에 비해서 컴파일 속도는 느리지만 Gccgo는 Gcc를 백엔드로 갖고 있기 때문에 일반적인 Gcc의 최적화 성능을 보여준다. 또한 다양한 CPU와 binary format을 지원해주기 때문에 OS 개발같이 cross compile이 필요한 경우에는 사용할 수 있다.

Gccgo compile

GNU Gcc FTP 혹은 그 외에 다른 사이트에서 적당한 버전의 Gcc소스를 다운받는다. 그리고 brew를 통해서 gmp mpfr libmpc를 다운 받는다.

$ brew install gmp mpfr libmpc

다운받은 소스코드는 다음과 같은 디렉토리 구조에 저장하였다.

~
|
|- ...
|_ opt/
	|_ binutils-2.28/
	|_ binutils-2.28-build/
	|_ gcc-6.3.0/
	|_ gcc-6.3.0-build/
|_ opt/
	|_ cross/
|_ ...

그런 다음 ~/opt/cross/bin 디렉토리에서 PATH 환경변수 설정하고, configure를 진행하고, make를 실행한다. 여기서 TARGET은 i686-elf로 설정하는데 컴파러의 컴파일 타겟 설정이다.

export PREFIX="$HOME/opt/cross"
export TARGET=i686-elf
export PATH="$PREFIX/bin:$PATH"

그리고 binutils-2.28-build/ 디렉토리와 gcc-6.3.0-build에서 binutils와 gcc를 컴파일합니다.

$ ../binutils-2.24/configure --target=$TARGET --prefix="$PREFIX" --disable-nls --disable-werror
$ make
$ make install
$ ../gcc-6.3.0/configure --target=$TARGET --prefix="$PREFIX" --disable-nls --enable-languages=c,c++,go --without-headers 
$ make all-gcc
$ make all-target-libgcc
$ make install-gcc
$ make install-target-libgcc

위와 같이 Gccgo를 컴파일하고, 소스코드를 컴파일합니다. osdevwiki에서는 부트로더를 작성하지 않고 이미 완성된 grub이나 다른 부트로더를 사용하도록 권고하고 있습니다. 링크에 나와 있는 boot.s와 kernel.go, terminal.go를 컴파일하고 link.ld로 링크랍니다. 그리고 여기서 볼 수 있는 특이한 점은 golang에서 import를 하기 위해서는 C 언어와 같이 include 파일이 필요하고 이를 objcopy 명령어를 통해서 .go_extern section만 추출해서 gox 파일을 만듭니다. objcopy의 사용 이유는 이 링크에서 확인 할 수 있고 objcopy를 flag의 사용법은 여기서 확인할 수 있습니다. 시간이 되면 이 소스를 바탕으로 조금 발전해서 작성해보고 이를 블로그로 정리해보겠습니다.