0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【ゼロからのOS自作入門をやってみる】第1章 PCの仕組みとハローワールド

Last updated at Posted at 2025-05-19

前々から興味があった「ゼロからのOS自作入門」をゆっくり読んでみようと思います。
挫折する可能性が非常に高いですが、作業メモをQiitaに書いていこうと思います。

作業は基本的にこちらのリポジトリで行います。

バイナリエディタでブートローダーを実装

MACなので Hex Fiend というアプリで書いてみました。

バイナリエディタを使ったことがないので適当にインストールしました

image.png

とりあえず、チェックサムが 12430 になっていればOKです。

チェックサムが全然合わなくて心折れそう

sum ~/Downloads/BOOTX64.EFI
# 12430 2 /Users/ktamido/Downloads/BOOTX64.EFI

できたファイルはこちら -> BOOTX64.EFI (1.5 kB)

BOOTX64.EFIの内容

出力文字列情報

Hello, World!の文字列情報は 0x400 から 0x041b あたりに存在します。
UEFIでは文字コードはUCS-2を使うことが決まっています。
UCS-2は一文字を2バイトで表します。例えば H はASCIIでは 0x48 ですが、 UCS-2では 0x0048 となります。
x86-64アーキテクチャはリトルエンディアンを採用しているので、バイナリエディタで入力するときは下位バイトを先に入力し、 0x4800 となります。

機械語命令

0x0200 から 0x022f あたりが文字列を画面に表示する命令のようです。

実機でイメージを起動

USBメモリをGPTでフォーマットして、EFIシステムパーティションの /EFI/BOOT/BOOTX64.EFI に先ほど作成したBOOTX64.EFIを配置すると、PCで起動できるようです。

ちょうどいいPCがなかったので、企業のリース落ちらしきパソコンを1.5万円で購入 (結構いいスペックしてる)

そもそもBIOSとUEFI、MBRとGPTの違いがよくわかっていなかったので調べてみました。

ESP(EFIシステムパーティション)とは

UEFIにおいて、起動プロセスに必須のパーティションで、ブートローダー(/EFI/BOOT/BOOTX64.EFI)やデバイスドライバ、システムユーティリティを格納します。
ファイルシステムは FAT32 でフォーマットされ、パーティションサイズは一般的に100MB ~ 1GB程度。
パーティションタイプGUIDは C12A7328-F81F-11D2-BA4B-00A0C93EC93B となります。

fdiskでパーティションがESPかを確認することができます。

fdisk -l
# Disk /dev/nvme0n1: 1.82 TiB, 2000398934016 bytes, 3907029168 sectors
# Disk model: KIOXIA-EXCERIA PLUS G3 SSD
# Units: sectors of 1 * 512 = 512 bytes
# Sector size (logical/physical): 512 bytes / 512 bytes
# I/O size (minimum/optimal): 512 bytes / 512 bytes
# Disklabel type: gpt
# Disk identifier: 38496A13-AD66-44C7-8B35-7C373B9B75AB
#
# Device           Start        End    Sectors  Size Type
# /dev/nvme0n1p1    2048    2203647    2201600    1G EFI System
# /dev/nvme0n1p2 2203648 2931892223 2929688576  1.4T Linux filesystem

MacでUSBメモリに書き込む方法

ディスクユーティリティをCLIで使います。

USBメモリをGPTでフォーマットします。

# デバイスの確認
diskutil list
# ...
#/dev/disk4 (external, physical):
#   #:                       TYPE NAME                    SIZE       IDENTIFIER
#   0:      GUID_partition_scheme                        *31.0 GB    disk4
#   1:                        EFI NO NAME                 209.7 MB   disk4s1
#   2:       Microsoft Basic Data NO NAME                 30.8 GB    disk4s2


