Cとカーネル開発におけるオブジェクト指向デザインパターン
(oshub.org)- オブジェクト指向デザインパターンは、C言語で書かれたカーネルでも多態性とモジュール性を実現し、柔軟なシステム設計を可能にする
- vtable(仮想関数テーブル)を使ってデバイスやサービスのインターフェースを標準化し、ランタイムでの動的変更によってさまざまな動作をサポート
- カーネルサービスやスケジューラは、vtableを通じて開始・停止・再起動といった一貫したインターフェースを提供し、実装の詳細をカプセル化
- カーネルモジュールとの組み合わせにより、動的なドライバーロードをサポートし、再コンパイルなしでシステムを拡張可能
- このアプローチは柔軟性と実験の自由をもたらす一方、複雑な構文や明示的なオブジェクト受け渡しによる煩雑さが欠点となる
OS開発における自由とオブジェクト指向パターン
- 自分自身のOS開発では、協業や実運用上の制約なしに自由な実験が可能
- セキュリティ脆弱性、コード保守、リリース負担から解放される
- これがOS開発の魅力であり、非標準的なプログラミングパターンを探求できる
- LWNの記事「Object-oriented design patterns in the kernel」では、LinuxカーネルがCでオブジェクト指向の原則を実装した事例を紹介
- 関数ポインタを含む構造体で多態性を実現
- カプセル化、モジュール性、拡張性によって、低レベルなカーネルでもオブジェクト指向の利点を活用
vtableの基本概念
- vtableは関数ポインタを含む構造体で、オブジェクトのインターフェースを定義する
- 例: デバイス動作用の構造体
struct device_ops { void (*start)(void); void (*stop)(void); }; struct device { const char *name; const struct device_ops *ops; };
- 例: デバイス動作用の構造体
- 異なるデバイス(例:
netdev,disk)が同じAPIを使い、実装は異なるnetdev.ops->start()はネットワークデバイス、disk.ops->start()はディスクデバイスの動作を呼び出す
- ランタイム変更: vtableを動的に差し替えることで、呼び出し側のコードを変えずに動作を変更
- 適切な同期により、クリーンな動的動作の進化を提供
OSでの適用例
サービス管理
- カーネルサービス(ネットワークマネージャー、ワーカープール、ウィンドウサーバーなど)を一貫したインターフェースで管理
- サービス構造体:
struct service_ops { void (*start)(void); void (*stop)(void); void (*restart)(void); }; struct service { pid_t pid; const struct service_ops *ops; };
- サービス構造体:
- 各サービスは固有の動作を実装しつつ、ターミナルから開始/停止/再起動を標準化された方法で実行
- コードとサービス間の結合度を低減し、管理を簡素化
スケジューラ
- スケジューラは、ラウンドロビン、最短ジョブ優先、FIFO、優先度スケジューリングなど多様な戦略をサポート
- インターフェースは
yield、block、add、nextに単純化 - vtableで定義し、ランタイムにスケジューリングポリシーを切り替え可能
- カーネルの残りの部分を修正せずに、全体のポリシーを変更可能
- インターフェースは
ファイル抽象化
- Linuxのfile_operations構造体は、「すべてはファイル」という哲学を実装
- 例: https://elixir.bootlin.com/linux/v6.15/source/include/linux/fs.h
struct file_operations { struct module *owner; loff_t (*llseek)(struct file *, loff_t, int); ssize_t (*read)(struct file *, char __user *, size_t, loff_t *); ssize_t (*write)(struct file *, const char __user *, size_t, loff_t *); ... };
- 例: https://elixir.bootlin.com/linux/v6.15/source/include/linux/fs.h
- ソケット、デバイス、テキストファイルはいずれも同じread/writeインターフェースを提供
- ユーザー空間のコードは、実装の詳細を知らなくても一貫した方法で動作できる
カーネルモジュールとの組み合わせ
- カーネルモジュールは、vtableの差し替えによって動的なドライバーやフックのロードをサポート
- Linuxモジュールのように、再コンパイルや再起動なしでカーネルを拡張可能
- 新機能追加時には既存構造体のvtableだけを更新
欠点
- 構文の複雑さ:
object->ops->start(object)のように、オブジェクトを明示的に渡す必要がある- C++の暗黙的な受け渡しに比べて煩雑
- 関数シグネチャも冗長:
static void object_start(struct object* this) { this->id = ... }
- 利点: 明示的な受け渡しにより関数の依存関係が明確になり、オブジェクトと動作の結び付きが透明になる
- カーネルコードにおける複雑さと明確さの適切なtradeoff
示唆
- vtableは柔軟性を保ちながら複雑さを減らすシンプルな方法を提供
- ランタイムでの動作差し替え、一貫したインターフェースの維持、新機能追加の容易さ
- C言語でオブジェクト指向設計を実装する新たな方法を示し、OS開発の実験的な面白さを強調
- 追加資料: xineプロジェクト(https://xine.sourceforge.net/hackersguide/…
- OS開発は創造的な実験の場であり、オブジェクト指向パターンが低レベルシステムでも強力な道具であることを証明
1件のコメント
Hacker Newsのコメント
object->ops->start(object)でオブジェクトを二度明示しなければならない点を指摘しているのだと思う。1回はvtable解決のため、もう1回はC関数実装にオブジェクトを渡すために必要になるmFoo、m_Foo、foo_などの命名規約をよく使っている。foo_はthis->fooより簡潔なので好んでいる。もちろんC++でも明示的にthisを使うことはできるmystruct_dosmth(s);よりs->dosmth();のほうが自然になるstruct file_operationsの例はthisポインタを受け取らない関数ポインタなので、厳密には本当のvtableとは言いにくいthing->vtable->foo(thing, ...)の代わりにfoo(thing, ...)のように書けるようにしている