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の助けを借りられない

→ 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ビットモード)へ移行

•

カーネル部分を、所定の位置へコピー

→ リンカへの指示と合わせる

•
...
カーネルの仕事
アプリ アプリ
•

ハードウェア初期化

カーネル
ハードウェア

•

割り込み管理

→ キーボード/マウス、タイマ、ネットワーク

•

タスク管理

•

メモリ管理
OS開発とC++
OS自作入門とC言語
•

『30日でできる!OS自作入門』はC & アセンブリ

•

C言語でキューの実装→だるい
int以外のキュー
struct FIFO32 {
int *buf;
int p, q, size, free, flag...
C++でOS開発
•

先人たち
•

BayOS : C++コンパイラだけで対応可能な機能だけ使用

•

MonaOS : 例外、純粋仮想関数なども使用

•

「gccをg++に置き換えるだけで使える機能」は簡単

•

でも、C++なら...
OS開発におけるC++の楽しさ
•

『30日でできる!OS自作入門』のC言語コードを
改良していく楽しさ
•

•

キューをクラスで実装し直すのが好き

OS開発がオブジェクト指向言語でできる楽しさ
•

継承を使って分かりやすいコード

...
OS開発におけるC++の苦労
•

『30日でできる!OS自作入門』の開発環境を流用できない
•
•

•

自分でクロスコンパイラを用意
特に川合さん謹製リンカは.ctorsや.rodataに対応しない

C++特有の機能への対応
•

純粋...
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)
{...
メモリ割り当て - リンクリスト方式
0x1000

Header* current
= (Header*)0x1000;

Allocated

0x2000

size=0xFF8
true

Header* next
= (Header*...
配置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...
配置newの使いドコロ
例えばこんな場面
ScreenRenderer* screen_renderer;
!

void KernelMain(void) {
BIOSで設定した画面解像度など取得;
!

!

}

if (24ビット色モー...
配置newの使いドコロ
例えばこんな場面
ScreenRenderer* screen_renderer;
!

様々なところで使う
→グローバル変数

void KernelMain(void) {
BIOSで設定した画面解像度など取得;
!...
配置newの使いドコロ
例えばこんな場面
ScreenRenderer* screen_renderer;
!

様々なところで使う
→グローバル変数

void KernelMain(void) {
BIOSで設定した画面解像度など取得;
!...
配置newの使いドコロ

•

普通、newは新たなメモリを割り当てる

(コンパイラがコンストラクタ呼び出しコードを自動で追加)

•

メモリ割り当てはOSの仕事

•

割り当て機能が無い段階でnewを使いたい!

•

→ 配置new
配置newの使いドコロ
class A {
int val_;
const char* str_;
public:
A() : val_(41), str_("foo") {}
};

41

確保した
メモリ

41
"foo"

"foo"...
配置newの使いドコロ
配置newで無事解決!
ScreenRenderer* screen_renderer;
char buf[128];
void KernelMain(void) {
BIOSで設定した画面解像度など取得;
!

!

...
C++特有の機能への対応

•

new

•

純粋仮想関数

•

グローバル変数コンストラクタ
仮想関数テーブル

•

C++で仮想関数を使うと出てくる用語

•

virtualなメンバ関数のアドレスの表

•

クラス毎に生成され、インスタンスはvtableへのポインタを持つ

class A {
public:
virtual v...
class A {
public:
virtual void foo();
virtual void bar();
};

class B : public A {
public:
virtual void foo();
};

A's vta...
class A {
public:
virtual void foo();
virtual void bar();
};

class B : public A {
public:
virtual void foo();
};

A's vta...
class A {
public:
virtual void foo();
virtual void bar();
};

class B : public A {
public:
virtual void foo();
};

A's vta...
class A {
public:
virtual void foo();
virtual void bar();
};

class B : public A {
public:
virtual void foo();
};

A's vta...
仮想関数テーブル実物
class Base
{
public:
virtual ~Base() {}
virtual int foo() = 0;
virtual int bar() {return 42;}
};
0 (int (*)(......
仮想関数テーブル実物
class Base
{
public:
virtual ~Base() {}
virtual int foo() = 0;
virtual int bar() {return 42;}
};
0 (int (*)(......
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;
!

コンストラクタは
誰が呼ぶ?

i...
コンストラクタ呼び出し

class MyClass {
int val_;
public:
MyClass() : val_(41) {}
};
!

MyClass global_instance;
!

コンストラクタは
誰が呼ぶ?

i...
コンストラクタ呼び出し
処理系が用意
コンストラクタ呼び出し
大域変数0初期化

_start

return

PBL

call
main

普通のアプリ

jump
自分でやる

kmain

カーネル
コンストラクタ呼び出し
.ctors
0x000005A8
0x00001277
0x000018ED

コンストラクタを呼び出す
関数のアドレス

void __call_constructors()
{
typedef void (*cto...
まとめ

•

OS自作とは

•

OSの起動シーケンス

•

OSとC++
•

new, 純粋仮想関数、コンストラクタ
Upcoming SlideShare
Loading in...5
×

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

23,090

Published on

at C++勉強会 in 広島

Published in: Technology

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, 純粋仮想関数、コンストラクタ
  1. A particular slide catching your eye?

    Clipping is a handy way to collect important slides you want to go back to later.

×