# ディスクをGPTで初期化(UEFIで起動できるように)
# diskutil eraseDisk ファイルフォーマット ボリューム名 パーティション方式 デバイスファイルパス
diskutil eraseDisk ExFAT VOL1 GPT /dev/disk4
# Started erase on disk4
# Unmounting disk
# Creating the partition map
# Waiting for partitions to activate
# Formatting disk4s2 as ExFAT with name VOL1
# Volume name      : VOL1
# Partition offset : 411648 sectors (210763776 bytes)
# Volume size      : 60215296 sectors (30830231552 bytes)
# Bytes per sector : 512
# Bytes per cluster: 32768
# FAT offset       : 2048 sectors (1048576 bytes)
# # FAT sectors    : 8192
# Number of FATs   : 1
# Cluster offset   : 10240 sectors (5242880 bytes)
# # Clusters       : 940704
# Volume Serial #  : 6801d255
# Bitmap start     : 2
# Bitmap file size : 117588
# Upcase start     : 6
# Upcase file size : 5836
# Root start       : 7
# Mounting disk
# Finished erase on disk


# 生成されたパーティションを確認
# NOTE: TYPE が EFI となっているのが EFIシステムパーティション (ESP)
diskutil list
# ...
# /dev/disk4 (external, physical):
#    #:                       TYPE NAME                    SIZE       IDENTIFIER
#    0:      GUID_partition_scheme                        *31.0 GB    disk4
#    1:                        EFI NO NAME                 209.7 MB   disk4s1
#    2:       Microsoft Basic Data VOL1                    30.8 GB    disk4s2


# EFIシステムパーティション(disk4s1)を確認
diskutil info disk4s1
#   Device Identifier:         disk4s1
#   Device Node:               /dev/disk4s1
#   Whole:                     No
#   Part of Whole:             disk4
#
#   Volume Name:               NO NAME
#   Mounted:                   No
#
#   Partition Type:            EFI
#   File System Personality:   MS-DOS FAT32
#   Type (Bundle):             msdos
#   Name (User Visible):       MS-DOS (FAT32)
#
#   OS Can Be Installed:       No
#   Media Type:                一般
#   Protocol:                  USB
#   SMART Status:              Not Supported
#   Volume UUID:               0E239BC6-F960-3107-89CF-1C97F78BB46B
#   Disk / Partition UUID:     D8B649D9-3BFE-4B21-B2DB-595251E72687
#   Partition Offset:          20480 Bytes (40 512-Byte-Device-Blocks)
#
#   Disk Size:                 209.7 MB (209715200 Bytes) (exactly 409600 512-Byte-Units)
#   Device Block Size:         512 Bytes
#
#   Volume Total Space:        0 B (0 Bytes) (exactly 0 512-Byte-Units)
#   Volume Free Space:         0 B (0 Bytes) (exactly 0 512-Byte-Units)
#
#   Media OS Use Only:         No
#   Media Read-Only:           No
#   Volume Read-Only:          Not applicable (not mounted)
#
#   Device Location:           External
#   Removable Media:           Removable
#   Media Removal:             Software-Activated
#
#   Solid State:               Info not available

# disk4s2を確認
diskutil info disk4s2
#   Device Identifier:         disk4s2
#   Device Node:               /dev/disk4s2
#   Whole:                     No
#   Part of Whole:             disk4
#
#   Volume Name:               VOL1
#   Mounted:                   Yes
#   Mount Point:               /Volumes/VOL1
#
#   Partition Type:            Microsoft Basic Data
#   File System Personality:   ExFAT
#   Type (Bundle):             exfat
#   Name (User Visible):       ExFAT
#
#   OS Can Be Installed:       No
#   Media Type:                一般
#   Protocol:                  USB
#   SMART Status:              Not Supported
#   Volume UUID:               9EBED486-32B4-3369-A19E-2D42E1429BE7
#   Disk / Partition UUID:     985066A8-8392-481A-9EF9-780E7566AF13
#   Partition Offset:          210763776 Bytes (411648 512-Byte-Device-Blocks)
#
#   Disk Size:                 30.8 GB (30830231552 Bytes) (exactly 60215296 512-Byte-Units)
#   Device Block Size:         512 Bytes
#
#   Volume Total Space:        30.8 GB (30825021440 Bytes) (exactly 60205120 512-Byte-Units)
#   Volume Used Space:         3.6 MB (3637248 Bytes) (exactly 7104 512-Byte-Units) (0.0%)
#   Volume Free Space:         30.8 GB (30821384192 Bytes) (exactly 60198016 512-Byte-Units) (100.0%)
#   Allocation Block Size:     512 Bytes
#
#   Media OS Use Only:         No
#   Media Read-Only:           No
#   Volume Read-Only:          No
#
#   Device Location:           External
#   Removable Media:           Removable
#   Media Removal:             Software-Activated
#
#   Solid State:               Info not available

