マイクロカーネル HinaOS にシステムコールを実装する

### はじめに

前回に引き続き、怒田晟也さん著“自作OSで学ぶマイクロカーネルの設計と実装”の`IDEAS.md`より、マイクロカーネルHinaOSにシステムコールを追加します。

### 実装概要

割り込みの発生回数を取得するシステムコールを実装します。

### あたりをつける

`kernel/syscall.c`がシステムコール定義場所になっています。このファイル内容を見る限り、`sys_uptime`関数がカーネル内の処理に関連する統計情報を返す関数になっているので、この関数まわりを踏襲することで、目的を達成できそうです。

```c
// 起動してからの経過時間をミリ秒単位で返す。
static int sys_uptime(void) {
    return uptime_ticks / TICK_HZ;
}
```

## 実装

### 1. ユーザランド側の関数呼び出し定義

各システムコールは、32bitのシステムコール番号で識別され、指定のレジスタに格納されたシステムコール番号に応じてカーネルモード時に処理を行います。`libs/common/types.h` にその定義場所があるので、定義します。

```c
#define SYS_INTERRUPT_COUNT    18
```

次に、`libs/user/syscall.c` に以下の関数を定義します。特に渡したい値はないので、0 ~ 4番目の引数は0にします。最後の引数はカーネルモード時に関数を実行する識別子になるので、先ほど定義した変数を入れます。

```c
// interruptシステムコール: システムの割り込み回数の取得
int sys_interrupt_count(void) {
    return arch_syscall(0, 0, 0, 0, 0, SYS_INTERRUPT_COUNT);
}
```

### 2. 割り込み回数カウントを定義

グローバル変数`uptime_ticks`は、`interrupt.h`に定義されているので、同様に割り込み回数カウントを`interrupt.h`に定義します。ちなみに、`extern` 修飾子は外部ファイルから参照可能な変数であることを表します。

```c
extern unsigned uptime_ticks;
extern unsigned interrupt_count; // 割り込み回数の総数
```

上記に加え、`interrupt.c`ファイル内でも`interrupt_count`を宣言＆初期化しないと怒られます。これは、ヘッダファイル内の`extern`で定義と初期化が同時にできないためです。

```c
unsigned interrupt_count = 0;
```

### 3. 統計情報取得実装

割り込み・例外ハンドラのエントリポイントとなる`kernel/riscv32/trap.c`の`riscv32_handle_trap`関数の頭に先ほど定義した`interrupt_count`のインクリメント処理を加えます。

```c
void riscv32_handle_trap(struct riscv32_trap_frame *frame) {
    stack_check();  // スタックオーバーフローをチェック
    interrupt_count++;
    ...
```

### 4. システムコール定義

`libs/user/`にて、ユーザランド側のシステムコールのインタフェースとなるAPIを定義します。

```c
// libs/user/syscall.h

int sys_interrupt_count(void);
```

```c

// libs/user/syscall.c

static int sys_interrupt_count(void) {
    return interrupt_count;
}
```

### 5. システムコールハンドラ追記

`/kernel/syscall.c`の`handle_syscall`にて、システムコール番号のパターンマッチを行う箇所があるので、そこにケースを追記します。

```c
// システムコールハンドラ
long handle_syscall(long a0, long a1, long a2, long a3, long a4, long n) {
    long ret;
    switch (n) {
	      // ...
        case SYS_INTERRUPT_COUNT:
            ret = sys_interrupt_count();
            break;
	      // ...
    }

    return ret;
}
```

### 6. コマンドを定義する

シェルサーバに割り込み回数を取得するコマンドを定義します。interruptは文字数が長く、タイポする可能性があるので、`intpt`というコマンドとして定義します。

```c
static void do_interrupt_count(struct args *args) {
    printf("%d times\n", sys_interrupt_count());
}

// ...

static struct command commands[] = {
		// ...
    {.name = "intpt", .run = do_interrupt_count, .help = "get interrupt count"},
    // ...
};
```

### 7. 実行

```c
shell> intpt
15279 times
shell> intpt
18689 times
shell> intpt
36608 times
```

一秒あたり2000回ほど割り込まれています。タイマ割り込みも含まれているので、このような数字になるのだと思います。

### 参考文献

[https://www.amazon.co.jp/自作OSで学ぶマイクロカーネルの設計と実装-怒田晟也-ebook/dp/B0C52SFYDC/ref=sr_1_1?__mk_ja_JP=カタカナ&crid=17NO089IHFDL0&keywords=マイクロカーネル&qid=1687662077&s=books&sprefix=マイクロカーネル%2Cstripbooks%2C166&sr=1-1](https://www.amazon.co.jp/%E8%87%AA%E4%BD%9COS%E3%81%A7%E5%AD%A6%E3%81%B6%E3%83%9E%E3%82%A4%E3%82%AF%E3%83%AD%E3%82%AB%E3%83%BC%E3%83%8D%E3%83%AB%E3%81%AE%E8%A8%AD%E8%A8%88%E3%81%A8%E5%AE%9F%E8%A3%85-%E6%80%92%E7%94%B0%E6%99%9F%E4%B9%9F-ebook/dp/B0C52SFYDC/ref=sr_1_1?__mk_ja_JP=%E3%82%AB%E3%82%BF%E3%82%AB%E3%83%8A&crid=17NO089IHFDL0&keywords=%E3%83%9E%E3%82%A4%E3%82%AF%E3%83%AD%E3%82%AB%E3%83%BC%E3%83%8D%E3%83%AB&qid=1687662077&s=books&sprefix=%E3%83%9E%E3%82%A4%E3%82%AF%E3%83%AD%E3%82%AB%E3%83%BC%E3%83%8D%E3%83%AB%2Cstripbooks%2C166&sr=1-1)