원문: Bare Bones (for creating an Operating System) (OSDev.org) (역: 베어본 (운영체제를 만들기 위한))
이 튜토리얼에서 우리는 32-bit x86 아키텍쳐를 위한 간단한 커널을 만들 것입니다. 이 것은 여러분의 운영체제를 만들기 위한 첫번째 단계이며 이 튜토리얼은 최소한의 시스템을 만드는 것에 대한 예제를 보여줍니다. 그러나 이건 여러분의 프로젝트를 어떻게 구성해야 하는지에 대한 것은 아닙니다. 여기서 다루는 것들은 커뮤니티 멤버들에 의해서 평가되었고, 좋은 이유로 현재 권장사항을 따르길 추천드립니다. 온라인에 많은 다른 튜토리얼들이 있지만, 그것들은 실제로 구현해보지 않은 채로 작성한 것들이며, 현대에 적용하기 힘든 부분이 있습니다.
여러분은 새 운영체제를 개발하려면 어떻게 시작해야 하는지에 대해서 궁금하실 것입니다. 아마도 언젠가는 여러분의 운영체제를 이용해서 여러분의 운영체제를 개발할 수 있는 날이 올것입니다. 이것은 부트스트랩핑 또는 스스로 동작하기 위해서 알아야 하는 과정을 다룹니다. 오늘, 우리는 이미 존재하는 운영체제에서 여러분의 운영체제를 컴파일할 수 있는 시스템을 구축할 것입니다. 이 과정을 크로스 컴파일이라고 하며 그건 운영체제를 개발하기 위한 가장 첫번째 단계입니다.
이 튜토리얼은 커널 개발로 바로 진입할 수 있도록 이미 존재하는 기술들을 사용할 것입니다. 뭐, 여러분 만의 프로그래밍을 언어를 만든다거나 컴파일러, 혹은 부트로더를 만든다기 보다는 말이지요.
이 튜토리얼에서 우리가 사용할 것들은 아래와 같습니다.
1. 우리가 만든 오브젝트 파일을 서로 링크해서 커널을 만들기 위해서 Binutils 패키지 안에 들어있는 GNU Linker를 사용할겁니다.
2. 마찬가지로, 기계어 코드를 어셈블링하기 위해서 Binutils안에 들어있는 GNU Assembler를(혹은 NASM을) 사용할겁니다.
3. 고급 프로그래밍 언어를 어셈블리로 컴파일하기 위해서 GNU Compiler Collection을 사용할 겁니다.
4. 커널의 고급 파트를 작성하기 위해서 C 언어(혹은 C++)를 사용할겁니다.
5. 우리의 커널로 부팅시키기 위해서 Multiboot 부트 프로토콜로 구성하여 GRUB 부트로더를 사용할겁니다.
6. 실행가능한 포멧으로서 ELF를 사용할 겁니다. 이건 커널이 어디에, 어떻게 로딩될 것인지 우리가 제어할 수 있도록 만들어줍니다.
이 글에서는 기본적으로 독자분들이 개발 환경을 잘 지원해주는 Linux 같은 UNIX 유사 시스템을 사용하고 있다는 가정하에 쓰여졌스빈다. 윈도우즈 사용자 분들께서는 반드시 MinGW나 Cygwin 환경에서 해야 할 겁니다.
운영체제 개발에서 잘 동작하는 커널을 만들기 위해서는 그것에 대한 전문가가 되어야 하고, 인내심을 가지고 작성법을 주의깊게 읽어야 합니다. 여러분은 계속 진행하기 전에 이 글에 있는 모든 것들을 읽을 필요가 있습니다. 만약 어떤 문제가 발생했을 때 여러분은 이 글을 매우 주의깊게 다시 읽고 그걸 다시 한번 해볼 필요가 있습니다. 그래도 문제가 계속 발생한다면 경험 많은 저희 커뮤니티(포럼이나 IRC에서)가 도움이 될 것입니다.
잠깐! 혹시 시작하기 전에 "시작하기", "초보자들의 실수", 그리고 운영체제론에 관련된 문서들을 읽어보셨나요?
(역자 주: 이것들도 번역본을 준비해서 링크를 걸어둘께요. 아직은 준비된게 없어서...)
Building a Cross-Compiler
이 부분은 "GCC Cross-Compiler(GCC 크로스 컴파일러)", "Why do I need a Cross Compiler?(크로스 컴파일러가 왜 필요한가요?)" 에서 더 자세히 다룹니다.
(역자 주: 이것들도 언젠간 번역해서 제공해 드릴....)
여러분이 가장 먼저 해야 하는 일은 i686-elf 타겟을 위한 GCC Cross-Compiler 환경을 셋팅 하는 것입니다. 여러분은 여러분이 만들 운영체제에 대해서 이미 알고 있는 컴파일러를 가지고 있지 않습니다. 그래서 우리는 i686-elf라고 불리는 일반적인 타킷을 사용합니다. 이것들은 System V ABI를 타겟팅하는 툴 집합으로 제공됩니다. 이 셋팅은 우리 커뮤니티에 의해서 잘 테스트되었고 분석되었습니다. 그리고 그건 아마 여러분이 GRUB과 Multiboot 플랫폼으로 구성된 부팅가능한 커널을 만들기 쉽게 도와줄 겁니다. (이미 여러분이 리눅스 같은 ELF 플랫폼을 사용하고 있다면 아마 여러분은 GCC가 ELF 프로그램을 생성해낸다는 것을 알고 계실 겁니다. 그런데 이건 운영체제 개발에서 적용하기 힘들고, 이런 컴파일러가 생성한 ELF 프로그램은 리눅스를 위한 것이지, 리눅스가 아닌 여러분의 운영체제에서는 동작하지 않을 것입니다. 그래서 여러분은 크로스 컴파일러를 사용하지 않으면 문제에 자주 부딧힐 수 있습니다)
여러분은 아마 크로스 컴파일러 없이 여러분의 운영체제를 올바르게 컴파일할 수 없으며 x86_64-elf 크로스 컴파일러로는 이 튜토리얼에서 제공하는 것들을 제대로 컴파일 할 수 없을지도 모릅니다. 만약 여러분이 처음으로 운영체제 개발에 도전하는 것이라면 반드시 32비트 커널을 먼저 도전해 보셔야 합니다. x86_64 컴파일러를 사용한다면 아마 여러분은 GRUB이 여러분의 커널을 어떻게 부팅시켜야 할 지 모르는채로 포기하게 될지도 모르는 일입니다.
Overview
이제 여러분은 i686-elf 타깃을 위한 크로스 컴파일러를 가지고 있어야만 합니다. (위에서 설명했습니다) 이 튜토리얼은 x86 시스템을 위한 운영체제를 만들기 위한 최소한의 솔루션입니다. 그건 프로젝트 구조를 위한 권장 스켈레톤으로 제공되는게 아니며 단순한 최소한의 커널을 만들기 위한 예제일 뿐입니다. 이걸 위해서 우리는 아래 세가지 파일을 만들게 될 것입니다.
1. boot.s - 커널이 CPU 환경에서 동작할 수 있도록 만들어 주기 위한 커널의 진입점입니다.
2. kernel.c - 여러분이 만들게 될 커널 루틴입니다.
3. linker.ld - 위에 두 소스코드를 컴파일한 후 링킹할 때 사용하게 될 링커 스크립트입니다.
Booting the Operating System
운영체제가 시작되면 이미 존재하는 작은 소프트웨어가 로드되어 실행될 것입니다. 이건 부트로드라고 불리며 우리는 이걸 직접 만들진 않고, GRUB을 사용하게 될겁니다. 여러분만의 부트로더를 만들기란 쉬운 주제가 아니기 때문에 했다치고 진행합니다. 우리는 그 설정을 바꾸기만 하면 되는 건데 운영체제 입장에서는 부터로더가 제어를 우리의 운영체제에게 넘겨주면 그걸 처리해야만 하는 것입니다. 커널은 정말 최소 환경으로 진입되어, 스택도 가상메모리도, 하드웨어도 전부 초기화되지 않은 상태에서 시작되게 됩니다.
우리가 가장 처음 할 작업은 부트로더가 커널을 구동시키는 방법에 대해 다루는 것입니다. 우리는 부트로더와 운영체제 커널 사이에 서로를 이어주는 쉬운 인터페이스인 Multiboot 표준이 이미 존재하고 있어서 꽤 운이 좋습니다. 이건 몇가지 특수한 값을 전역 변수(멀티부트 헤더라 불리는)에다가 집어 넣기만 하면됩니다. 그리고 그 헤더는 부트로더가 읽게 될 겁니다. 부트로더가 거기 들어있는 값을 볼 때 부트로더는 커널이 Multiboot 와 호환되는지를 먼저 확인합니다. 그리고 부트로더가 우리의 커널을 어떻게 로드해야 하는지 알게되고, 심지어 부트로더가 우리의 커널에 아직 필요하진 않지만 메모리맵 같은 중요한 정보까지도 함께 넘겨줍니다.
아직 스택이 구성되지 않았기 때문에 우리는 전역변수들을 올바르게 설정했는지 반드시 확인해야 하고, 그걸 어셈블리어로 작업할겁니다.
Bootstrap Assembly
또 다른 형태의 구현으로 NASM 어셈블러 코드를 사용하실 수 있습니다.
우리는 이제 boot.s라는 파일을 만들 것인데 아래에 그 파일에 들어갈 코드를 써두었습니다. 이 예제에서 우리는 아까 여러분이 만들었던 크로스 컴파일러의 일부인 GNU 어셈블러를 사용하고 있습니다. 이 어셈블러는 GNU 툴체인과 완벽하게 통합되어 동작하게 되지요. 이걸 만들 때 가장 중요한 것은 멀티부트 헤더입니다. 이건 커널 바이너리의 제일 앞에 배치되어야만 하고, 그렇지 않다면 부트로더는 우리가 만든 커널을 인식하는데 실패할겁니다.
# 멀티부트 헤더를 위한 상수를 설정합니다.
.set ALIGN, 1<<0 # 모듈들을 로드할 때 페이지 경계선에 맞춰 정렬해달라는 의미입니다.
.set MEMINFO, 1<<1 # 메모리 맵을 제공받기를 원한다는 플래그.
.set FLAGS, ALIGN | MEMINFO # 이게 멀티부트 헤더의 플래그 필드에 들어갈겁니다.
.set MAGIC, 0x1BADB002 # 부트로더가 이 헤더를 찾을 수 있도록 만드는 식별값입니다.
.set CHECKSUM, -(MAGIC + FLAGS) # 위 값의 체크섬인데, 이건 우리가 multiboot 규약을 따른다는 것을 알려줍니다.
# 커널에 프로그램의 일부로서 멀티부트 헤더를 정의합니다. 이런 것들은 멀티부트 표준 문서에 기술되어 있습니다.
# 부트로더는 커널의 최초 8 KiB안에서 이 시그네쳐를 검색하게 되고, 그건 32비트 경계선 안쪽에 정렬되어 있게 됩니다.
# 그래서 그건 우리가 이 값들을 최소한 커널의 최초 8 KiB안에 강제로 배치시켜야 한다는 의미이기도 하지요.
.section .multiboot
.align 4
.long MAGIC
.long FLAGS
.long CHECKSUM
# 멀티부트 표준은 스택 포인터 레지스터(esp)의 값에 대한 정의를 하지 않습니다.
# 그리고 그건 커널의 상단에 있어서 스택으로서는 적합하지 않습니다.
# 아래 코드는 어떤 심볼을 만들고 그 아래쪽을 스택으로 쓰기 위한 목적을 가지고 있습니다.
# 그래서 이걸 위해 16 KiB를 할당하게 되고 최종적으로 우리는 심볼의 최상단을 가르키는 심볼을 하나 더 정의할 겁니다.
# x86 아키텍쳐에서 스택은 상위 메모리에서 하위 메모리로 진행됩니다.
입니다.
.section .bss
.align 16
stack_bottom:
.skip 16384 # 16 KiB
stack_top:
# 링커 스크립트는 _start 를 커널의 진입점으로 정의하고 있습니다.
# 그래서 부트로더는 이 위치로 제어를 넘기게 되어 커널이 로딩되는 것입니다.
# 그건 부트로더가 제어를 넘긴 이후에는 절대 원래 위치로 돌아가려고 해서는 안됩니다.
.section .text
.global _start
.type _start, @function
_start:
# x86 환경에서 부트로더는 우리를 32비트 보호모드로 로드시킬겁니다.
# 인터럽트와 페이징은 비활성화되어 있으며 프로세서의 상태는 멀티부트 표준에 정의된 상태로 설정될 겁니다.
# 커널은 CPU 전체를 제어하게 됩니다. 오로지 커널만이 하드웨어 기능들을 사용할 수 있고,
# 모든 코드는 그것의 일부로서 제공되게 될 것입니다.
# 그리고 커널을 위한 stdio.h 헤더 구현이 없기 때문에 printf 함수도 없습니다.
# 보안 대첵이나 보호 기능, 디버깅 메커니즘, 그 무엇도 존재하지 않으며
# 커널은 자기 스스로가 자길 위한 동작을 해야 합니다.
# 컴퓨터 전체를 완전히 제어하는 것이죠.
# 스택의 상단을 우리가 아까 만들엇던 영역의 최상단 포인터로 채워줍니다.
# 이것은 C 코드로 진입하기 전에 필수적으로 완료되어야 합니다.
# 왜냐하면 어셈블리와 달리 고급 언어들은 스택 없이 작동할 수 없기 때문입니다..
mov $stack_top, %esp
# 여기가 고수준 커널에 진입하기 전에 프로세서의 상태를 초기화해주기 가장 좋은 최적의 장소입니다.
# 모든 기능들이 꺼져있는 최소 환경이지요. 기억해야 할 것은 프로세서가 아직 완전히 초기화 되지 않아서,
# 부동소수점 명령이나 확장 명령 셋들을 사용할 수 없다는 점입니다. (초기화 한 뒤에는 쓸 수 있습니다)
# GDT 는 여기에서 로딩되어야 하고, 페이징 역시도 여기서 활성화 되어야 합니다.
# C++ 특징 중 하나인 전역 생성자나 예외 처리는 아마 더 나중에서야 쓸 수 있는 기능이지 않을까 십네요.
# (왜냐하면 그것들은 런타임 지원을 필요로 하기 때문입니다)
# 이제 고수준 커널로 진입합니다. 이걸 위해서 ABI가 요구하는 스택 크기는 16 바이트정도입니다.
# 이건 call 명령을 수행하기 위한 스택 크기입니다.
# (왜냐하면 스택 프레임을 구성하면서 함수가 종료된 이후에 돌아오게 될 포인터 4바이트를 함께 푸슁하기 때문이죠)
# 스택은 원래 16바이트로 정렬된 프레임을 다수 구축했습니다. 그러므로 그걸 하기 위한 스택은 충분하지요.
call kernel_main
# 만약 여기서 시스템이 아무것도 할게 없다면, 컴퓨터는 무한 루프를 돌게 될것입니다.
cli
1: hlt
jmp 1b
# Set the size of the _start symbol to the current location '.' minus its start.
# This is useful when debugging or when you implement call tracing.
.size _start, . - _start
이걸 어셈블링 하기 위해서 아래 명령을 타이핑합니다.
i686-elf-as boot.s -o boot.o
Implementing the Kernel
우리가 부트스트랩 어셈블리를 쓴 이후에 프로세서는 고수준 언어로 진입할 준비가 되었습니다. C나 C++로 만든 커널 같은 곳으로 말이지요.
Freestanding and Hosted Environments
만약 사용자 영역에서 C나 C++ 프로그래밍을 해보셨다면, 여러분은 그걸 호스팅된 환경이라고 불렀을 겁니다. "호스팅 된" 이라는 의미는 C 표준 라이브러리나 다른 유용한 런타임 기능들을 의미하는 것이죠. 비슷하게, 우리가 여기에서 사용한 것처럼 Freestanding(홀로서는) 버전도 있습니다. 이것의 의미는 C 표준 라이브러리 없이 오직 스스로 구현한 기능만으로 작동한다는 것입니다. 그러나 몇몇 헤더들은 C 표준 라이브러리의 일부가 아니며 컴파일러의 일부입니다. 이것은 C 소스코드의 홀로서기가 가능한 이유죠. 이런 경우 우리가 boolean 데이터형을 위해서 stdbool.h을 포함시킨다거나 size_t나 NULL같은 걸 쓰기 위해서 stddef.h를 포함시킬 수도 있고, intx_t 형태의 자료형을 위해서 stdint.h를 포함시킬 수 있는 것이죠. 당연하게도 이런 것들은 운영체제 개발에 있어서 가장 유용합니다. 왜냐하면 이들은 컴파일러에만 의존하기 때문에 홀로서기가 가능한 것이죠. 추가적으로 float.h나 iso646.h, limits.h 역시도 홀로서기가 가능합니다.
Writing a kernel in C
우리는 C 언어로 커널을 어떻게 만드는지를 보여드릴겁니다. 이 커널은 VGA 텍스트 모드 버퍼를 출력장치로 사용합니다. 그건 다음 글자가 배치될 위치를 기억했다가 글자를 하나 출력하고, 위치를 한번 증가시키는 간단한 드라이버로 구성되어 있습니다. 중요한 것은 \n과 같은 줄바꿈 같은게 지원되지 않는다는 것이고, \n은 VGA 장치 자체가 정의한 글자를 출력하게 될 것입니다. 그리고, 화면이 가득찼을 때 스크롤링에 대한 지원도 없습니다. 이걸 가장먼저 추가할 것입니다. 잠깐이면 아래 코드를 이해하실 수 있을 겁니다.
/* 이 전처리기를 제거하기 전에 이 코드가 C++을 쓰고 있는지 아닌지를 먼저 확인하기 바랍니다. */
#if !defined(__cplusplus)
#include <stdbool.h> /* C 언어는 불리언 자료형을 기본 자료형으로 지원하지 않습니다. */
#endif
#include <stddef.h>
#include <stdint.h>
/* 컴파일러가 다른 운영체제를 타겟팅하는건 아닌지 테스트합니다. */
#if defined(__linux__)
#error "You are not using a cross-compiler, you will most certainly run into trouble"
#endif
/* 이 튜토리얼은 32비트 x86 타깃에서만 동작합니다. */
#if !defined(__i386__)
#error "This tutorial needs to be compiled with a ix86-elf compiler"
#endif
/* 하드웨어 텍스트 모드를 위한 색상 상수값 */
enum vga_color {
VGA_COLOR_BLACK = 0,
VGA_COLOR_BLUE = 1,
VGA_COLOR_GREEN = 2,
VGA_COLOR_CYAN = 3,
VGA_COLOR_RED = 4,
VGA_COLOR_MAGENTA = 5,
VGA_COLOR_BROWN = 6,
VGA_COLOR_LIGHT_GREY = 7,
VGA_COLOR_DARK_GREY = 8,
VGA_COLOR_LIGHT_BLUE = 9,
VGA_COLOR_LIGHT_GREEN = 10,
VGA_COLOR_LIGHT_CYAN = 11,
VGA_COLOR_LIGHT_RED = 12,
VGA_COLOR_LIGHT_MAGENTA = 13,
VGA_COLOR_LIGHT_BROWN = 14,
VGA_COLOR_WHITE = 15,
};
static inline uint8_t vga_entry_color(enum vga_color fg, enum vga_color bg) {
return fg | bg << 4;
}
static inline uint16_t vga_entry(unsigned char uc, uint8_t color) {
return (uint16_t) uc | (uint16_t) color << 8;
}
size_t strlen(const char* str) {
size_t len = 0;
while (str[len])
len++;
return len;
}
static const size_t VGA_WIDTH = 80;
static const size_t VGA_HEIGHT = 25;
size_t terminal_row;
size_t terminal_column;
uint8_t terminal_color;
uint16_t* terminal_buffer;
void terminal_initialize(void) {
terminal_row = 0;
terminal_column = 0;
terminal_color = vga_entry_color(VGA_COLOR_LIGHT_GREY, VGA_COLOR_BLACK);
terminal_buffer = (uint16_t*) 0xB8000;
for (size_t y = 0; y < VGA_HEIGHT; y++) {
for (size_t x = 0; x < VGA_WIDTH; x++) {
const size_t index = y * VGA_WIDTH + x;
terminal_buffer[index] = vga_entry(' ', terminal_color);
}
}
}
void terminal_setcolor(uint8_t color) {
terminal_color = color;
}
void terminal_putentryat(char c, uint8_t color, size_t x, size_t y) {
const size_t index = y * VGA_WIDTH + x;
terminal_buffer[index] = vga_entry(c, color);
}
void terminal_putchar(char c) {
terminal_putentryat(c, terminal_color, terminal_column, terminal_row);
if (++terminal_column == VGA_WIDTH) {
terminal_column = 0;
if (++terminal_row == VGA_HEIGHT)
terminal_row = 0;
}
}
void terminal_write(const char* data, size_t size) {
for (size_t i = 0; i < size; i++)
terminal_putchar(data[i]);
}
void terminal_writestring(const char* data) {
terminal_write(data, strlen(data));
}
#if defined(__cplusplus)
extern "C" /* Use C linkage for kernel_main. */
#endif
void kernel_main(void) {
/* 터미널 인터페이스를 초기화합니다. */
terminal_initialize();
/* 이제 연습삼아서 줄바꿈을 구현해 보세요. (지금은 구현되어 있지 않습니다) */
terminal_writestring("Hello, kernel World!\n");
}
C 함수인 strlen 함수를 구현한 이유를 이해 해야합니다. 이 함수는 C 표준 함수중 하나이기 때문에 우리는 커널에서 사용할 수가 없습니다. 예를 들면, 예외지원은 특별한 런타임 지원을 필요로 하고, 메모리 할당 또한 필요로 합니다. 대신에 우리는 size_t를 사용하기 위해서 stddef.h에 의존합니다. 이 코드를 컴파일 할 때에는 아래 처럼 명령을 주면 됩니다.
i686-elf-gcc -c kernel.c -o kernel.o -std=gnu99 -ffreestanding -O2 -Wall -Wextra
Writing a kernel in C++
C++로 커널을 작성할 때에는 간단하게 위 코드를 kernel.cpp 파일로 저장만 하면 됩니다. (아니면 여러분이 원하는 확장자와 파일이름을 사용하면 됩니다) 다만 중요한 것은, kernel_main 함수를 어떻게 정의되어 있는지에 주목해야 합니다. 다르게 말하면, 컴파일러가 함수의 이름을 바꾸기 때문입니다. (네임 맹글링) 이건 어셈블리 코드에서 참조한 함수 이름과 C++에서 출력하는 함수 이름이 서로 달라서 발생하는 문제로, extern 키워드를 이용해서 맹글링이 일어나지 않도록 막아야 하는 것입니다.
이걸 컴파일 할 때는 아래 명령을 사용하면 됩니다.
i686-elf-g++ -c kernel.c++ -o kernel.o -ffreestanding -O2 -Wall -Wextra -fno-exceptions -fno-rtti
Linking the Kernel
우리는 boot.s를 어셈블링했고, kernel.c를 컴파일 했습니다. 이것은 커널에 포함되어야 하는 오브젝트 파일 두개를 만들었고 최종 결과물을 만들기 위해서 우리는 이러한 파일들을 부트로더가 알아볼 수 있는 형태로 링크해야 합니다. 사용자 영역에서 동작하는 프로그램을 만들 때 여러분이 사용하는 도구집합은 그러한 프로그램들을 위한 기본 스크립트로 링크를 수행하는데, 커널 개발에서는 이런 방법이 적절하지 않습니다. 그래서 우리는 우리에게 맞는 링커 스크립트가 필요합니다. 아래 내용을 link.ld로 저장하세요.
/* 부트로더는 이 이미지를 읽어서 진입점으로 지정된 심볼을 실행시킵니다. */
ENTRY(_start)
/* 오브젝트 파일에 정의된 여러 섹션들이 최종 결과물의 어디에 들어가야 하는지를 정의합니다. */
SECTIONS
{
/* 이 이미지는 1 MiB 지점에 로드되어야 하고, 모든 섹션은 이 지점을 기준으로 재배치 되어야 한다고 선언합니다. */
. = 1M;
/* 가장 처음 등장해야 하는 것은 멀티부트 헤더이고, 이건 항상 맨 앞에 등장해야 합니다.
그렇지 않으면 부트로더는 이 파일이 뭔지 모를겁니다. 그 이후에 우리의 text 섹션이 배치될겁니다. */
.text BLOCK(4K) : ALIGN(4K)
{
*(.multiboot)
*(.text)
}
/* 읽기 전용 데이터 섹션입니다. */
.rodata BLOCK(4K) : ALIGN(4K)
{
*(.rodata)
}
/* 초기화된 읽기/쓰기가 가능한 데이터 섹션입니다. */
.data BLOCK(4K) : ALIGN(4K)
{
*(.data)
}
/* 초기화되지 않은 데이터 영역과 스택이 들어올겁니다. */
.bss BLOCK(4K) : ALIGN(4K)
{
*(COMMON)
*(.bss)
}
/* 컴파일러는 다른 심볼들을 많이 만들거지만, 그것들을 같은 이름의 세그먼트에 집어넣게 될겁니다.
필요하다면 다른 것들을 추가하면 됩니다. */
}
여러분이 최종 커널을 정확히 빌드할 수 있는 부속품들이 준비되었습니다. 우리는 컴파일러를 링커로 쓸겁니다. 만약 C++로 작성했다면 C++ 컴파일러를 사용해야 합니다.
i686-elf-gcc -T linker.ld -o myos.bin -ffreestanding -O2 -nostdlib boot.o kernel.o -lgcc
주의: 몇가지 튜토리얼들을 컴파일러보다 i686-elf-ld를 이용하기를 권장할 겁니다. 그런데 이건 링크하는 과정중에 수행되어야 하는 다양한 작업들이 일어나지 않게 될 수 있습니다.
이제 만들어진 myos.bin 파일은 여러분의 커널입니다. 이제 다른 파일들은 필요하지 않지요. 중요한건 크로스 컴파일러가 의존하는 런타임 루틴들이나 libgcc를 함께 링크하면 안된다는 겁니다. 그것들을 쓰지 않는 것이 나중에 문제를 덜 일으킬겁니다. 만약 크로스 컴파일러의 일부로 libgcc를 설치했다거나 빌드하지 않았다면 다시 크로스 컴파일러 만들기 과정으로 돌아가서 libgcc를 컴파일러에 포함시켜야 할겁니다. 컴파일러는 여러분이 그걸 제공하든 하지 않든 이 라이브러리에 의존합니다.
Verifying Multiboot
만약 여러분이 GRUB을 설치했다면 여러분은 어떤 파일이 정상적인 Multiboot 버젼 1 헤더를 가지고 있는지 아닌지 확인할 수 있습니다. 가장 중요한 것은 멀티 부트 헤더가 4 바이트 정렬을 사용하는 프로그램의 최초 8 KiB 안에 배치되어야 있어야 하는데, 그렇지 않다면 빌드를 하는 과정중에 뭔가 실수를 했거나, 링커 스크립트나 다른 뭔가가 잘못되었을 때 중대한 문제를 일으킬 수 있습니다. 헤더가 잘못되었다면 GRUB은 부팅할 때 그런걸 찾을 수 없다는 에러 메시지를 여러분에게 보여줄겁니다.
grub-file --is-x86-multiboot myos.bin
grub-file 명령은 아무 메시지도 보여주지 않지만 정상적인 헤더라면 종료 코드가 0, 정상적이지 않다면 1로 셋팅됩니다. 이건 쉘 스크립트를 통해 간단하게 만들 수 있습니다.
if grub-file --is-x86-multiboot myos.bin; then
echo multiboot confirmed
else
echo the file is not multiboot
fi
Booting the Kernel
몇가지를 수행하고 나면 여러분의 커널이 동작하는 것을 볼 수 있을 겁니다.
(역자 주: Building a bootable cdrom image: 이 부분은 생략하겠습니다. 아무래도 현실적이지 않다고 생각했습니다. 필요한 분은 댓글을 달아주세요. 필요한 분이 나타나면 이 부분을 추가하겠습니다. 같은 이유로 Testing your operating system(real hardware) 파트도 생략했습니다)
Testing your operating system (QEMU)
가상 머신은 커널 개발에 있어서 가장 유용한 도구입니다. 그것들은 빠르게 코드를 테스트 할 수 있도록 만들어 주며, 실행하는 동안에도 코드를 볼 수 있게 해줍니다. 음, 뭐, 끝도 없는 재부팅 싸이클이 여러분을 화나게 할지도 모르는 거죠. 가상머신들은 매우 빠르며, 우리가 만든 것 처럼 작은 운영체제를 시험하기에 더할나위 없이 좋습니다.
이 튜토리얼에서 우리는 QEMU를 썻습니다. 그리고 여러분이 원한다면 다른걸 쓸 수도 있고, 간단하게 ISO나 CD 이미지로 만들어서 집어 넣어도 됩니다. QEMU를 설치하고 나면 아래 명령으로 여러분의 운영체제를 테스트할 수 있습니다.
ISO 이미지로 만들었다면
qemu-system-i386 -cdrom myos.iso
그냥 바로 테스트 하려면
qemu-system-i386 -kernel myos.bin
(역자 주: 이하 내용은 내일 찾아뵙겠습니다. 아마 How to create an Operating System - Bare Bones, Moving Forward나 How to create an Operating System - Bare Bones, FAQ 라는 이름으로 두개의 글로 작성하게 될 것 같네요.)
(역자 주: Moving Forward - 앞으로 전진하기 - 혹은 FAQ를 보시려면 "[Translation] How to create an Operating System - Bare Bones, Moving Forward&FAQ"를 보시면 되겠습니다)
Review
이 글의 출처는 위에 원문 링크에 있습니다. 라이센스 정보는 없구요. 이 글은 생각보다 내용물의 난위도가 매우 낮은 편이었습니다. 그냥 어느정도 개발을 경험해보신 분들은 복사&붙혀넣기만으로 손쉽게 하실 수 있는 난위도였고, 다른 글들과는 달리 독자에게 퀴즈를 내준다는 것이 제일 좋았습니다. 다만, 제가 오역을 해서 원문의 퀄리티를 너무 떨어트리진 않았나 하는 걱정도 드네요. 제가 쓴 여느 글 처럼 출처와 원문 저자(이 글은 저자가 알려지지 않았는데 알게 되는 대로 반영하겠습니다), 역자 및 번역본 주소를 표기해 주시기 바랍니다.
출처 포기하는 방법:
How to create an Operating System - Bare Bones (OSDev.org) / 역: Jay K (http://www.jayks.ml/7)
이런 형태로 출처와 원문 저자(위와 같습니다), 역자 및 번역본 주소가 잘 표기되어 있으면 좋겠습니다.
감사합니다.
'Translations' 카테고리의 다른 글
How to create an Operating System - Bare Bones, Moving Forward&FAQ (0) | 2017.07.13 |
---|---|
Creating a C++ Thread Class (0) | 2017.07.11 |
A C++ Websocket Server for realtime interaction with Web client (0) | 2017.07.10 |
C++ Memory Leak Finder under Linux (0) | 2017.07.08 |