EFIシステムパーティション(disk4s1)をマウントしてブートローダー(/EFI/BOOT/BOOTX64.EFI)を配置します。

mkdir -p ~/mnt/esp

# EFIシステムパーティションをマウント
sudo diskutil mount -mountPoint ~/mnt/esp /dev/disk4s1

# ブートローダー(BOOTX64.EFI)を/EFI/BOOT/BOOTX64.EFIに配置
mkdir -p ~/mnt/esp/EFI/BOOT
cp BOOTX64.EFI ~/mnt/esp/EFI/BOOT/BOOTX64.EFI

# アンマウント
diskutil unmountDisk /dev/disk4

Linux(Ubuntu24.04)でUSBメモリに書き込む方法

USBメモリをGPTでフォーマットします。

# ディスクがマウントされてしまっている場合はアンマウント (GUI付きのLinuxだと必要)
sudo df -h
# ...
# /dev/sdb2        29G  3.5M   29G   1% /media/ktamido/VOL1
sudo umount /media/ktamido/VOL1

# 接続されているディスクを確認
sudo fdisk -l
# ...
# ディスク /dev/sda: 28.91 GiB, 31042043904 バイト, 60628992 セクタ
# Disk model: USB Flash Disk  
# 単位: セクタ (1 * 512 = 512 バイト)
# セクタサイズ (論理 / 物理): 512 バイト / 512 バイト
# I/O サイズ (最小 / 推奨): 512 バイト / 512 バイト
# ディスクラベルのタイプ: gpt
# ディスク識別子: 1B5CA5F7-2AE4-4ACE-B9AC-15FC7FE1E041

DEVICE_FILE=/dev/sda

sudo parted $DEVICE_FILE

# パーティションテーブルを作成
(parted) mklabel gpt
(parted) print
# モデル: BUFFALO USB Flash Disk (scsi)
# ディスク /dev/sda: 31.0GB
# セクタサイズ (論理/物理): 512B/512B
# パーティションテーブル: gpt
# ディスクフラグ:
# 
# 番号  開始  終了  サイズ  ファイルシステム  名前  フラグ

# EFIシステムパーティション(ESP)を作成
(parted) mkpart primary fat32 1MiB 1GiB
(parted) set 1 esp on
(parted) print
# モデル: BUFFALO USB Flash Disk (scsi)
# ディスク /dev/sda: 31.0GB
# セクタサイズ (論理/物理): 512B/512B
# パーティションテーブル: gpt
# ディスクフラグ:
# 
# 番号  開始    終了    サイズ  ファイルシステム  名前     フラグ
#  1    1049kB  1074MB  1073MB                    primary  boot, esp

# partedを終了
(parted) quit


# パーティションが作成できていることを確認
sudo fdisk -l $DEVICE_FILE
# ディスク /dev/sda: 28.91 GiB, 31042043904 バイト, 60628992 セクタ
# Disk model: USB Flash Disk
# 単位: セクタ (1 * 512 = 512 バイト)
# セクタサイズ (論理 / 物理): 512 バイト / 512 バイト
# I/O サイズ (最小 / 推奨): 512 バイト / 512 バイト
# ディスクラベルのタイプ: gpt
# ディスク識別子: 832B57BC-0A3B-442D-BD4B-5597B99950AB
# 
# デバイス   開始位置 最後から  セクタ サイズ タイプ
# /dev/sda1      2048  2097151 2095104  1023M EFI システム

# ファイルシステムを作成
sudo mkfs.vfat -F32 ${DEVICE_FILE}1

# ファイルシステムがfat32になっていることを確認
sudo parted $DEVICE_FILE print
# モデル: BUFFALO USB Flash Disk (scsi)
# ディスク /dev/sda: 31.0GB
# セクタサイズ (論理/物理): 512B/512B
# パーティションテーブル: gpt
# ディスクフラグ:
#
# 番号  開始    終了    サイズ  ファイルシステム  名前     フラグ
#  1    1049kB  1074MB  1073MB  fat32             primary  boot, esp

