view final/text/chapter3.tex @ 0:d31d54a41664 default tip

add thesis
author nana <e205729@ie.u-ryukyu.ac.jp>
date Wed, 31 Jan 2024 19:17:13 +0900
parents
children
line wrap: on
line source

\chapter{UEFIを用いたxv6のブート}


\section{基礎概念}

\subsection{xv6}
xv6\cite{xv6}は,MIT(マサチューセッツ工科大学)によって開発された,教育用のUnix系OSである.OSの機能には,プロセス、仮想メモリ、カーネルとユーザ の分離、割り込み、ファイルシステムなどの基本的な Unix の構造を持つが,シンプルな実装で学習しやすい.様々なプラットフォーム向けに書き換えられたものが存在しており,本研究ではAArch64に対応したxv6を使用する.

\subsection{ARM64/AAsrch64}
AArch64\cite{AArch64}は,Arm Holdingsによって設計された64bitプロセッサアーキテクチャである.AArch64はARMv8-Aアーキテクチャとも呼ばれ.32bit版(ARMv7-A以前)と比較して,CPUの特権モードがシンプルになり,マルチタスクに必要な割り込みやコンテキスト退避、メモリ保護に必要なMMUなどの機能が向上している.

\subsection{QEMU(Quick Emulator)}
QEMU\cite{QEMU}は,Fabrice Bellard が中心となって開発している,ハードウェアを仮想化したエミュレーターである.幅広いプラットフォームに対応しており,ホストマシンと異なるアーキテクチャのプログラムをエミュレートできる.本研究では,AArch64をターゲットとしたプログラムのクロスコンパイルと,UEFIのエミュレートに使用する.

\subsection{UEFI}
UEFI(Unified Extensible Firmware Interface)\cite{UEFI}は、コンピュータシステムの起動と初期化を担当するファームウェアの1種であり、Intel社のBIOSの後継として設計されたものである.64bitアーキテクチャに対応したことで,大容量のメモリやストレージを扱うことが可能になった.特定のプロセッサに依存せず,C言語を用いてUEFIアプリケーションを実装することができるため,制約の多いBIOSに代わって,近年のOS開発では主流のファームウェアとなっている.

\subsection{singulariy}
Singularity\cite{singularity}は、主に科学計算やハイパフォーマンスコンピューティング(HPC)の環境で利用されているコンテナベースの仮想化ソフトウェアである.Dockerとの差異として,ホストマシンのリソースを直接利用できる他,コンテナ内でもユーザー権限を引き継ぐことができる.コンテナは独自のSIFフォーマットに集約されるため,ファイルコピーによる環境の共有・再現が可能である.

\subsection{GCC}
GCC(GNU Compiler Collection)\cite{GCC}とは,GNUプロジェクトが開発および配布しているコンパイラ群である.C以外の言語もサポートしており,対応しているアーキテクチャも多い.本研究では,xv6本体のコンパイルと,UEFIアプリケーションをaarch64向けにコンパイルするために使用する.

\subsection{gnu-efi}
gnu-efi\cite{gnu-efi}は,UEFIアプリケーションをコンパイルするための,軽量なライブラリ・ヘッダ群である.同じUEFIのツールセットであるEDK2よりも軽量で,実装からコンパイルまでの作業が比較的簡易である.GCCと併用することで,C言語で記述したUEFIアプリケーションをコンパイルすることができる.

\subsection{EDK2}
EDK2\cite{EDK2}は,TianoCoreプロジェクトによって管理されているオープンソースのUEFI開発キットである.gnu-efiと比較してやや複雑なフレームワークを持っているが,UEFIアプリケーションの開発だけではなく,UEFI本体をビルドすることもできる.

\subsection{clang}
Clang\cite{Clang}は、プログラミング言語のC、C++、Objective-C、Objective-C++ 向けのコンパイラフロントエンドである。GCCを置き換えることを目標にしているため,大部分でGCCと互換性がある.Gears OSのコンパイラは,Clamgをベースに開発されている.

\subsection{CMake}
CMake\cite{Cmake}とは、コンパイラに依存しないビルド自動化のためのフリーソフトウェアであり、様々なオペレーティングシステムで動作させることができる.複数の言語とビルドターゲットに対応しており,CMakeLists.txtと呼ばれるテキストファイルでプロジェクト構成を記述し、それを元にビルド設定を生成する.

\subsection{GDB}
GNUデバッガ(GDB)\cite{GDB}は、GNUソフトウェア・システムで動く標準のデバッガである。プログラムの実行の変更や追跡機能を持ち,プログラム内部の変数の値を修正して状態を監視したり、プログラムの通常の動作とは別に関数を呼び出すことができる.


