「ゼロからのOS自作入門」4章 4.1〜4.2を実行する(前編)
'「ゼロからのOS自作入門」4章 4.1〜4.2を実行します(前編)'
# Makefileの作成
下記のコードのkernel/Makefileを作成します.
TARGET = kernel.elf
OBJS = main.o
CXXFLAGS += -O2 -Wall -g --target=x86_64-elf -ffreestanding -mno-red-zone \
-fno-exceptions -fno-rtti -std=c++17
LDFLAGS += --entry KernelMain -z norelro --image-base 0x100000 --static
.PHONY: all
all: $(TARGET)
.PHONY: claen
clean: rm -rf *.o
kernel.elf: $(OBJS) Makefile
ld.lld $(LDFLAGS) -o kernel.elf $(OBJS)
%.o: %.cpp Makefile
clang++ $(CPPFLAGS) $(CXXFLAGS) -c $<
Makefileの変数は下記の通りです.
変数名 | 意味 |
---|---|
TARGET | このMakefileで生成する最終成果物. |
OBJS | TARGETを作るのに必要なオブジェクトファイル群. |
CXXFLAGS | コンパイルオプション |
LDFLAGS | リンクオプション |
Makefileの書き方として,最終成果物(TARGET)を作る上での途中で生成されるファイルもターゲットと呼び扱います.
上記のMakefileでは,all,clean, kernel.elf, %.oの4つのターゲット(生成されるファイル等)があります.
Makefileでのターゲットの書き方は以下の通りです.
ターゲット: 必須項目
レシピ
// %.o: %.cpp Makefile
// clang++ $(CPPFLAGS) $(CXXFLAGS) -c $<
PHONY(偽物)宣言は偽のTARGETを作る宣言です.
本来TARGETはファイルでないといけませんが,PHONY(偽物)宣言では「rm -rf *.o」のようなルール(指示)を指定します.
.PHONY: 偽ターゲット
偽ターゲット: 必須項目(or ルール)
レシピ
// .PHONY: claen
// clean:
// rm -rf *.o
makeの実行時に自動的に定義する変数と幹の実例を下記に示す.
変数名 | 意味 |
---|---|
$< | 必須項目の先頭 |
$^ | 必須項目全てをスペース区切りで並べたもの |
$@ | ターゲット(拡張子含む) |
$* | パターンルールにおける幹(stem) |
ターゲットパターン | 実際のターゲット | $*の値(幹) |
---|---|---|
%.o | foo.o | foo |
a.%.b | dir/a.foo.b | dir/foo |
下記のコードでMakefileを実行します.
cd /home/vscode/workspaces/mikanos-devcontainer/mikanos/kernel
make
// clang++ -I/home/vscode/osbook/devenv/x86_64-elf/include/c++/v1 -I/home/vscode/osbook/devenv/x86_64-elf/include -I/home/vscode/osbook/devenv/x86_64-elf/include/freetype2 -I/home/vscode/edk2/MdePkg/Include -I/home/vscode/edk2/MdePkg/Include/X64 -nostdlibinc -D__ELF__ -D_LDBL_EQ_DBL -D_GNU_SOURCE -D_POSIX_TIMERS -DEFIAPI='__attribute__((ms_abi))' -O2 -Wall -g --target=x86_64-elf -ffreestanding -mno-red-zone -fno-exceptions -fno-rtti -std=c++17 -c main.cpp
// ld.lld -L/home/vscode/osbook/devenv/x86_64-elf/lib --entry KernelMain -z norelro --image-base 0x100000 --static -o kernel.elf main.o
実行の工程は下記の通りです.
all: kerel.elf
--> 必須項目 (kerel.elf) のルール (kernel.elf: main.o Makefile)
--> 必須項目 (main.o)のルール (main.o: main.cpp Makefile)
--> 必須項目 (Makefile)のルール (ない)
--> レシピの実行でclang++でmain.oの生成
--> レシピなしで何も実行しない
# ピクセルを自在に描く
# フレームバッファの構成情報を表す構造体
/mikanos/kernel/frame_buffer_config.hppを作成します.
#pragma once
#include <stdint.h>
enum PixelFormat {
kPixelRGBResv8BitPerColor,
kPixelBGRResv8BitPerColor,
};
struct FrameBufferConfig {
uint8_t* frame_buffer;
uint32_t pixels_per_scan_line;
uint32_t horizontal_resolution;
uint32_t vertical_resolution;
enum PixelFormat pixel_format;
};
作成した/mikanos/kernel/frame_buffer_config.hppは,/mikanos/MikanLoaderPkg/にシンボリックリンクさせます.
cd ~/workspaces/mikanos-devcontainer/mikanos/kernel
ln frame_buffer_config.hpp ../MikanLoaderPkg/
# ブートローダがOS本体に描画に必要な情報を渡す処理
/mikanos/MikanLoaderPkg/Main.cに追記します.
追記箇所はcall_kernel付近です.
struct FrameBufferConfig config = {
(UINT8*)gop->Mode->FrameBufferBase,
gop->Mode->Info->PixelsPerScanLine,
gop->Mode->Info->HorizontalResolution,
gop->Mode->Info->VerticalResolution,
0
};
switch (gop->Mode->Info->PixelFormat) {
case PixelRedGreenBlueReserved8BitPerColor:
config.pixel_format = kPixelRGBResv8BitPerColor;
break;
case PixelBlueGreenRedReserved8BitPerColor:
config.pixel_format = kPixelBGRResv8BitPerColor;
break;
default:
Print(L"Unimplemented pixel format: %d\n", gop->Mode->Info->PixelFormat);
Halt();
}
typedef void EntryPointType(const struct FrameBufferConfig*);
EntryPointType* entry_point = (EntryPointType*)entry_addr;
entry_point(&config);
# WritePixel()を使って描画する処理
/mikanos/kernel/main.cppに追記します.
// #@@range_begin(call_write_pixel)
extern "C" void KernelMain(const FrameBufferConfig& frame_buffer_config){
for (int x = 0; x < frame_buffer_config.horizontal_resolution; ++x){
for (int y = 0; y < frame_buffer_config.vertical_resolution; ++y){
WritePixel(frame_buffer_config, x, y, {255, 255, 255});
}
}
for (int x = 0; x < 200; ++x){
for (int y = 0; y < 100; ++y){
WritePixel(frame_buffer_config, 100 + x, 100 + y, {0, 255, 0});
}
}
while (1) __asm__("hlt");
}
// #@@range_end(call_write_pixel)
# WritePixel()の実装
/mikanos/kernel/main.cppに追記します.
// #@@range_begin(write_pixel)
struct PixelColor{
uint8_t r, g, b;
};
/** WritePixelは1つの点を描画します
* @retval 0 成功
* @retval 非0 失敗
*/
int WritePixel(const FrameBufferConfig& config,
int x, int y, const PixelColor& c){
const int pixel_position = config.pixels_per_scan_line * y + x;
if (config.pixel_format == kPixelRGBResv8BitPerColor){
uint8_t* p = &config.frame_buffer[4 * pixel_position];
p[0] = c.r;
p[1] = c.g;
p[2] = c.b;
} else if (config.pixel_format == kPixelBGRResv8BitPerColor){
uint8_t* p = &config.frame_buffer[4 * pixel_position];
p[0] = c.b;
p[1] = c.g;
p[2] = c.r;
} else {
return -1;
}
return 0;
}
// #@@range_end(write_pixel)
# MakeFile
/mikanos/kernel/Makefileを修正します.
LDFLAGS に -z separate-code に追加します。
# Hello worldの実行
# ファイルの中身
/mikanos/kernel/frame_buffer_config.hpp
#pragma once
#include <stdint.h>
enum PixelFormat {
kPixelRGBResv8BitPerColor,
kPixelBGRResv8BitPerColor,
};
struct FrameBufferConfig {
uint8_t* frame_buffer;
uint32_t pixels_per_scan_line;
uint32_t horizontal_resolution;
uint32_t vertical_resolution;
enum PixelFormat pixel_format;
};
/mikanos/MikanLoaderPkg/Main.c
#include <Uefi.h>
#include <Library/UefiLib.h>
#include <Library/UefiBootServicesTableLib.h>
#include <Library/PrintLib.h>
#include <Library/MemoryAllocationLib.h>
#include <Protocol/LoadedImage.h>
#include <Protocol/SimpleFileSystem.h>
#include <Protocol/DiskIo2.h>
#include <Protocol/BlockIo.h>
#include <Guid/FileInfo.h>
#include "frame_buffer_config.hpp"
struct MemoryMap {
UINTN buffer_size;
VOID* buffer;
UINTN map_size;
UINTN map_key;
UINTN descriptor_size;
UINT32 descriptor_version;
};
EFI_STATUS GetMemoryMap(struct MemoryMap* map) {
if (map->buffer == NULL) {
return EFI_BUFFER_TOO_SMALL;
}
map->map_size = map->buffer_size;
return gBS->GetMemoryMap(
&map->map_size,
(EFI_MEMORY_DESCRIPTOR*)map->buffer,
&map->map_key,
&map->descriptor_size,
&map->descriptor_version);
}
const CHAR16* GetMemoryTypeUnicode(EFI_MEMORY_TYPE type) {
switch (type) {
case EfiReservedMemoryType: return L"EfiReservedMemoryType";
case EfiLoaderCode: return L"EfiLoaderCode";
case EfiLoaderData: return L"EfiLoaderData";
case EfiBootServicesCode: return L"EfiBootServicesCode";
case EfiBootServicesData: return L"EfiBootServicesData";
case EfiRuntimeServicesCode: return L"EfiRuntimeServicesCode";
case EfiRuntimeServicesData: return L"EfiRuntimeServicesData";
case EfiConventionalMemory: return L"EfiConventionalMemory";
case EfiUnusableMemory: return L"EfiUnusableMemory";
case EfiACPIReclaimMemory: return L"EfiACPIReclaimMemory";
case EfiACPIMemoryNVS: return L"EfiACPIMemoryNVS";
case EfiMemoryMappedIO: return L"EfiMemoryMappedIO";
case EfiMemoryMappedIOPortSpace: return L"EfiMemoryMappedIOPortSpace";
case EfiPalCode: return L"EfiPalCode";
case EfiPersistentMemory: return L"EfiPersistentMemory";
case EfiMaxMemoryType: return L"EfiMaxMemoryType";
default: return L"InvalidMemoryType";
}
}
EFI_STATUS SaveMemoryMap(struct MemoryMap* map, EFI_FILE_PROTOCOL* file) {
EFI_STATUS status;
CHAR8 buf[256];
UINTN len;
CHAR8* header =
"Index, Type, Type(name), PhysicalStart, NumberOfPages, Attribute\n";
len = AsciiStrLen(header);
status = file->Write(file, &len, header);
if (EFI_ERROR(status)) {
return status;
}
Print(L"map->buffer = %08lx, map->map_size = %08lx\n",
map->buffer, map->map_size);
EFI_PHYSICAL_ADDRESS iter;
int i;
for (iter = (EFI_PHYSICAL_ADDRESS)map->buffer, i = 0;
iter < (EFI_PHYSICAL_ADDRESS)map->buffer + map->map_size;
iter += map->descriptor_size, i++) {
EFI_MEMORY_DESCRIPTOR* desc = (EFI_MEMORY_DESCRIPTOR*)iter;
len = AsciiSPrint(
buf, sizeof(buf),
"%u, %x, %-ls, %08lx, %lx, %lx\n",
i, desc->Type, GetMemoryTypeUnicode(desc->Type),
desc->PhysicalStart, desc->NumberOfPages,
desc->Attribute & 0xffffflu);
status = file->Write(file, &len, buf);
if (EFI_ERROR(status)) {
return status;
}
}
return EFI_SUCCESS;
}
EFI_STATUS OpenRootDir(EFI_HANDLE image_handle, EFI_FILE_PROTOCOL** root) {
EFI_STATUS status;
EFI_LOADED_IMAGE_PROTOCOL* loaded_image;
EFI_SIMPLE_FILE_SYSTEM_PROTOCOL* fs;
status = gBS->OpenProtocol(
image_handle,
&gEfiLoadedImageProtocolGuid,
(VOID**)&loaded_image,
image_handle,
NULL,
EFI_OPEN_PROTOCOL_BY_HANDLE_PROTOCOL);
if (EFI_ERROR(status)) {
return status;
}
status = gBS->OpenProtocol(
loaded_image->DeviceHandle,
&gEfiSimpleFileSystemProtocolGuid,
(VOID**)&fs,
image_handle,
NULL,
EFI_OPEN_PROTOCOL_BY_HANDLE_PROTOCOL);
if (EFI_ERROR(status)) {
return status;
}
return fs->OpenVolume(fs, root);
}
EFI_STATUS OpenGOP(EFI_HANDLE image_handle,
EFI_GRAPHICS_OUTPUT_PROTOCOL** gop) {
EFI_STATUS status;
UINTN num_gop_handles = 0;
EFI_HANDLE* gop_handles = NULL;
status = gBS->LocateHandleBuffer(
ByProtocol,
&gEfiGraphicsOutputProtocolGuid,
NULL,
&num_gop_handles,
&gop_handles);
if (EFI_ERROR(status)) {
return status;
}
status = gBS->OpenProtocol(
gop_handles[0],
&gEfiGraphicsOutputProtocolGuid,
(VOID**)gop,
image_handle,
NULL,
EFI_OPEN_PROTOCOL_BY_HANDLE_PROTOCOL);
if (EFI_ERROR(status)) {
return status;
}
FreePool(gop_handles);
return EFI_SUCCESS;
}
const CHAR16* GetPixelFormatUnicode(EFI_GRAPHICS_PIXEL_FORMAT fmt) {
switch (fmt) {
case PixelRedGreenBlueReserved8BitPerColor:
return L"PixelRedGreenBlueReserved8BitPerColor";
case PixelBlueGreenRedReserved8BitPerColor:
return L"PixelBlueGreenRedReserved8BitPerColor";
case PixelBitMask:
return L"PixelBitMask";
case PixelBltOnly:
return L"PixelBltOnly";
case PixelFormatMax:
return L"PixelFormatMax";
default:
return L"InvalidPixelFormat";
}
}
void Halt(void) {
while (1) __asm__("hlt");
}
EFI_STATUS EFIAPI UefiMain(
EFI_HANDLE image_handle,
EFI_SYSTEM_TABLE* system_table) {
EFI_STATUS status;
Print(L"Hello, Mikan World!\n");
CHAR8 memmap_buf[4096 * 4];
struct MemoryMap memmap = {sizeof(memmap_buf), memmap_buf, 0, 0, 0, 0};
status = GetMemoryMap(&memmap);
if (EFI_ERROR(status)) {
Print(L"failed to get memory map: %r\n", status);
Halt();
}
EFI_FILE_PROTOCOL* root_dir;
status = OpenRootDir(image_handle, &root_dir);
if (EFI_ERROR(status)) {
Print(L"failed to open root directory: %r\n", status);
Halt();
}
EFI_FILE_PROTOCOL* memmap_file;
status = root_dir->Open(
root_dir, &memmap_file, L"\\memmap",
EFI_FILE_MODE_READ | EFI_FILE_MODE_WRITE | EFI_FILE_MODE_CREATE, 0);
if (EFI_ERROR(status)) {
Print(L"failed to open file '\\memmap': %r\n", status);
Print(L"Ignored.\n");
} else {
status = SaveMemoryMap(&memmap, memmap_file);
if (EFI_ERROR(status)) {
Print(L"failed to save memory map: %r\n", status);
Halt();
}
status = memmap_file->Close(memmap_file);
if (EFI_ERROR(status)) {
Print(L"failed to close memory map: %r\n", status);
Halt();
}
}
EFI_GRAPHICS_OUTPUT_PROTOCOL* gop;
status = OpenGOP(image_handle, &gop);
if (EFI_ERROR(status)) {
Print(L"failed to open GOP: %r\n", status);
Halt();
}
Print(L"Resolution: %ux%u, Pixel Format: %s, %u pixels/line\n",
gop->Mode->Info->HorizontalResolution,
gop->Mode->Info->VerticalResolution,
GetPixelFormatUnicode(gop->Mode->Info->PixelFormat),
gop->Mode->Info->PixelsPerScanLine);
Print(L"Frame Buffer: 0x%0lx - 0x%0lx, Size: %lu bytes\n",
gop->Mode->FrameBufferBase,
gop->Mode->FrameBufferBase + gop->Mode->FrameBufferSize,
gop->Mode->FrameBufferSize);
UINT8* frame_buffer = (UINT8*)gop->Mode->FrameBufferBase;
for (UINTN i = 0; i < gop->Mode->FrameBufferSize; ++i) {
frame_buffer[i] = 255;
}
EFI_FILE_PROTOCOL* kernel_file;
status = root_dir->Open(
root_dir, &kernel_file, L"\\kernel.elf",
EFI_FILE_MODE_READ, 0);
if (EFI_ERROR(status)) {
Print(L"failed to open file '\\kernel.elf': %r\n", status);
Halt();
}
UINTN file_info_size = sizeof(EFI_FILE_INFO) + sizeof(CHAR16) * 12;
UINT8 file_info_buffer[file_info_size];
status = kernel_file->GetInfo(
kernel_file, &gEfiFileInfoGuid,
&file_info_size, file_info_buffer);
if (EFI_ERROR(status)) {
Print(L"failed to get file information: %r\n", status);
Halt();
}
EFI_FILE_INFO* file_info = (EFI_FILE_INFO*)file_info_buffer;
UINTN kernel_file_size = file_info->FileSize;
// #@@range_begin(alloc_error)
EFI_PHYSICAL_ADDRESS kernel_base_addr = 0x100000;
status = gBS->AllocatePages(
AllocateAddress, EfiLoaderData,
(kernel_file_size + 0xfff) / 0x1000, &kernel_base_addr);
if (EFI_ERROR(status)) {
Print(L"failed to allocate pages: %r", status);
Halt();
}
// #@@range_end(alloc_error)
status = kernel_file->Read(kernel_file, &kernel_file_size, (VOID*)kernel_base_addr);
if (EFI_ERROR(status)) {
Print(L"error: %r", status);
Halt();
}
Print(L"call_kernel\n");
Print(L"Kernel: 0x%0lx (%lu bytes)\n", kernel_base_addr, kernel_file_size);
// #@@range_begin(exit_bs)
status = gBS->ExitBootServices(image_handle, memmap.map_key);
if (EFI_ERROR(status)) {
status = GetMemoryMap(&memmap);
if (EFI_ERROR(status)) {
Print(L"failed to get memory map: %r\n", status);
Halt();
}
status = gBS->ExitBootServices(image_handle, memmap.map_key);
if (EFI_ERROR(status)) {
Print(L"Could not exit boot service: %r\n", status);
Halt();
}
}
// #@@range_end(exit_bs)
// ここの周囲にPrintを記載しない
UINT64 entry_addr = *(UINT64*)(kernel_base_addr + 24);
// #@@range_begin(pass_frame_buffer_config)
struct FrameBufferConfig config = {
(UINT8*)gop->Mode->FrameBufferBase,
gop->Mode->Info->PixelsPerScanLine,
gop->Mode->Info->HorizontalResolution,
gop->Mode->Info->VerticalResolution,
0
};
switch (gop->Mode->Info->PixelFormat) {
case PixelRedGreenBlueReserved8BitPerColor:
config.pixel_format = kPixelRGBResv8BitPerColor;
break;
case PixelBlueGreenRedReserved8BitPerColor:
config.pixel_format = kPixelBGRResv8BitPerColor;
break;
default:
Print(L"Unimplemented pixel format: %d\n", gop->Mode->Info->PixelFormat);
Halt();
}
typedef void EntryPointType(const struct FrameBufferConfig*);
EntryPointType* entry_point = (EntryPointType*)entry_addr;
entry_point(&config);
// #@@range_end(pass_frame_buffer_config)
Print(L"All done\n");
while (1);
return EFI_SUCCESS;
}
/mikanos/kernel/main.cpp
#include <cstdint>
#include <cstddef>
#include "frame_buffer_config.hpp"
// #@@range_begin(write_pixel)
struct PixelColor{
uint8_t r, g, b;
};
/** WritePixelは1つの点を描画します
* @retval 0 成功
* @retval 非0 失敗
*/
int WritePixel(const FrameBufferConfig& config,
int x, int y, const PixelColor& c){
const int pixel_position = config.pixels_per_scan_line * y + x;
if (config.pixel_format == kPixelRGBResv8BitPerColor){
uint8_t* p = &config.frame_buffer[4 * pixel_position];
p[0] = c.r;
p[1] = c.g;
p[2] = c.b;
} else if (config.pixel_format == kPixelBGRResv8BitPerColor){
uint8_t* p = &config.frame_buffer[4 * pixel_position];
p[0] = c.b;
p[1] = c.g;
p[2] = c.r;
} else {
return -1;
}
return 0;
}
// #@@range_end(write_pixel)
// #@@range_begin(call_write_pixel)
extern "C" void KernelMain(const FrameBufferConfig& frame_buffer_config){
for (int x = 0; x < frame_buffer_config.horizontal_resolution; ++x){
for (int y = 0; y < frame_buffer_config.vertical_resolution; ++y){
WritePixel(frame_buffer_config, x, y, {255, 255, 255});
}
}
for (int x = 0; x < 200; ++x){
for (int y = 0; y < 100; ++y){
WritePixel(frame_buffer_config, 100 + x, 100 + y, {0, 255, 0});
}
}
while (1) __asm__("hlt");
}
// #@@range_end(call_write_pixel)
# ビルド
下記のコードで変更したMain.c等を反映します.
source $HOME/osbook/devenv/buildenv.sh
cd ~/workspaces/mikanos-devcontainer/mikanos/kernel
make
cd $HOME/edk2/
source edksetup.sh
build
# Hello world
下記のコードでイメージファイルを上書きし,Hello Worldを実行します.
cd $HOME/edk2
$HOME/osbook/devenv/run_qemu.sh Build/MikanLoaderX64/DEBUG_CLANG38/X64/Loader.efi $HOME/workspaces/mikanos-devcontainer/mikanos/kernel/kernel.elf
# まとめ
「ゼロからのOS自作入門」4章 4.1〜4.2を実行しました(前編).
# 参考サイト
『ゼロからのOS自作入門』をMacで動かしてみた記録(第4章 4.2 osbook_day04b) (opens new window)