1.1K Views
January 11, 14
スライド概要
at C++勉強会 in 広島
サイボウズ・ラボ株式会社で教育向けのOSやCPU、コンパイラなどの研究開発をしています。
C++でできる!OS自作入門 OS入門へのいざない C++勉強会in広島 @uchan_nos
そもそもOSとは • 身近な例 Windows, OS X, Linux, iOS, Android • ハードウェアとアプリの橋渡しをするソフト • メモリ管理、ハードウェア制御、タスク管理、…
OS自作 #とは • Windowsみたいなソフトを自分で作る • 具体的にどうやって作るのか? ! • そんなあなたに『OS自作入門』
30日でできる!OS自作入門 30日! マルチタスク GUI
OS自作の楽しさ • 自分のコンピュータを支配している感覚 → ハードウェアを直接いじる • 普段使ってるOSの裏側を実体験できる → メモリ管理、タスク管理ってどうなってる? • 独自アイデアを盛り込んだOSの創造 → エミュレータOS、クラウドOS
OS自作の苦労 • 自分がOSなので、OSの助けを借りられない → main関数さえ自分で呼び出す • 画面表示も自分の責任 → 画面表示にバグがあると涙目 • エミュレータと実機での差 → エミュレータだと上手く動いたのに!
BITNOS • 私が作っているOS • NOS = Not Operating System OSぽい画面のジョークソフト (Akkie氏命名) • Bit NOS : ちょびっとだけNOS • 5回くらい作り直しているが、 まだ全く完成しない
OS起動プロセス OSってどうやって起動するの??
OSの起動 MBR読み込み MBR: Master Boot Record BIOS 先頭セクタ = MBR 0x7c00 0x7e00
OSの起動 MBRコピー 0x7a00 0x7c00 0x7e00 MBR MBR 自身をコピー& コピー先へジャンプ
OSの起動 パーティション解析 パーティション情報
OSの起動 PBL読み込み PBL: Partition Boot Loader MBR パーティション 先頭セクタ = PBL 0x7c00 0x7e00 MBR PBL
OSの起動 なぜMBRをコピーするか BIOSを模倣するため • パーティションがないメディア → PBLがディスク先頭 • BIOSは機械的にディスク先頭を0x7c00に読み込む • MBRがPBLを0x7c00に配置し、BIOSを模倣
OSの起動 PBLの仕事 • FATを解析 • "BITNOS.SYS"を探してロード • • ローダー+カーネル(2段階ロード) ロード先へジャンプ
OSの起動 • ローダーの仕事 BIOSを使う用事を済ます → 画面モード設定、キーボードLED状態取得 • プロテクトモード(32ビットモード)へ移行 • カーネル部分を、所定の位置へコピー → リンカへの指示と合わせる • カーネル(kmain)にジャンプ
カーネルの仕事 アプリ アプリ • ハードウェア初期化 カーネル ハードウェア • 割り込み管理 → キーボード/マウス、タイマ、ネットワーク • タスク管理 • メモリ管理
OS開発とC++
OS自作入門とC言語
•
『30日でできる!OS自作入門』はC & アセンブリ
•
C言語でキューの実装→だるい
int以外のキュー
struct FIFO32 {
int *buf;
int p, q, size, free, flags;
};
!
も欲しいのだが…
void fifo32_init(struct FIFO32 *fifo, int size, int *buf)
{
fifo->size = size;
fifo->buf = buf;
fifo->free = size;
fifo->flags = 0;
fifo->p = 0;
fifo->q = 0;
}
C++でOS開発 • 先人たち • BayOS : C++コンパイラだけで対応可能な機能だけ使用 • MonaOS : 例外、純粋仮想関数なども使用 • 「gccをg++に置き換えるだけで使える機能」は簡単 • でも、C++ならそれらしい機能使いたいよね! • 純粋仮想関数、例外、実行時型情報、new/delete
OS開発におけるC++の楽しさ • 『30日でできる!OS自作入門』のC言語コードを 改良していく楽しさ • • キューをクラスで実装し直すのが好き OS開発がオブジェクト指向言語でできる楽しさ • 継承を使って分かりやすいコード • 純粋仮想関数ひゃっはー
OS開発におけるC++の苦労 • • 『30日でできる!OS自作入門』の開発環境を流用できない • 自分でクロスコンパイラを用意 • 特に川合さん謹製リンカは.ctorsや.rodataに対応しない C++特有の機能への対応 • 純粋仮想関数、例外、実行時型情報、new/delete、グ ローバル変数コンストラクタなど
C++特有の機能への対応 • new • 純粋仮想関数 • グローバル変数コンストラクタ
C++特有の機能への対応 • new • 純粋仮想関数 • グローバル変数コンストラクタ
new演算子 • new演算子は 1)メモリを確保し、2)コンストラクタを呼ぶ • OSが空きメモリを探し、メモリを割り当てる • g++がコンストラクタ呼び出しコードを追加する
new演算子の実装 void* operator new(size_t size) { void* buf = my_malloc(size); return buf; } void* operator new[](size_t size) { void* buf = my_malloc(size); return buf; } カーネルの.cppに書いておく
メモリ割り当て - リンクリスト方式 0x1000 0x2000 size=0xFF8 true Header* current = (Header*)0x1000; Allocated Header* next = (Header*)( (uintptr_t)current + sizeof(Header) + current->size); size=0xAF8 false Released 0x2B00 struct Header { size_t size; bool is_allocated; };
配置new演算子 • new演算子は 1)メモリを確保し、2)そこでコンストラクタを呼ぶ • 配置new演算子は 1)指定されたメモリ領域でコンストラクタを呼ぶ • プログラマがメモリ領域を与える • g++がコンストラクタ呼び出しコードを追加する
配置new演算子の実装 普通のnew 配置new void* operator new(size_t size) { void* buf = my_malloc(size); return buf; } void* operator new(size_t size, void* buf) { return buf; }
配置newの使いドコロ 例えばこんな場面 ScreenRenderer* screen_renderer; ! void KernelMain(void) { BIOSで設定した画面解像度など取得; ! ! } if (24ビット色モード) { screen_renderer = new 24ビットレンダラ(解像度); } else if (32ビット色モード) { screen_renderer = new 32ビットレンダラ(解像度); } …
配置newの使いドコロ 例えばこんな場面 ScreenRenderer* screen_renderer; ! 様々なところで使う →グローバル変数 void KernelMain(void) { BIOSで設定した画面解像度など取得; ! ! } if (24ビット色モード) { screen_renderer = new 24ビットレンダラ(解像度); } else if (32ビット色モード) { screen_renderer = new 32ビットレンダラ(解像度); } …
配置newの使いドコロ 例えばこんな場面 ScreenRenderer* screen_renderer; ! 様々なところで使う →グローバル変数 void KernelMain(void) { BIOSで設定した画面解像度など取得; ! ! } if (24ビット色モード) { screen_renderer = new 24ビットレンダラ(解像度); } else if (32ビット色モード) { screen_renderer = new 32ビットレンダラ(解像度); } … でもインスタンスは 後で生成したい → newを使う
配置newの使いドコロ • 普通、newは新たなメモリを割り当てる (コンパイラがコンストラクタ呼び出しコードを自動で追加) • メモリ割り当てはOSの仕事 • 割り当て機能が無い段階でnewを使いたい! • → 配置new
配置newの使いドコロ class A { int val_; const char* str_; public: A() : val_(41), str_("foo") {} }; 41 確保した メモリ 41 "foo" "foo" new A(); char buf[32]; new(buf) A(); buf
配置newの使いドコロ 配置newで無事解決! ScreenRenderer* screen_renderer; char buf[128]; void KernelMain(void) { BIOSで設定した画面解像度など取得; ! ! } if (24ビット色モード) { screen_renderer = new(buf) 24ビットレンダラ(解像度); } else if (32ビット色モード) { screen_renderer = new(buf) 32ビットレンダラ(解像度); } …
C++特有の機能への対応 • new • 純粋仮想関数 • グローバル変数コンストラクタ
仮想関数テーブル • C++で仮想関数を使うと出てくる用語 • virtualなメンバ関数のアドレスの表 • クラス毎に生成され、インスタンスはvtableへのポインタを持つ class A { public: virtual void foo(); virtual void bar(); }; A::foo A::bar
class A { public: virtual void foo(); virtual void bar(); }; class B : public A { public: virtual void foo(); }; A's vtable B's vtable A::foo A::bar B::foo A::bar
class A { public: virtual void foo(); virtual void bar(); }; class B : public A { public: virtual void foo(); }; A's vtable B's vtable A::foo A::bar B::foo A::bar B instance vptr p1 A* p1 = new B();
class A { public: virtual void foo(); virtual void bar(); }; class B : public A { public: virtual void foo(); }; A's vtable B's vtable A::foo A::bar B::foo A::bar B instance vptr A instance vptr p1 p2 A* p1 = new B(); A* p2 = new A();
class A { public: virtual void foo(); virtual void bar(); }; class B : public A { public: virtual void foo(); }; A's vtable B's vtable A::foo A::bar B::foo A::bar B instance vptr A instance Aのポインタ経由でも vptr 正しくBのメソッドを呼べる p1 p2 A* p1 = new B(); A* p2 = new A();
仮想関数テーブル実物 class Base { public: virtual ~Base() {} virtual int foo() = 0; virtual int bar() {return 42;} }; 0 (int (*)(...))0 8 (int (*)(...))(& _ZTI4Base) 16 (int (*)(...))Base::~Base 24 (int (*)(...))Base::~Base 32 (int (*)(...))__cxa_pure_virtual 40 (int (*)(...))Base::bar class MyClass : public Base { public: virtual int foo() {return 43;} }; 0 8 16 24 32 40 (int (*)(...))0 (int (*)(...))(& _ZTI7MyClass) (int (*)(...))MyClass::~MyClass (int (*)(...))MyClass::~MyClass (int (*)(...))MyClass::foo (int (*)(...))Base::bar
仮想関数テーブル実物 class Base { public: virtual ~Base() {} virtual int foo() = 0; virtual int bar() {return 42;} }; 0 (int (*)(...))0 8 (int (*)(...))(& _ZTI4Base) 16 (int (*)(...))Base::~Base 24 (int (*)(...))Base::~Base 32 (int (*)(...))__cxa_pure_virtual 40 (int (*)(...))Base::bar 自分で実装 class MyClass : public Base { public: virtual int foo() {return 43;} }; 0 8 16 24 32 40 (int (*)(...))0 (int (*)(...))(& _ZTI7MyClass) (int (*)(...))MyClass::~MyClass (int (*)(...))MyClass::~MyClass (int (*)(...))MyClass::foo (int (*)(...))Base::bar
C++特有の機能への対応 • new • 純粋仮想関数 • グローバル変数コンストラクタ
コンストラクタ呼び出し class MyClass { int val_; public: MyClass() : val_(41) {} }; ! MyClass global_instance; ! int main(void) { … } コンストラクタは 誰が呼ぶ?
コンストラクタ呼び出し class MyClass { int val_; public: MyClass() : val_(41) {} }; ! MyClass global_instance; ! コンストラクタは 誰が呼ぶ? int main(void) { … } →規格では、main関数実行前に呼ばれる
コンストラクタ呼び出し class MyClass { int val_; public: MyClass() : val_(41) {} }; ! MyClass global_instance; ! コンストラクタは 誰が呼ぶ? int main(void) { … } →規格では、main関数実行前に呼ばれる 誰が呼ぶんだろう?
コンストラクタ呼び出し 処理系が用意 コンストラクタ呼び出し 大域変数0初期化 _start return PBL call main 普通のアプリ jump 自分でやる kmain カーネル
コンストラクタ呼び出し .ctors 0x000005A8 0x00001277 0x000018ED コンストラクタを呼び出す 関数のアドレス void __call_constructors() { typedef void (*ctor_caller_t)(void); extern int __ctors, __ctors_count; } ctor_caller_t* ctors = (ctor_caller_t*)&__ctors; uarch_t ctors_count = (uarch_t)&__ctors_count; for (uarch_t i = 0; i < ctors_count; ++i) { ctors[i](); }
まとめ • OS自作とは • OSの起動シーケンス • OSとC++ • new, 純粋仮想関数、コンストラクタ