\subsection{RaspBerry Pi Model 3B+}
RaspBerry Pi \cite{rpi}は,英国のRaspberry Pi Foundationによって開発された,Armプロセッサを搭載した教育用シングルボードコンピュータである.本研究では,64bitのArmプロセッサであるAArch64を搭載したRaspBerry Pi 3B+を使用する.



\section{Geras OSの開発環境について}

過去に行われたGears OSに関する実装やOSのソースコードは,当研究室で使用しているサーバーのdalmoreと,fireflyのmercurial repositoryで管理されている.ソフトウェア開発をチームで行っている場合,開発環境は容易に共有・再現できるように構築することが望ましい.
当研究室では,コンテナ仮想化プラットフォームの一種であるsingularity\cite{singularity}を用いて開発環境を構築している.singularityで作成したコンテナは複製して共有することが容易であり,複製した状態から開発を引き継ぐことが可能である.

本研究では,dalmore上にsingularityのコンテナを作成し,仮想環境を用いてブートローダの実装とエミュレートを行う.実装したソースコード類の管理には,分散型バージョン管理システムのMercurial\cite{Mercurial}を用いる.

\section{singularityによるコンテナの作成}\label{container}

singularityでは,表\ref{program1}のように定義ファイルを作成することで,容易にコンテナのbuildを行うことができる.

\lstinputlisting[caption = xv6-aauefi.def ,label = program1]{code/xv6-aauefi.def}


コンテナのbuildは,以下のコマンドで行う.

\begin{lstlisting}[caption=singularity-build-command,label=fuga]
singularity build -f xv6-aarch64.sif xv6-aarch64.def
\end{lstlisting}

また,以下のようにsingularityの起動コマンドをシェルスクリプトに記述しておくと,コンテナの起動コマンドを省略できる.

\lstinputlisting[caption = run.sh ,label = program2]{code/run.sh}

シェルスクリプトの詳細を説明する.

\begin{quote}
 \begin{itemize}
   \item singularity shell --shell /bin/zsh は,singularityをzshを使って起動するオプションである.
   \item -B(バインド) の後に記述されたディレクトリは,コンテナの中でマウントされる.作業ディレクトリなどをバインドする.
 \end{itemize}
\end{quote}


\section{QEMUを用いたUEFIのエミュレート}

\subsection{UEFIのbuild}\label{uefi-build}

Gears OSは,AArch64プロセッサをターゲットにOSの書き換えを行う予定である.
当研究室で所有しているRaspberri Pi 3B+\cite{rpi}は,Armの64bitアーキテクチャを持つAArch64プロセッサが搭載されているため,UEFIを搭載することでGears OSのテストマシンとして扱える.実機でのテストを想定して,今回はRaspberry Pi用にビルド済みのUEFIを用いる.

\subsection{QEMUでUEFIのエミュレート}

表\ref{container}のコマンドで作成したコンテナを起動し,UEFIのFDファイルをQEMUで起動する.

以下の表\ref{run-qemu}のように,シェルスクリプトを作成すると起動が容易である.

\begin{lstlisting}[caption=run-qemu.sh,label=run-qemu]
qemu-system-aarch64 -m 128 \
    -M virt \
    -bios ./QEMU_EFI_DBG.fd \
    -drive format=raw,file=fat:rw:gnu-efi-3.0.12/arm/apps \
    -net none \
    -nographic
\end{lstlisting}

QEMUの起動オプションについて説明する.\\

\begin{quote}
 \begin{itemize}
  \item -bios ./ここにbuildしたUEFIのFDファイルのパスを記述する.
  \item -drive format=raw,file=fat:rw:\{directory\}で指定されているフォルダが,FATファイルシステムとしてマウントされる.
 \end{itemize}
\end{quote}

FATファイルシステム下にEFIアプリケーションを配置すると,UEFiからファイルシステムとEFIアプリケーションが認識可能になる.


\section{UEFIアプリケーションとbootloader}

\subsection{UEFIアプリケーションの作成とコンパイル}

UEFIアプリケーションは,EDK2\cite{EDK2}やgnu-efi\cite{gnu-efi}のような,UEFIアプリケーションを記述するためのライブラリ群とC言語を用いて記述する.記述したアプリケーションは,ターゲットとなるアーキテクチャに向けてクロスコンパイルする必要がある.C言語のコンパイラにはGCC\cite{GCC}とClang\cite{Clang}があるが,今回ブートするxv6-aarch64\cite{xv6-aarch64}はGCCを利用してコンパイルされる.
ブートローダーも同様に,GCC とgnu-efiを利用して記述する.

\subsection{OSがbootするまでの流れ}

OSがUEFIを用いてbootするまでの流れを,図\ref{fig:os-boot}に示す.
\begin{figure}[H]
  \begin{center}
   \includegraphics[width=60mm]{./figs/boot.pdf}
   \caption[OSがbootするまでの流れ]{OSがbootするまでの流れ}
   \label{fig:os-boot}
   \end{center}
