2014年12月25日木曜日

ブートローダーは4行で実装される

この記事はUEFI Advent Calender自作OS Advent Calenderのためにかかれました。
22日(+3日)の記事です。

遅れてもうしわけないです。

ブートローダー

まず、自作OSにはブートローダーによってカーネルをロードしなければはじまりません。
特段複雑なローダーがなくとも、たとえばBIOSから直接起動するのなら
そのための形式で用意せねばなりません。
しかし、これが自作の入口としてかなりの関門になってしまっているのではないでしょうか。

なので自作OSからはちょっと離れますが、ブートローダーについて書きます。

UEFI

ということで、UEFI Appsとしてブートローダーを実装する話です。
まず、UEFIとは何かについてはUEFI Advent Calender 1日目の記事を、
そしてこの作業の前知識としてUEFI Advent Calender 12日目の記事を参照していただけると良いです。

一応ざっとUEFIについて触れておくと、
BIOSの代替規格のファームウェアで、多機能かつ高機能。また、可搬性も考えられています。

UEFIのファームウェア上で直接動作できるUEFI Appを記述するには、
tianocoreがBSDLで公開しているEDKというツールキットか、
Gnu toolchainと合わせて使えるgnu-efiというBSDLのライブラリを用います。
EDKでも一応gcc等が使えます。

EDKは、パッケージとして豊富にライブラリが揃っており、libcやbsd socket、pythonがつかえます。
gnu-efiは、Linux/OS Xの上で開発するにはEDKより手軽にはじめる事ができます。

UEFI AppはWindows同様PE形式のバイナリなので、
実はEDKやgnu-efiがなくとも*.dllなバイナリ(つまりrelocatableでsharedなPEバイナリ)を作成してから、objcopyコマンドとかで必要な部分だけ取り出して拡張子がefiのファイルにするとかでも一応動作できるバイナリを作る事は可能です(が、面倒です)

UEFIのAPIであるProtocolは初期状態でもFile I/OやGraphicsの処理まで揃っていますが、
自分でUEFI Driverを実装してこのドライバをロードすればProtocolを増やせます。
今回はDriverは実装しません。

実装

さて、そしてUEFI Appとして実装されたブートローダーがこちらになります。(料理番組風)
後でコードの説明をするので、最初は読み飛ばしても結構です。

いきなりコード貼ってしまいましたが、そんなにコード量はないですね。

Protocol

さて、UEFI Appですが、uefi_mainというエントリポイントが
EFI_IMAGE_HANDLE、EFI_SYSTEM_TABLEという引数をとって起動していますね。
InitializeLibを行なっているのは、gnu-efi独特の作法です。

その後早速Print();なんてやってますが、これでいきなり文字列出力ができてしまいます。
単純ですね。

その次からが、肝心のブートローダーです。

UEFIのAPIは、ただ関数コールすれば良いものではありません。
全てのProtocolには、それぞれの構造体が定義されています。
BIOSメーカーでありUEFIコンソーシアムの参加企業でもあるPhoenixのwikiに、
構造体の一覧が見やすく書いてあるのでこれを見ると良いです。

UEFIのProtocolは、まず使いたいProtocolと同名の型の構造体を宣言し、
OpenProtocol();にその構造体と、使いたいProtocolのGUIDと、いくつかのオプションを渡すことで、
渡した構造体のメンバに欲しい情報であったりとか、必要な関数ポインタが代入されます。

そこで、その情報を使うなり、関数ポインタから関数を呼ぶなりして、Protocolは使われます。

しかし、OpenProtocl();自体もある種のProtocolの関数であるため、そのような関数がグローバルに
宣言されているわけではないです。

ではどうやってProtocolを呼ぶかというと、UEFI Appの起動時に
BSという大域変数(EDKの場合はgBS)にEFI_BOOT_SERVICEのProtocol構造体に
必要な情報が詰められて最初から使えるようになっています。
よって、BS->OpenProtocol();のようにして、Protocolを呼び出します。

このブートローダーのコードでは、Print();の次にuefi_call_wrapper();という関数の
第一引数にBS->OpenProtocolを渡していますが、
これはUEFI Protocolの呼び出しが一般的なC言語の関数呼び出し(cdecl規約)ではなく
Win32API同様のstdcall規約を用いているためのwrapperです。

uefi_call_wrapper(つかいたいProtocolの関数ポインタ, その関数の引数の数, その関数への引数);
という使い方になります。

ブートローダー仕組み

では、ブートローダーの本格的な部分である17行目以降から解説します。

まず、エントリポイントの引数であったEFI_IMAGE_HANDLE型のImageHandleを使って、
EFI_LOADED_IMAGE_PROTOCOLを構造体LoadedImageParentに入れています。

このIMAGEというのは、実行メモリイメージの事(だと思われます)
ImageHandleについては実行している自分自身のハンドルになります。

