C++でできる!OS自作入門

31,388 views

Published on

at C++勉強会 in 広島

Published in: Technology
0 Comments
75 Likes
Statistics
Notes
  • Be the first to comment

No Downloads
Views
Total views
31,388
On SlideShare
0
From Embeds
0
Number of Embeds
551
Actions
Shares
0
Downloads
115
Comments
0
Likes
75
Embeds 0
No embeds

No notes for slide

C++でできる!OS自作入門

  1. 1. C++でできる!OS自作入門 OS入門へのいざない C++勉強会in広島 @uchan_nos
  2. 2. そもそもOSとは • 身近な例 Windows, OS X, Linux, iOS, Android • ハードウェアとアプリの橋渡しをするソフト • メモリ管理、ハードウェア制御、タスク管理、…
  3. 3. OS自作 #とは • Windowsみたいなソフトを自分で作る • 具体的にどうやって作るのか? ! • そんなあなたに『OS自作入門』
  4. 4.   30日でできる!OS自作入門 30日! マルチタスク GUI
  5. 5. OS自作の楽しさ • 自分のコンピュータを支配している感覚
 → ハードウェアを直接いじる • 普段使ってるOSの裏側を実体験できる
 → メモリ管理、タスク管理ってどうなってる? • 独自アイデアを盛り込んだOSの創造
 → エミュレータOS、クラウドOS
  6. 6. OS自作の苦労 • 自分がOSなので、OSの助けを借りられない
 → main関数さえ自分で呼び出す • 画面表示も自分の責任
 → 画面表示にバグがあると涙目 • エミュレータと実機での差
 → エミュレータだと上手く動いたのに!
  7. 7. BITNOS • 私が作っているOS • NOS = Not Operating System
 OSぽい画面のジョークソフト
 (Akkie氏命名) • Bit NOS : ちょびっとだけNOS • 5回くらい作り直しているが、 まだ全く完成しない
  8. 8. OS起動プロセス OSってどうやって起動するの??
  9. 9. OSの起動 MBR読み込み MBR: Master Boot Record BIOS 先頭セクタ = MBR 0x7c00 0x7e00
  10. 10. OSの起動 MBRコピー 0x7a00 0x7c00 0x7e00 MBR MBR 自身をコピー& コピー先へジャンプ
  11. 11. OSの起動 パーティション解析 パーティション情報
  12. 12. OSの起動 PBL読み込み PBL: Partition Boot Loader MBR パーティション 先頭セクタ = PBL 0x7c00 0x7e00 MBR PBL
  13. 13. OSの起動 なぜMBRをコピーするか BIOSを模倣するため • パーティションがないメディア
 → PBLがディスク先頭 • BIOSは機械的にディスク先頭を0x7c00に読み込む • MBRがPBLを0x7c00に配置し、BIOSを模倣
  14. 14. OSの起動 PBLの仕事 • FATを解析 • "BITNOS.SYS"を探してロード • • ローダー+カーネル(2段階ロード) ロード先へジャンプ
  15. 15. OSの起動 ローダーの仕事 • BIOSを使う用事を済ます
 → 画面モード設定、キーボードLED状態取得 • プロテクトモード(32ビットモード)へ移行 • カーネル部分を、所定の位置へコピー
 → リンカへの指示と合わせる • カーネル(kmain)にジャンプ
  16. 16. カーネルの仕事 アプリ アプリ • ハードウェア初期化 カーネル ハードウェア • 割り込み管理
 → キーボード/マウス、タイマ、ネットワーク • タスク管理 • メモリ管理
  17. 17. OS開発とC++
  18. 18. 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; }
  19. 19. C++でOS開発 • 先人たち • BayOS : C++コンパイラだけで対応可能な機能だけ使用 • MonaOS : 例外、純粋仮想関数なども使用 • 「gccをg++に置き換えるだけで使える機能」は簡単 • でも、C++ならそれらしい機能使いたいよね! • 純粋仮想関数、例外、実行時型情報、new/delete
  20. 20. OS開発におけるC++の楽しさ • 『30日でできる!OS自作入門』のC言語コードを 改良していく楽しさ • • キューをクラスで実装し直すのが好き OS開発がオブジェクト指向言語でできる楽しさ • 継承を使って分かりやすいコード • 純粋仮想関数ひゃっはー
  21. 21. OS開発におけるC++の苦労 • 『30日でできる!OS自作入門』の開発環境を流用できない • • • 自分でクロスコンパイラを用意 特に川合さん謹製リンカは.ctorsや.rodataに対応しない C++特有の機能への対応 • 純粋仮想関数、例外、実行時型情報、new/delete、グ ローバル変数コンストラクタなど
  22. 22. C++特有の機能への対応 • new • 純粋仮想関数 • グローバル変数コンストラクタ
  23. 23. C++特有の機能への対応 • new • 純粋仮想関数 • グローバル変数コンストラクタ
  24. 24. new演算子 • new演算子は
 1)メモリを確保し、2)コンストラクタを呼ぶ • OSが空きメモリを探し、メモリを割り当てる • g++がコンストラクタ呼び出しコードを追加する
  25. 25. 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に書いておく
  26. 26. メモリ割り当て - リンクリスト方式 0x1000 Header* current = (Header*)0x1000; Allocated 0x2000 size=0xFF8 true Header* next = (Header*)( (uintptr_t)current + sizeof(Header) + current->size); size=0xAF8 false Released 0x2B00 struct Header { size_t size; bool is_allocated; };
  27. 27. 配置new演算子 • new演算子は
 1)メモリを確保し、2)そこでコンストラクタを呼ぶ • 配置new演算子は
 1)指定されたメモリ領域でコンストラクタを呼ぶ • プログラマがメモリ領域を与える • g++がコンストラクタ呼び出しコードを追加する
  28. 28. 配置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; }
  29. 29. 配置newの使いドコロ 例えばこんな場面 ScreenRenderer* screen_renderer; ! void KernelMain(void) { BIOSで設定した画面解像度など取得; ! ! } if (24ビット色モード) { screen_renderer = new 24ビットレンダラ(解像度); } else if (32ビット色モード) { screen_renderer = new 32ビットレンダラ(解像度); } …
  30. 30. 配置newの使いドコロ 例えばこんな場面 ScreenRenderer* screen_renderer; ! 様々なところで使う →グローバル変数 void KernelMain(void) { BIOSで設定した画面解像度など取得; ! ! } if (24ビット色モード) { screen_renderer = new 24ビットレンダラ(解像度); } else if (32ビット色モード) { screen_renderer = new 32ビットレンダラ(解像度); } …
  31. 31. 配置newの使いドコロ 例えばこんな場面 ScreenRenderer* screen_renderer; ! 様々なところで使う →グローバル変数 void KernelMain(void) { BIOSで設定した画面解像度など取得; ! ! } if (24ビット色モード) { screen_renderer = new 24ビットレンダラ(解像度); } else if (32ビット色モード) { screen_renderer = new 32ビットレンダラ(解像度); } … でもインスタンスは 後で生成したい → newを使う
  32. 32. 配置newの使いドコロ • 普通、newは新たなメモリを割り当てる
 (コンパイラがコンストラクタ呼び出しコードを自動で追加) • メモリ割り当てはOSの仕事 • 割り当て機能が無い段階でnewを使いたい! • → 配置new
  33. 33. 配置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
  34. 34. 配置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ビットレンダラ(解像度); } …
  35. 35. C++特有の機能への対応 • new • 純粋仮想関数 • グローバル変数コンストラクタ
  36. 36. 仮想関数テーブル • C++で仮想関数を使うと出てくる用語 • virtualなメンバ関数のアドレスの表 • クラス毎に生成され、インスタンスはvtableへのポインタを持つ class A { public: virtual void foo(); virtual void bar(); }; A::foo A::bar
  37. 37. 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
  38. 38. 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();
  39. 39. 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();
  40. 40. 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();
  41. 41. 仮想関数テーブル実物 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
  42. 42. 仮想関数テーブル実物 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
  43. 43. C++特有の機能への対応 • new • 純粋仮想関数 • グローバル変数コンストラクタ
  44. 44. コンストラクタ呼び出し class MyClass { int val_; public: MyClass() : val_(41) {} }; ! MyClass global_instance; ! int main(void) { … } コンストラクタは 誰が呼ぶ?
  45. 45. コンストラクタ呼び出し class MyClass { int val_; public: MyClass() : val_(41) {} }; ! MyClass global_instance; ! コンストラクタは 誰が呼ぶ? int main(void) { … } →規格では、main関数実行前に呼ばれる
  46. 46. コンストラクタ呼び出し class MyClass { int val_; public: MyClass() : val_(41) {} }; ! MyClass global_instance; ! コンストラクタは 誰が呼ぶ? int main(void) { … } →規格では、main関数実行前に呼ばれる 誰が呼ぶんだろう?
  47. 47. コンストラクタ呼び出し 処理系が用意 コンストラクタ呼び出し 大域変数0初期化 _start return PBL call main 普通のアプリ jump 自分でやる kmain カーネル
  48. 48. コンストラクタ呼び出し .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](); }
  49. 49. まとめ • OS自作とは • OSの起動シーケンス • OSとC++ • new, 純粋仮想関数、コンストラクタ

×