EFIシステムパーティション(disk4s1)をマウントしてブートローダー(/EFI/BOOT/BOOTX64.EFI)を配置します。

# /EFI/BOOT/BOOTX64.EFIを配置
sudo mkdir -p /mnt/esp
sudo mount ${DEVICE_FILE}1 /mnt/esp
sudo mkdir -p /mnt/esp/EFI/BOOT
sudo cp /home/ktamido/BOOTX64.EFI /mnt/esp/EFI/BOOT/BOOTX64.EFI
sudo umount /mnt/esp

UEFI BIOSで起動ディスクの1番目にUSBを設定

USBを起動するPCのUEFI BIOS画面を開いて以下の設定を行います。
※ 富士通FMVは起動時にF2でUEFI BIOSが起動します。

  1. 起動ディスクの1番目をUSBメモリに設定
  2. セキュアブートをOFFにする

IMG_8665.jpg

IMG_8664.jpg

起動

USBを挿して起動するとこんな感じに文字が表示されます。

IMG_8666.jpg

QEMUでイメージを起動

QEMUでもイメージを起動できるようなので試してみます。

sudo apt install qemu-system-x86 git


mkdir tmp
cd tmp

# イメージの作成
qemu-img create -f raw disk.img 200M
mkfs.fat -n "MIKAN OS" -s 2 -f 2 -R 32 -F 32 disk.img

# 作成したイメージの確認
sudo parted disk.img print
# モデル:  (file)
# ディスク /home/ktamido/Projects/mikanos/tmp/disk.img: 210MB
# セクタサイズ (論理/物理): 512B/512B
# パーティションテーブル: loop
# ディスクフラグ:
#
# 番号  開始   終了   サイズ  ファイルシステム  フラグ
#  1    0.00B  210MB  210MB   fat32

# EFIシステムパーティション(ESP)の/ EFI/BOOT に BOOTX64.EFI を配置
mkdir -p mnt
sudo mount -o loop disk.img mnt
sudo mkdir -p mnt/EFI/BOOT
sudo cp BOOTX64.EFI mnt/EFI/BOOT/
sudo umount mnt

# OVMF_CODE.fd, OVMF_VARS.fdを参照するためにリポジトリをクローン
git clone https://github.com/uchan-nos/mikanos-build.git

# CLIモードで実行する場合は -nographicをつける
sudo qemu-system-x86_64 \
  -drive if=pflash,file=mikanos-build/devenv/OVMF_CODE.fd,format=raw \
  -drive if=pflash,file=mikanos-build/devenv/OVMF_VARS.fd,format=raw \
  -drive file=disk.img,format=raw \
  -nographic

起動するとこんな感じになる

image.png

CLIモードのQEMUを終了するときは Ctrl + a を押して x

C言語でブートローダを実装

先ほどバイナリエディタで作成したブートローダーを今度はC言語で実装してみます。

C言語は書いたことがないですが、とりあえずやりながら調べて覚える方針。

この時点では何を書いているかさっぱりわからない。
エントリーポイントでprint("Hello, world")をするために必要な依存を無理やり定義している感じだろうか。

UEFIアプリケーション(ブートローダー)のソースコード

hello.c
typedef unsigned short CHAR16;
typedef unsigned long long EFI_STATUS;
typedef void *EFI_HANDLE;

// _EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL構造体の宣言。(定義は後で)
// この構造体をポインタ型として先に使いたいため宣言のみ行う
struct _EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL;

typedef EFI_STATUS (*EFI_TEXT_STRING)(
  struct _EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL *This,
  CHAR16                                  *String
);

// _EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL 構造体に EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL という別名をつける
typedef struct _EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL {
  void            *dummy;  // void* 型はどんな方のポインタにもなれる汎用ポインタ。サイズ合わせや将来の拡張のために存在する
  EFI_TEXT_STRING OutputString;
} EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL;