LOADED_IMAGE_PROTOCOLというのは、自分自身やBS->LoadImage()によって
既にメモリ上にロード済みのイメージに対して情報を得る事ができます。

続く23行目では先程入手した自分自身のハンドルのDevicePathを使って、
\vmlinuzというファイルパスのファイルをEFI_DEVICE_PATH型の変数に代入しています。

これはどういうことかというと、
自分自身はブートローダーなので、かならずEFIのシステムパーティションに置かれています。
このパーティションはHDDの先頭のパーティションでFAT32です。
なので、自分自身のDevicePathはこのシステムパーティションのDeviceそのものを指す事になります。
更に、\vmlinuzということなので、このシステムパーティションのroot directoryにあるvmlinuzファイルについてのDevicePathをPathという変数に格納した事になります。
このDevicePathはブートローダーがカーネルをロードするのに用います。

29行目のBS->LoadImage()で呼び出しているのがそれです。これでカーネルはメモリに展開されています。

46行目のBS->StartImage()でこのロードされたイメージを起動するだけで、カーネルは起動されます。

29行目と46行目の間の処理は、ロードしたカーネルイメージについてLOADED_IMAGE_PROTOCOLを手に入れ、LoadOptionsというメンバに起動オプションを
指定することで、カーネルコマンドラインを指定しています。

自作OSをつくる上では、エラー処理や後処理を省くと、
17行目 23行目 29行目 46行目
この4行の処理のみでブートローダーは完成してしまっています。

さらに、USBメモリなどでEFIシステムパーティションのみで完結させる構成のディレクトリに
このようなブートローダーと自作カーネルとあと必要なバイナリや設定ファイルを適当に配置して
しまうだけで、おそらくさくっと自作OSの入口に立つ事が可能でしょう。



と、いうことで、自作OSをしたい方は是非、UEFIを前提に考えてみてください!

なお、このブートローダーでLinuxカーネルを起動してみたい場合、
EFIシステムパーティションのroot directoryにvmlinuzとrenameされたカーネルを配置し、
11行目のカーネルコマンドラインを自分の環境に合わせて書きなおしてください。
また、initrdについては度外視しているので、カーネルにroot partitionのファイルシステムのドライバを組み込んでおかないと、起動時にroot partitionがmountできずカーネルパニックを起こします。

このブートローダー自体は、EFIシステムパーティションの好きな所において、
bcdeditなりblessなりefibootmgrなりでUEFIにそのPathを登録するか、
UEFI Shellから直接このバイナリをたたいてやってください。

上記コードのMakefileも含めたリポジトリはこちらになります。
https://github.com/orumin/SimpleMyLoader

2014年12月20日土曜日

UEFIアプリケーションのはじめかた

UEFI Advent Calender 12日目だったoruminです
盛大に遅刻かましてもはや遅刻とさえ言えない感じです。

UEFIのアプリケーション、まずどう作るかわからない方も多いかと思います。

まず、UEFIが実行できる実行形式について、これはWindowsと同じPEバイナリです。

また、EDKのようなツールキットを用いる必要がありますが、
今回はgnu-efiで説明します。

gnu-efiとは、BSDLなEFIアプリケーション開発用ライブラリです。
LinuxやBSDでの開発に親和性が高いと思われます。

これは最近だとaptやpacmanでそのままバイナリインストールが可能です。

そして、次にこのようなファイルを用意します




前者がUEFIでのHello, World!です。efi_mainがエントリポイント、
ImageHandleもSystemTableもUEFIのAPI(Protocol)を呼び出し、使うのに必要なパラメータとなります。
今回は、gnu-efiの初期化をしてから文字列を出力するだけなので特に使用していません。
デバッグ文字列出力は
SystemTable->ConOut->OutputString(SystemTable->ConOut, L"Hello, EFI!\n");
でも良いのですが。

後者のMakefileについては、書いてある通りです。
ライブラリとインクルードファイルの指定、
それにリンカスクリプトやスタートアップルーチンはgnu-efiのものを使うように指定。
shared objectな実行ファイルを作ってから、
objcopyコマンドで必要な部分を抜き出す(ELFヘッダは要らないので)
という流れになります。
CFLAGSについては、色々ごちゃっと指定してますが、
まず必須なのは-fPICと-fshort-wchar、そしてx86_64であれば-DEFI_FUNCTION_WRAPPERだけで、あとのは特段必ず指定しないといけないものではないです。

さて、これでmakeをしますと、hello.efiが作成されるので、

あとはEFIシステムパーティション直下にでも置いてあげて、起動時にUEFI Shellを立ち上げてから
hello.efi
とコマンドを入力してみてください。Hello, EFI!と文字列が出たら成功です!