\end{figure}

UEFIを起動した後,ブートローダーによってkernelがロードされる.UEFIのブートサービスを終了してkernelのエントリポイントにジャンプすると,kernelのエントリポイントにある関数の処理が始まる.xv6-aarch64では,entry.Sが始めに実行される.
AArch64は図\ref{fig:EL}のように実行権限を分離していて,UEFIを立ち上げる際はEL1,またはEL2で起動する.

\begin{figure}[H]
  \begin{center}
   \includegraphics[width=40mm]{./figs/EL.pdf}
   \caption[AArch64のException Level]{aarch64のException Level}
   \label{fig:EL}
   \end{center}
\end{figure}

\subsection{bootloaderの実装}

xv6-AArch64は,コンパイルすることでバイナリファイルが生成される.
ブートローダーで以下の処理を実装することで,kernelのバイナリを直接ロードする.

\begin{quote}
 \begin{itemize}
  \item kernelのFileをロードする
  \item エントリポイントを取得して,アドレスにjumpする
  \item boot Serviceを終了する
 \end{itemize}
\end{quote}

以下が実装されたブートローダである.

\lstinputlisting[caption = bootloader.c ,label = program3]{code/bootloader.c}

ブートローダーの処理を説明する.\\

53行目から始まるEFI\_STATUS LoadFileは,UEFIによって提供されている,ファイルシステムをロードするためのプロトコルである.69行目からは,UEFIで認識されているファイルシステムデバイスをforループで開き,ファイルが存在する場合はロードされる.93行目のFile\-\>GetInfoでファイルサイズの情報を取得し,104行目のBS\-\>AllocatePagesで,読み込むファイルのサイズに対応したメモリを割り当てている.その後ファイルのヘッダを読み取り,バイナリを取得したらファイル操作を終了する.

172行目からefi\_main関数が始まり,LoadFileを使用して,指定されたパスのkernelファイルをロードする.199行目では,ハードウェア構成を管理するACPIテーブルからアドレス\(RSDP\)を取得している.225行後は,ロードされたkernelと,kernelのヘッダ情報であるdumpを表示するコードである.287行目でブートサービスを終了し,インラインアセンブリを使用して、カーネルのエントリーポイント0x40000000にジャンプする.299行目のポインタのEntryによって,kernelのエントリーポイントにある関数が呼び出され,kernelが実行される.\\

UEFI shellでbootloader.cを実行すると,xv6-aarch64のブートを確認できた.

\begin{figure}[H]
  \begin{center}
   \includegraphics[width=100mm]{./figs/xv6-aarch64.pdf}
   \caption[xv6-aarch64のブート]{xv6-aarch64のブート}
   \label{fig:xv6-aarch64}
   \end{center}
\end{figure}


\subsection{GBDを用いたデバッグ}

今回の実装では,gdb-murtiarchを使用してブートローダーのデバッグを行った.AArch64などのマルチアーキテクチャに対応している.

GDBのデバッグには,ターミナルの画面を2つ使用する.以下の図\ref{fig:gdb}は,左がQEMU+UEFIのコンソール画面で,右がGDBのデバッグコンソールである.

\begin{figure}[H]
  \begin{center}
   \includegraphics[width=130mm]{./figs/gdb.pdf}
   \caption[GDBによるデバッグ]{qemuとgdbによるデバッグ}
   \label{fig:gdb}
   \end{center}
\end{figure}

今回は,GDBを手動で接続する..gdbを記述する方法もある.
\begin{lstlisting}[caption=gdb-remote,label=gdb-remote]
(gdb) target remote 127.0.0.1:27698
\end{lstlisting}

terget remoteコマンドを用いてGDBを接続する.接続先のtcpの値は,左のQEMU+UEFIコンソールの,QEMUオプションの最後に見つけることができる.

GDBは,以下のコマンドでブレークポイントを設置できる.今回はkernelが格納されていることを確認するため,エントリーポイントである0x40000000にブレークポイントを置く.

\begin{lstlisting}[caption=gdb-breakp,label=gdb-breakp]
(gdb) b *0x40000000
\end{lstlisting}

以下のように入力すると,0x40000000番地から20個先のメモリアドレスの中を確認することができる.

\begin{lstlisting}[caption=gdb-remote,label=gdb-remote]
(gdb) x/20i $pc
\end{lstlisting}

メモリアドレスに配置されているアセンブラを確認できる.

\begin{figure}[H]
  \begin{center}
   \includegraphics[width=60mm]{./figs/gdb2.pdf}
   \caption[ブレークポイントの設定]{メモリダンプ}
   \label{fig:gdb2}
   \end{center}