// 構造体を定義して EFI_SYSTEM_TABLE という別名を付ける
typedef struct {
  char dummy[52];
  EFI_HANDLE ConsoleOutHandle;
  EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL *ConOut;
} EFI_SYSTEM_TABLE;

// エントリーポイント
EFI_STATUS EfiMain(
  EFI_HANDLE IageHandle,
  EFI_SYSTEM_TABLE *SystemTable
) {
  SystemTable->ConOut->OutputString(SystemTable->ConOut, L"Hello, world!\n");
  while(1);
  return 0;
}

ブートローダーのビルド & イメージ作成 & 実行

sudo apt update && sudo apt install -y make clang lld qemu-system-x86

# hello.cをコンパイルしてCOFF形式のhello.oを生成する
#   -target x86_64-pc-win32-coff: 出力を Windows向けの x86_64 COFF形式(=UEFIで使える形式)にする
#   -mno-red-zone: red zone(スタックの下の128バイト)を使わない。割り込みが入る環境(UEFIなど)では red zone は危険なのでオフにする
#   -fno-stack-protector: スタックプロテクションをオフにする。UEFIでは実行時ライブラリがないので不要(むしろ動かない)
#   -fshort-wchar: wchar_t を 2バイト(UTF-16)にする。UEFI は CHAR16 = wchar_t(16bit)と前提しているため
#   -Wall: 全ての一般的な警告を有効化(コードの品質チェック)
#   -o $@ 出力ファイル名を指定
#   -c: コンパイルのみ実行(リンクはしない)
clang -target x86_64-pc-win32-coff \
  -mno-red-zone \
  -fno-stack-protector \
  -fshort-wchar \
  -Wall \
  -o hello.o -c hello.c

# hello.oをリンクしてPE形式の実行可能ファイル (hello.efi) を生成する
# -subsystem:efi_application UEFIファームウェアで起動可能な .efi 形式にする
# -entry:EfiMain エントリーポイントを EfiMain にする
# -out:hello.efi 出力ファイル名を指定
lld-link /subsystem:efi_application /entry:EfiMain /out:hello.efi hello.o


# イメージの作成
IMAGE_FILE=disk.img
qemu-img create -f raw $IMAGE_FILE 200M

# イメージにファイルシステム(FAT32)を作成
#   -n "MIKAN OS" ボリュームラベルを "MIKAN OS" に設定します。エクスプローラーなどでドライブ名として表示される名前です。
#   -s 2 セクタサイズを指定します。1セクタは通常 512 バイトなので、1 クラスタ = 1024 バイトになります。クラスタが小さいと空き容量の効率は良くなりますが、FAT テーブルのサイズは大きくなります。
#   -f 2 FAT テーブルの数を指定します。通常 2 つ(FAT の冗長性を確保)。1 つが壊れても、もう 1 つでリカバリ可能。
#   -R 32 ルートディレクトリエントリの予約数。FAT12/16ではルートディレクトリが固定サイズでしたが、FAT32ではこの数で予約されるクラスタ数を指定します(ファイル数とは直接関係しません)。
#   -F 32 FAT の種類を指定します。ここでは FAT32。FAT12 や FAT16 にも変更可能ですが、容量が大きいディスクイメージでは FAT32 が推奨されます。
mkfs.fat -n "MIKAN OS" -s 2 -f 2 -R 32 -F 32 $IMAGE_FILE
sudo parted $IMAGE_FILE print
mkdir -p mnt
sudo mount -o loop $IMAGE_FILE mnt
sudo mkdir -p mnt/EFI/BOOT
sudo cp hello.efi mnt/EFI/BOOT/BOOTX64.EFI
sudo umount mnt

# qemuでイメージを実行
# Ctrl + a のあとに x でしゅう
sudo qemu-system-x86_64 \
  -drive if=pflash,file=OVMF_CODE.fd,format=raw \
  -drive if=pflash,file=OVMF_VARS.fd,format=raw \
  -drive file=disk.img,format=raw \
  -nographic

# お掃除
sudo rm -rf hello.efi hello.o disk.img mnt

最終的に出来上がったソースコード

Makefileを作って、ビルド、イメージ作成、QEMUで起動する一連の処理を1コマンドでできるようにしてみました。

make run
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?