UEFIの開発やProtocolについては、1日目の記事の最後に書いたドキュメントがオススメですが、
リファレンスよりも手軽にProtocolの型や名前だけ把握するには、
Phoenixのwikiが便利です。

2014年12月10日水曜日

@toshi_aにTLを大破させられて1年がたちました……

mikutter Advent Calender 9日目の記事です.

みなさん,去年のAdventCalenderは覚えておいででしょうか.きっと,
全部覚えてる人は少ないでしょう.

@toshi_a被害者の会

http://orumin.blogspot.jp/2013/12/toshia.html

mikutter Advent Calender 2013 8日目の記事の様子です.
TLを壊されました.そしてささやかな復讐をしました.

そう,あれから1年がたったのです.

何か記念(怒)に書こうと思ったのですがネタが特に思い付きませんでした.
適当に今年の振り返りでも書きます.

あれから色々ありました.

まずなぜか@toshi_aさんにフォローされました.

そして,
とりあえず私がひっこしたおかげでイベントとかに参加しやすくなったので,


なんか貰ったんですね,初夏の京都のOSCで.
おかげで部屋に彩りが増えました.
ありがとうございました

とくにそれからはmikutter関連でなにかあったりはしてないのですが,
mikutterそのものは3系になりさらに進化して良いです.
ArchLinuxユーザーの私は新しいmikutterをすぐに使えてとても有り難いですね.
ということでArchをつかおう!

あれ,なんかArchの宣伝になってしまった,まあいいや,oruminでした.

2014年12月3日水曜日

Ruby on OSv

これはOSv Advent Calender 2+1日目の記事です.
グアムのハワイのワイキキビーチで書き上げたからまだ12/2という"てい"でよろしくおねがいします.



OSvのCRuby移植担当してたoruminです.
OSv1日目の記事@syuu1228さんが説明されていた通り,現在OSvで
複数の言語ランタイム/アプライアンスが動作します.

CRubyも移植は一応の完成を見ることができ,動作させる事が可能です.
今回は動作方法について書きます.

じゅんび

まずはリポジトリのcloneから.
$ git clone http://github.com/cloudius-systems/osv.git osv
その後,忘れずsubmoduleをinit & updateしましょう
$ cd osv && git submodule --init update
ビルドのためには,いくつかパッケージが必要です.
https://github.com/cloudius-systems/osv/blob/master/README.md
に必要なパッケージはFedora,Debian,Archについて記述されています.

しかし,Fedora20を用意できるのであれば,osvのディレクトリで,
$ sudo ./script/setup.py
と実行すれば必要なパッケージを全て準備してくれます.

ビルド 

ビルドは単純です.
$ make -j`nprocs` image=ruby-example
このようにしたら,あとはビルドが終わるまで待機です.
なお,rubyのビルド中にエラーで失敗する時は,-jフラグを外してもう一度やってみてください.
まだrubyとosv kernelの並列ビルドがうまくいかない時があるようです(すみません)

実行

次のスクリプトでOSvは実行できます
$ ./script/run.py
どうでしょうか? うまくいけば
OSv v0.16-814d434
eth0: 192.168.1.89
Hello, world!
のように表示されます.
また,
$ ./script/run.py -e "/ruby.so /irb"
のように起動引数を手動で設定すれば,irbが実行できたりします.
apps/ruby-example/sample-codes/に,このビルドされたイメージファイルで他に
実行できるサンプルのrubyスクリプトもあるので,適当に指定してみたりして試してみてください.

2014年12月1日月曜日

What's UEFI

UEFI Advent Calender一日目,oruminです.

初っ端なのでまずUEFIとは何かについて書こうと思います.

・UEFIとは?

UEFIとは,ファームウェアの一種です.
一般的なPCはBIOSからOSを起動している事はご存知だと思われますが,
実はBIOSは最早過去の遺物となりました.

BIOSの代替として2000年頃からIntelが開発していたEFIは,
多くの企業とコンソーシアムを立ち上げ,現在UEFIとして規格が策定されています.
2010年頃からはIntelのマザーボードを皮切りとして一般向けにも採用され,
現在市場にあるPCのほぼ全てがUEFIでしょう.
BIOSの設定画面だと思っているそれは最早UEFIです


・BIOSとの違いは?

大きな違いはデファクトスタンダードとしてなんとなしに採用されてきたBIOSと違い,
多くの企業のコンセサスの元策定された規格が存在し,閲覧が可能である事です.

この規格は,ブートローダや診断ツールといったものの開発が容易であるよう,
プログラミングのためのインターフェイス,APIが規定され公開されています.
これを扱うためのSDKが公開されているため,C言語やPythonといったもので
診断ツール等を記述可能になっています.

なんと,UEFIのAPIを用いて記述したアプリケーションプロラムは,OSが起動する前の段階(pre-OS Environment)でUEFI内蔵のShellから起動する事ができます.

