panda's tech note

Advent Calendar 2018: advos

Day 1: 環境の構築

まず最初に開発環境を構築しようと思います。私は普段はmacOS上で開発するのですが,残念なことにmacOSの標準リンカ(/usr/bin/ld)の-Tオプションが使えません。そのため,OSをBIOSから実行するバイナリを生成できません。いままでは,FreeBSDのクロスコンパイラ(gcc)をインストールしていましたが,開発環境の統一化とテスト自動化・CIを考えて今回はDockerを使った開発環境の構築をしてみようと思います。

と言ってもDockerはLinuxコンテナなので,自作OSを直接動かすことはできません。ですので,仮想マシン上で実行します。仮想マシンの実行環境としてQEMUを使います。DockerでGUI (X)を使うには,クライアントを入れたり設定したりしないといけないので,今回はすべてCLIでやります。CLIだけで完結させるためにうQEMUをcursesモードで使います。ただし,cursesを使う都合上,CTRL-Cなどのプロセスへのキーボード入力が効かないので,強制終了させたいときにはESC-2(またはALT-2)でQEMUのモニタコンソールを開き,quitと打ち込むことで終了できます。

DockerとQEMUを使って開発環境と実行環境を整えますが,今回はそれに加えてdocker-composeを使おうと思います。個人的にはYAMLが好きではないのですが,PXEブートに対応するときにDHCPサーバとTFTPサーバの連携が楽だと思われるためです。今回PXEブートまで対応できるかはわかりません。ただし,docker-compose.ymlのサービス定義内にtty: trueを指定すればttyが使えるはずなのですが,docker-composeの標準出力がサービスのttyと衝突するためか,hangしてしまうので,cursesベースのUIを使うときはdocker-compose upではなくdocker-compose runで実行します。PXEブートするときはどうするか決めていないです。

macOSへのDocker環境のインストール

macOSでDocker環境を構築するには仮想マシン上にDocker用のLinuxをインストールする必要があります。そのため,VMを管理するdocker-machineというパッケージが必要になります。dockerおよびdocker-composeにあわせて,docker-machineも以下のようにbrewでインストールします。

$ brew install docker docker-machine docker-compose  

macOSではVM上のLinuxを使うため,ハイパーバイザが必要です。今回はVirtualBoxを使います。DockerのホストLinuxマシンをVirtualBox上にインストールするには以下のコマンドを使います。

$ docker-machine create --driver virtualbox dockerdev

このDockerのホストLinuxを起動するには次のコマンドを使います。

$ docker-machine start dockerdev

少しややこしいですが,今回開発するOSは,「macOS」上の「DockerのホストLinux」上の「Dockerコンテナ」上の「QEMU」上で動きます。VirtualBox上でも直接動かすこともできますが,コンパイル環境の構築が面倒なので,今回はこうします。

インストールしたDockerのホストLinuxをmacOSのdockerコマンドやdocker-composeコマンドから使うために以下のコマンドで環境変数を設定します。

$ eval $(docker-machine env dockerdev)

これでmacOSへのDocker環境の構築は終了です。eval $(docker-machine env dockerdev)はシェルの起動スクリプトに入れておくと良いと思います。

レポジトリのディレクトリ構成

ディレクトリ構成は以下のようにします。OSのソースコードはsrcに入れます。

  • src: OSのソースコード
    • boot: ブートローダ
  • qemu: OSを実行するQEMU用Dockerfile置き場

今日は環境構築が目標なのでブートローダについては明日以降にまわそうと思います。ただ,何も実行できないと悲しいので,src/boot/mbr.SのようにBIOSの機能を使ってメッセージを表示するだけのアセンブリプログラムとOSイメージを作成するためのMakefileを置いています。これらのファイルについては明日説明するつもりです。

QEMUでOSを実行するコンテナのDockerfile

自作OSのコンパイルおよび実行を行うコンテナを定義します。以下にDockerfileを書きます。

FROM ubuntu:16.04

MAINTAINER Hirochika Asai <panda@jar.jp>

## Install build-essentials and qemu
RUN apt-get update
RUN apt-get install -y --no-install-recommends build-essential qemu-system

## Copy source to the workdir
COPY src /usr/src
WORKDIR /usr/src
RUN make

## Run the OS with qemu
CMD ["qemu-system-x86_64", "-m", "1024", \
    "-drive", "id=disk,file=advos.img,if=none", \
    "-device", "ahci,id=ahci", \
    "-device", "ide-drive,drive=disk,bus=ahci.0", \
    "-boot", "a", "-display", "curses"]

ベースのイメージとしてUbuntu 16.04を使います(1行目)。そこにaptでコンパイル環境としてbuild-essential,実行環境としてqemu-systemをインストールします(7行目)。10行目のCOPY src /usr/src でレポジトリ内のsrcディレクトリの中身を全てコンテナ内の/usr/srcにコピーし,makeMakefileの定義通りにコンパイルします。後述しますが,このmakeでOSの実行イメージがadvos.imgとして作成されます。最後にCMDでQEMUでそのOSイメージを実行します。

docker-compose.yml

上述のDockerfileでOSのソースファイルをコピー,コンパイルし,QEMUで実行するコンテナを定義しました。OS開発は原則単一のサービス(QEMUのみ)でできますが,例えばPXEブートのように他のサービスと組み合わせるときに構成管理をしておくと便利なので,今回は勉強も兼ねてdocker-composeを使います。

docker-compose.ymlは以下のように最低限のものにしています。OSをQEMU以外の実行環境(例えばbochs)でテストするかもしれないので,Dockerfilesrcディレクトリやルートに置かず,contextをレポジトリのルートにしてqemuディレクトリ下のDockerfileを指定するようにしました。

version: '3'

services:
  qemu:
    build:
      context: ./
      dockerfile: ./qemu/Dockerfile

Dockerコンテナの実行

これでOS開発環境は整いましたので,試しにコンパイルおよび実行をしてみます。

$ docker-compose build

qemu/Dockerfileに従い,ビルドツールやQEMUをインストールし,advosイメージをコンパイルしたコンテナを構築します。

$ docker-compose run qemu

を実行すると,curses画面でCLIに

 Welcome to advos

と表示されると思います。いまの状態のadvosにはシャットダウン機能が実装されていないので,ESC-2またはALT-2で以下のようなQEMUのコンソール画面に遷移できるので,こちらからquitと打って終了してください。

compat_monitor0 console
QEMU 2.5.0 monitor - type 'help' for more information
(qemu)

まとめと明日の予定

今日はDockerとdocker-composeを使ったOSの開発環境・実行環境を整えました。明日はBIOSから自作OSを起動するためのブートローダについて書こうと思います。