はじめに
“自作OSで学ぶマイクロカーネルの設計と実装”におけるレポジトリ内のIDEAS.md
に非常に刺激的な課題が記載されていたので、チャレンジしようと思います。ちなみに、当書籍は懇切丁寧にソースコードの解説が記載されており、OS実装の学習に最適であると思います。
また、私は課題に対して頭を使って試行錯誤する過程が一番力を付くと考えているので、大まかに全体に目を通し、まだ完璧には内容を理解していない状態で、課題に取り組んでいます。
実装概要
初歩的な取り組みとして、int型のデータを送ると、インクリメントして返すincサーバを立てます。
実装フロー
IDEAS.md 参照より、具体的な実装フローは以下のようになります。
1. `messages.idl`に新しいメッセージを定義し、一回 `make` を実行してメッセージの型定義を自動生成する。
2. `servers`ディレクトリに新しいサーバのディレクトリを作成する (`servers/pong`をコピーアンドペーストするとよい)。
3. サーバの実装を書く。
3. Makefileの`BOOT_SERVERS`変数に新しいサーバを追加する。すると、起動時に自動的にサーバが起動するようになる。
4. クライアント側の実装を書く。たとえばコマンドラインシェルにサーバと通信する新しいコマンドを追加する。
この手順に従い、サーバを実装していきます。
1. messages.idl
に新しいメッセージを定義する
リモートプロシージャコール (rpc) 用のメッセージを定義します。文法自体はRust言語の関数定義のようです。
rpc inc(sock: int) -> (sock: int);
上記のような関数を定義し、makeを実行します。すると、libs/common/ipcstub.h において、
- サーバへのメッセージ
- サーバの返答用メッセージ
の2つに対応する構造体と定数が自動生成されました。
// libs/common/ipcstub.h
struct inc_fields {
int value;
};
struct inc_reply_fields {
int value;
};
...
#define INC_MSG 63
#define INC_REPLY_MSG 64
2. servers
ディレクトリに新しいサーバのディレクトリを作成する
ヒントに従いpongディレクトリをコピペし、名前をincに。build.mkの効用はまだ分からないですが、まとめてコピーします。
3. サーバの実装を書く。
pingの実装を踏襲します。内容は非常にシンプルなもので、受け取った値をインクリメントして返すというものです。
// servers/inc/main.c
void main(void) {
ASSERT_OK(ipc_register("inc"));
TRACE("ready");
while (true) {
struct message m;
ASSERT_OK(ipc_recv(IPC_ANY, &m));
switch (m.type) {
case INC_MSG: {
DBG("received ping message from #%d (value=%d)", m.src,
m.inc);
m.type = INC_REPLY_MSG;
m.inc_reply.value = m.inc.value++;
ipc_reply(m.src, &m);
break;
}
default:
WARN("unhandled message: %s (%x)", msgtype2str(m.type), m.type);
break;
}
}
}
主にIPCで扱うメッセージの構成は以下のようになります。union
型内で示されているIPCSTUB_MESSAGE_FIELDS
は、メッセージの種類、type
に対応する構造体になります。
struct message {
int32_t type; // メッセージの種類 (負の数の場合はエラー値)
task_t src; // メッセージの送信元
union {
uint8_t data[0]; // メッセージデータの先頭を指す
/// 自動生成される各メッセージのフィールド定義:
//
// struct { int x; int y; } add;
// struct { int answer; } add_reply;
// ...
//
IPCSTUB_MESSAGE_FIELDS
};
};
4. シェルコマンドの実装
シェルから、incサーバにアクセスするコマンドを実装します。これもまた非常にシンプルで、inc <value>
といった文法です。
// servers/shell/main.c
static void do_inc(struct args *args) {
if (args->argc != 2) {
WARN("Usage: inc <VALUE>");
return;
}
task_t inc_server = ipc_lookup("inc");
int value = atoi(args->argv[1]);
// incサーバにメッセージを送信する
struct message m;
m.type = INC_MSG;
m.inc.value = value;
ASSERT_OK(ipc_call(inc_server, &m));
// incサーバからの応答が想定されたものか確認する
printf("reply: %d\n", m.inc_reply.value);
ASSERT(m.type == INC_REPLY_MSG);
}
実装した関数をもとに、コマンド設定に追記します。.name
がシェルに打つコマンド名、.run_inc
がそのバイナリに対応する処理、.help
がhelpコマンドを打ち込んだ際に表示される説明文ですね。
// servers/shell/main.c
static struct command commands[] = {
...
{.name = "inc", .run = do_inc, .help = "incremnt given number"},
...
};
自動サーバに追記を行います。
# Makefile
# 自動起動するサーバのリスト
BOOT_SERVERS ?= fs tcpip shell virtio_blk virtio_net pong inc
動作確認
正常に機能しました!これでHina OS のハックに成功しました!笑
まとめ
Hina OSは大変面白い題材です。是非次の課題にも取り組んでいこうと思います。