また,IBM-PCの時に作られたBIOSは16bit Intel CPU前提のアークテクチャであるため,
多くの制限がありましたが,UEFIの場合はこの制限が無くなり,CPUのアーキテクチャに非依存です.Intel CPUのReal modeでないとBIOSから起動できない,などという事がないためブートローダーも16bitでなく32bitプロセッサなら32bit,64bitプロセッサなら64bitの命令がつかえるため,メモリも潤沢に使えます.

現在はx86,x86_64,AArch64のUEFI実装が存在します.

そして,一番のBIOSとの違いはブートシーケンスにあるのではないでしょうか.

・UEFIのブートシーケンス

BIOSは,当初のその制約から,ディスク先頭の第一セクタしかロードできませんでした.
よって,現在はその第一セクタ ― ディスク先頭512バイトの領域に
ブートローダーとパーティションテーブル(MBR)を格納し,そのブートローダーからOSを起動するという仕組みが一般的でしょう.
このブートローダーがMBMであればさらにパーティション毎の先頭のセクタ(PBR)のブートローダーを起動し,そのブートローダーが宜しくやってくれるというわけです.

この第一セクタ,つまりブートセクタをつかう方法は今や旧い方法となりました.
このブートセクタに置かれるブートローダーは容量の制約上技巧的な物にならざるを得ませんし,普通のファイルの容易に書き込みや入れ換えができるものでもないです.
多段階的な方法を取らねばマルチブートできないのもBIOSの制約から来るものです.

UEFIでは,ディスクの第一パーティションをFAT32でフォーマットし,これをシステムパーティションと呼びます.このシステムパーティションにUEFIのSDKで記述したアプリケーション,ブートローダーを配置します.
UEFIのアプリケーションバイナリはWindowsのEXE同様,PEバイナリとなります.

さて,このファイルをどう起動するよう指定するかというと,
UEFIのファームウェアそのものにブートマネージャーというマルチブートの際のOS選択画面の機能のようなものが備わってます.
このブートマネージャーに,ブートマネージャーに表示するエントリ名と,ブートローダーやUEFIアプリケーションのシステムパーティションでのパスを指定し,登録する事が可能です.
よって,OS毎に専用のローダーをUEFI SDKで記述し,それぞれのローダーをこのUEFIファームウェアのブートマネージャーに登録するだけでマルチブートが可能となるのです.

MBMやGRUBのような巨大なマルチブートのための"からくり"はもう必要ないのかもしれません.

もう一つ,ここで重要なのは,パーティションテーブルはMBRではなくなり,GPT形式となります.これは,パーティション数が最大128となり,1つのパーティションも2TiBの壁を越えて設定できます.パーティションの管理はGUIDを用いて行なわれます.



以上,軽くBIOSのおさらいも兼ねてUEFIとの違いに触れました.
UEFIのもう一つのBIOSとの違いとしてセキュリティ面で大幅に変化がありましたが,
これはまた別の機会に触れたいと思います.
また,度々文中に記載しましたがUEFIのSDK(EDK)の存在によりブートローダーの
作成は容易化しました.自作OSを作成する場合にもこれは大きなアドバンテージではないでしょうか.このEDKについても別の機会に触れたいと思います.

最後に,UEFIのドキュメントを紹介します.
まずUEFIの規格書は最大のドキュメントでしょう.
http://www.uefi.org/specifications

また,Intelが出しているペーパーブックは
UEFIアプリケーションの開発等で役立つほぼ唯一のドキュメントです

2014年11月27日木曜日

オーディオアンプを作ってみました。

初アンプ自作。

ぺるけさんのFET作動式ヘッドフォンアンプを作成。


ぺるけさんの作例をそのまま使わせてもらってます。
ただ、シャーシの穴開けをボール盤使える環境にある友人に依頼した時、依頼ミスで左右が反転したのでラグの位置等もそのようになってます。おかげで音量ツマミが左側になりましたが些細な事でしょう。
結局他にいくつか穴を開ける必要出てきて結局電動ドリル買いました。
ぺるけさんはオーディオクラスでないパーツを使っておられましたが、私はなんとなーくコンデンサを東信工業のUTSJにしてみました。初アンプ自作ですしオーディオに対して無知なのでこのコンデンサにしてどうとかあんまりわからんのですが。

一発で音が出て感動してますし、個人的には結構良いように思えます。
あとはボリューム抵抗のシャフトを切る道具もってなくてツマミをつけず裸のままにしてるのをなんとかしたいです

電気式華憐音楽集団2nd Live "Gig Detonator"

表題通りのライヴに参加してきました。




グッズ類

BLACK BOX、white box、Red Box

ライヴ、あまり行ったことなかったのですがなかなか楽しめました。
良いものですね。