\end{figure}

\subsection{clangでのコンパイル}
本研究で用いたxv6-aarch64はGCCでコンパイルされるが,Gears OSはClang/LLVMで実装されている.今後Gears OSを書き換えるにあたり,コンパイルはCbClang + cmakeで行う必要がある.
CbClangは,ClangとLLVMをベースに,CbCのコンパイラとして開発されている.
ClangはGCCの大部分に互換性があるため,Makefileとkernelのソースコードの一部を書き換えることで,Clangを用いてxv6-aarch64をコンパイルできた.\\

Clangで変更したMakefileの部分を,以下に示す.

\begin{lstlisting}[caption=clang-Makefile,label=clang]
CC = clang

CFLAGS = -Wall -Werror -Os -g -fno-omit-frame-pointer -target aarch64-none-linux-gnu

ASFLAGS = -Og -ggdb -target aarch64-none-linux-gnu -MD -I.
\end{lstlisting}

\begin{lstlisting}[caption=gcc-Makefile,label=gcc]
CC = $(TOOLPREFIX)gcc

CFLAGS = -Wall -Werror -Os -g -fno-omit-frame-pointer -mcpu=cortex-a72+nofp

ASFLAGS = -Og -ggdb -mcpu=cortex-a72 -MD -I.
\end{lstlisting}

ASFLAGS = -mcpu=cortex-a72,CFLAGS = -mcpu=cortex-a72+nofpはGNUのライブラリに依存しているため,-target aarch64-none-linux-gnuに置き換える.

\begin{lstlisting}[caption=clang-proc.c,label=clang-proc]
[0] = "unused",
[1] = "sleep ",
[2] = "runble",
[3] = "run   ",
[4] = "zombie"
\end{lstlisting}

\begin{lstlisting}[caption=gcc-proc.c,label=gcc-proc]
[UNUSED] = "unused",
[SLEEPING] = "sleep ",
[RUNNABLE] = "runble",
[RUNNING] =  "run   ",
[ZOMBIE] =  "zombie"
\end{lstlisting}



\begin{lstlisting}[caption=clang-swith.S,label=clang-swith]
 stp x10, x18, [x9, #16 * 0]
 ldp x10, x18, [x9, #16 * 0]
\end{lstlisting}

\begin{lstlisting}[caption=gcc-swith.S,label=gcc-swith]
stp x10/*sp*/, x18, [x9, #16 * 0]
ldp x10/*sp*/, x18, [x9, #16 * 0
\end{lstlisting}



\begin{lstlisting}[caption=clang-syscall.c,label=clang-syscall]
  uint64 sys_fork(void);
  uint64 sys_exit(void);
  uint64 sys_wait(void);
  uint64 sys_pipe(void);
  uint64 sys_read(void);
  uint64 sys_kill(void);
  uint64 sys_exec(void);
  uint64 sys_fstat(void);
  uint64 sys_chdir(void);
  uint64 sys_dup(void);
  uint64 sys_getpid(void);
  uint64 sys_sbrk(void);
  uint64 sys_sleep(void);
  uint64 sys_uptime(void);
  uint64 sys_open(void);
  uint64 sys_write(void);
  uint64 sys_mknod(void);
  uint64 sys_unlink(void);
  uint64 sys_link(void);
  uint64 sys_mkdir(void);
  uint64 sys_close(void);
\end{lstlisting}

\begin{lstlisting}[caption=gcc-syscall.S,label=gcc-syscall]
  extern uint64 sys_chdir(void);
  extern uint64 sys_close(void);
  extern uint64 sys_dup(void);
  extern uint64 sys_exec(void);
  extern uint64 sys_exit(void);
  extern uint64 sys_fork(void);
  extern uint64 sys_fstat(void);
  extern uint64 sys_getpid(void);
  extern uint64 sys_kill(void);
  extern uint64 sys_link(void);
  extern uint64 sys_mkdir(void);
  extern uint64 sys_mknod(void);
  extern uint64 sys_open(void);
  extern uint64 sys_pipe(void);
  extern uint64 sys_read(void);
  extern uint64 sys_sbrk(void);
  extern uint64 sys_sleep(void);
  extern uint64 sys_unlink(void);
  extern uint64 sys_wait(void);
  extern uint64 sys_write(void);
  extern uint64 sys_uptime(void);
\end{lstlisting}

Clangでコンパイルしたxv6-aaarch64をQEMUで実行すると,xv6の起動を確認できた.

\begin{figure}[H]
  \begin{center}
   \includegraphics[width=100mm]{./figs/clang-xv6.pdf}
   \caption[clangでコンパイルしたxv6-aarch64]{clang-uefi}
   \label{fig:clang-uefi}
   \end{center}
\end{figure}