Mercurial > hg > Papers > 2024 > nana-thesis
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}