令和アドベントカレンダー: advos
2018年12月のアドベントカレンダーでadvosというOSを作りましたが,平成の最後にadvosでやり残した実装をやろうと思います。(某氏より,「令和に向けたアドベントカレンダーだから平成アドベントカレンダーではなく,令和アドベントカレンダーだと指摘を受けたので,訂正します。」)
Extra Day 1: PXEブート(その1)
今日は,ネットワークからシステムを起動するPXE (Pre-Boot Execution Environment)ブートを取り扱います。ネットワークブートやディスクレスブートとも呼ばれます。
PXEブートの仕組み
PXEブートは,まず通常のDHCPによるアドレス設定を行います。具体的には,BIOSがDHCP Discoverメッセージを送信し,DHCPサーバはDHCP Offerによりアドレスを割り当てます。次に,BIOSはこのアドレスを送信元としてDHCP RequestをDHCPサーバに送付することで,割り当てられたアドレスを使用することを通知します。DHCPサーバは,その確認応答としてDHCP Ackをクライアントに返します。
DHCPによりアドレスの設定が終わると,BIOSはDHCPサーバにOption 66 (Boot Server)およびOption 67 (Bootstrap File)のDHCP Requestを送信します。DHCPサーバはこれらのオプションにTFTPサーバおよびファイル名を含むDHCP Ackを返します。PXEブートでは,このTFTPサーバから指定されたファイル名のファイルをTFTPで取得し,メインメモリに読み込み,実行します。このファイルは仕様上は0x0000:0x7c00
に読み込まれることになっていますが,今回は特にこのアドレスを想定せずに実装しています。
また,メインメモリには,実行されるファイル以外にPXEブートの管理構造体(TFTPサーバ情報等)が保存されており,起動後のレジスタ %es:%bx
(PXENV+)または%ss:(%sp+4)
(!PXE) で場所を取得できます。PXEバージョン1.5以降では!PXEが使われますが,PXENV+も後方互換性のため使われています。さらに,%ss:%sp
から下位アドレス方向に最低1.5 KiBはスタックとして利用できることが保証されています。
PXEブートの設計
PXEブートでは,MBRの512バイト制限とは大きく異なり,最大32 KiBまでのファイルを読み込むことができます。そのため,MBRのように2段階のブートローダを実装せずに,このファイルをブートローダとして使い,これからカーネルやモジュールファイルを読み込,実行します。
PXEブート環境の構築と実行
いろいろとTFTPサーバやDHCPサーバを別のコンテナで実行しようと試みましたが,コンテナ間をブリッジで繋ぐことができなかったので,同一コンテナ内にDHCPサーバとTFTPサーバを入れ,QEMUを実行する環境を構築しました。
PXEブート環境のDockerfileは以下のように書きました。DHCPサーバとしてisc-dhcp-server
,TFTPサーバとしてtftpd-hpa
を使うため,これらのパッケージをインストールしています。また,ブリッジを作成し,そのブリッジに対してアドレス設定をCLIコマンドで設定するため,iproute2
,bridge-utils
をインストールします。また,TFTPサーバで公開するディレクトリとして/tftpboot
を作成し,パーミッションを読み書き可能なように0777
に設定します。さらに,コンパイルしたpxeboot
ファイル(後述)をpxeadvos
という名前でこのディレクトリに設置します。
FROM ubuntu:18.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 isc-dhcp-server iproute2 bridge-utils tftpd-hpa
## Copy source to the workdir
COPY src /usr/src
WORKDIR /usr/src
RUN make
RUN mkdir -p /tftpboot
RUN chmod 0777 /tftpboot
RUN cp boot/pxeboot /tftpboot/pxeadvos
## For qemu+bridge
RUN mkdir /etc/qemu
RUN echo "allow all" > /etc/qemu/bridge.conf
## Run the OS with qemu
CMD ["/pxe-cmd.sh"]
以下の2行は,QEMUの仮想NICをアタッチするブリッジのACL設定ファイルですが,今回はすべてを許可するallow all
で設定します。
RUN mkdir /etc/qemu
RUN echo "allow all" > /etc/qemu/bridge.conf
以下のコマンドで,/pxe-cmd.sh
を実行します。このファイルはdocker-compose.yml
でボリュームとしてマウントするように指定しています。ブリッジの作成などはDockerfile内のRUN
命令では作成できなかったため,CMD
命令でこのシェルスクリプトを指定し,スクリプト内で作成するようにしました。
CMD ["/pxe-cmd.sh"]
pxe-cmd.sh
の中身は以下の通りです。ブリッジインターフェイス br100
を作成,設定し,DHCPサーバおよびTFTPサーバを起動します。そして,最後にQEMUを実行します。
#!/bin/sh
## Create a new bridge interface and add an IPv4 address
brctl addbr br100
ip a add 192.168.253.1/24 dev br100
ip link set br100 up
## Run dhcp server
touch /var/lib/dhcp/dhcpd.leases
dhcpd -4 -pf /run/dhcpd.pid -cf /etc/dhcp/dhcpd.conf br100
## Run tftp server
in.tftpd --lsten --secure /tftpboot
qemu-system-x86_64 -m 1024 -smp cores=4,threads=1,sockets=2 \
-numa node,nodeid=0,cpus=0-3 \
-numa node,nodeid=1,cpus=4-7 \
-option-rom /usr/share/qemu/pxe-e1000.rom \
-net bridge,br=br100 -net nic,macaddr=52:54:00:12:34:56 \
-display curses
PXEブートファイル
上述したpxeboot
ファイルはsrc/boot/pxeboot.S
ファイルをコンパイルしたものです。ビルドルールはsrc/boot/Makefile
のpxeboot
を参照してください。
まず,テストのためにPXEブートの管理データ構造などは無視し,MBRのときと同様にメッセージを表示するだけのコードを書きます。以下のファイルは,Welcome to advos
と表示するための src/boot/pxeboot.S
です。Day 2のsrc/boot/mbr.S
とほとんど同じです。エントリポイントはpxeboot
にしました(src/boot/pxe.ld
で指定)。
#define VGA_TEXT_COLOR_80x25 0x03
.globl pxeboot
.text
.code16
/* Entry point for the PXE boot
* %cs:%ip is 0x0000:0x7c000
* %es:%bx points to the PXENV+ structure
* %ss:%sp points to a valid stack (at least 1.5 KiB available)
* %ss:(%sp+4) points to the !PXE structure, if available
*/
pxeboot:
cld /* Clear direction flag (inc di/si for str ops) */
cli
/* Setup the stack (below $start = 0x7c00) */
xorw %ax,%ax
movw %ax,%ss
movw $0x7c00,%sp
/* Reset data segment registers */
movw %ax,%ds
movw %ax,%es
sti
/* Set the video mode to 16-bit color text mode */
movb $VGA_TEXT_COLOR_80x25,%al
movb $0x00,%ah
int $0x10
movw $msg_welcome,%ax
movw %ax,%si
call putstr
halt:
hlt
jmp halt
/* Display a null-terminated string
* Parameters: %ds:%(si)
* Return value: %ax
* Preserved Registers:
*/
putstr:
putstr.load:
lodsb /* Load %ds:(%si) to %al, then incl %si */
testb %al,%al
jnz putstr.putc
ret
putstr.putc:
call putc
jmp putstr
putc:
pushw %bx
movw $0x7,%bx
movb $0xe,%ah
int $0x10
popw %bx
ret
/* Data segment */
.data
msg_welcome:
.asciz "Welcome to advos"
$ docker-compose build
$ docker-compose run qemu-pxe
を実行すると,
Welcome to advos
と表示されると思います。
今日のまとめと明日の予定
今日はPXEブートを実装するための環境構築と簡単なコードを実装しました。明日はPXEブートからカーネル読み込み,実行しようと思います。