# 機能設計書 86-Taskスケジューラ

## 概要

本ドキュメントは、mrubyのmruby-task gemが提供する協調的マルチタスクとプリエンプティブスケジューリング機能の機能設計を記述する。単一mruby VMインスタンス内で複数タスクの並行実行を実現するTaskクラスとスケジューラを提供する。

### 本機能の処理概要

**業務上の目的・背景**：組み込みシステムや小型デバイスでは、OSスレッドを使わずに複数の処理を並行実行したい要件がある。本機能は優先度ベースのプリエンプティブスケジューラとタイマー割り込みによるタイムスライシングを提供し、軽量な協調的マルチタスクを実現する。

**機能の利用シーン**：IoTデバイスでの複数センサーの並行読み取り、対話型シェル（PicoRuby）でのバックグラウンドタスク実行、WASM環境でのイベントループ統合、リアルタイム処理とUI処理の並行実行等。

**主要な処理内容**：
1. Task.new - 新しいタスクを作成してREADYキューに追加する
2. Task.run - スケジューラメインループを実行する
3. Task.pass - 他のタスクに制御を譲る（協調的切り替え）
4. タイムスライスベースのプリエンプティブスケジューリング
5. タスクの一時停止・再開・終了・結合（join）操作
6. HAL（ハードウェア抽象化層）によるプラットフォーム抽象化
7. 同期実行API（mrb_execute_proc_synchronously）

**関連システム・外部連携**：HAL gem（hal-posix-task、hal-win-task）、mruby VM（mrb_vm_exec）、GCシステム

**権限による制御**：scheduler_lockによる同期実行時のAPI制限

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| - | CLI | 主画面 | Task.new/run/pass等の呼び出し |

## 機能種別

システム制御処理（タスクスケジューリング）

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| block (new) | Block | Yes | タスクのコードブロック | ブロック必須 |
| name (new) | String | No | タスク名。キーワード引数 | Stringであること |
| priority (new) | Integer | No | 優先度0-255。デフォルト128 | 0-255の範囲 |
| name (get) | String | Yes | 検索するタスク名 | - |

### 入力データソース

Ruby関数呼び出しの引数

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| Task (new) | Task | 生成されたTaskオブジェクト |
| status | Symbol | :DORMANT, :READY, :RUNNING, :WAITING, :SUSPENDED |
| stat | Hash | スケジューラ統計情報 |
| list | Array | 全タスクの配列 |

### 出力先

Ruby関数の戻り値

## 処理フロー

### 処理シーケンス

```
1. Task.new(name:, priority:, &block)
   ├─ ブロック未指定時はArgumentError
   ├─ キーワード引数解析（name: String, priority: 0-255）
   ├─ task_alloc() でタスク構造体を確保
   ├─ task_init_context() でVMコンテキスト初期化
   │   ├─ VMスタック確保（初期TASK_STACK_INIT_SIZE=64）
   │   ├─ callinfo確保（初期TASK_CI_INIT_SIZE=4）
   │   └─ PC/procを設定
   ├─ RDataオブジェクト生成
   ├─ GC登録（mrb_gc_register）
   ├─ READYキューに優先度順で挿入
   └─ 高優先度の場合コンテキストスイッチをトリガー

2. Task.run (スケジューラメインループ)
   ├─ while(1)ループ
   │   ├─ READYキューからタスク取得
   │   ├─ タスクなし:
   │   │   ├─ WAITING/SUSPENDEDもなし → break（全完了）
   │   │   └─ 待機中あり → mrb_hal_task_idle_cpu()でアイドル
   │   ├─ execute_task()で実行
   │   │   ├─ コンテキスト切替（mrb->c = &task->c）
   │   │   ├─ mrb_vm_exec()で実行
   │   │   └─ コンテキスト復元
   │   ├─ READY状態ならキュー末尾に再挿入（ラウンドロビン）
   │   └─ インクリメンタルGC実行
   └─ mrb_nil_value()を返却

3. mrb_tick() (タイマー割り込みハンドラ)
   ├─ グローバルtickカウンタ増加
   ├─ RUNNINGタスクのtimeslice減少
   │   └─ 0になったらswitching_=TRUE
   └─ スリープ中タスクの起床判定
```

### フローチャート

```mermaid
flowchart TD
    A[Task.run] --> B{READYタスクあり?}
    B -->|No| C{WAITING/SUSPENDEDあり?}
    C -->|No| D[スケジューラ終了]
    C -->|Yes| E[CPU idle]
    E --> B
    B -->|Yes| F[execute_task]
    F --> G[コンテキスト切替]
    G --> H[mrb_vm_exec実行]
    H --> I{タスク完了?}
    I -->|Yes| J[DORMANTキューへ移動]
    J --> K[join待ちタスクを起床]
    I -->|No| L[READYキューに再挿入]
    K --> B
    L --> M[インクリメンタルGC]
    M --> B
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-86-1 | 優先度順序 | 低い数値ほど高優先度。0が最高、255が最低 | 常時 |
| BR-86-2 | タイムスライス | デフォルト12ms（4ms*3ticks）でプリエンプション | MRB_TICK_UNIT*MRB_TIMESLICE_TICK_COUNT |
| BR-86-3 | ラウンドロビン | 同優先度タスクはラウンドロビンで実行 | 同優先度タスク複数時 |
| BR-86-4 | scheduler_lock | ロック中は非同期タスクAPIが使用不可 | mrb_execute_proc_synchronously実行中 |
| BR-86-5 | C関数境界 | C関数呼び出しスタック内ではコンテキストスイッチ不可 | cci > 0のcallinfo存在時 |

### 計算ロジック

- タイムスライス: `MRB_TICK_UNIT(4ms) * MRB_TIMESLICE_TICK_COUNT(3) = 12ms`
- マイクロ秒→tick変換: `USEC_TO_TICKS(usec) = ((usec / 1000) / MRB_TICK_UNIT)`

## データベース操作仕様

該当なし

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| - | ArgumentError | Task.newにブロックなし | "tried to create task without a block" |
| - | ArgumentError | priority範囲外 | "priority must be 0-255" |
| - | TypeError | name/priorityの型不正 | "name must be a String" / "priority must be an Integer" |
| - | RuntimeError | scheduler_lock中にAPI使用 | "Cannot use asynchronous Task API during synchronous execution" |
| - | RuntimeError | C関数境界越えのpass | "can't pass across C function boundary" |
| - | ArgumentError | join selfの場合 | "can't join self" |

### リトライ仕様

該当なし

## トランザクション仕様

- IRQ保護: `mrb_task_disable_irq()`/`mrb_task_enable_irq()` でキュー操作をアトミックに実行
- scheduler_lock: 同期実行中のインクリメント/デクリメントで排他制御

## パフォーマンス要件

- コンテキストスイッチ: mrb->cポインタの切り替えのみ（O(1)）
- キュー操作: 優先度順挿入のため O(N)
- tick処理: O(待機タスク数)

## セキュリティ考慮事項

- タスク間のメモリ空間は分離されていない（同一VM内で共有）
- スレッドセーフではない（全タスクは単一OSスレッドで動作）

## 備考

- mruby-sleepと併用する場合、sleep/usleepはmruby-taskのタスク対応版に上書きされる
- FiberとTaskは互換性がない（混在不可）
- HAL gem（hal-posix-task/hal-win-task）が必須

---

## コードリーディングガイド

本機能を理解するために参照すべきファイルと、推奨する読み解き順序を以下に示す。

### 推奨読解順序

#### Step 1: データ構造を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | task.h | `mrbgems/mruby-task/include/task.h` | mrb_task構造体（44-67行目）でタスクの内部表現を理解 |
| 1-2 | task.h | `mrbgems/mruby-task/include/task.h` | タスクステータス定数（15-21行目）とキュー定義（73-79行目） |
| 1-3 | task_hal.h | `mrbgems/mruby-task/include/task_hal.h` | HALインターフェース定義 |

**読解のコツ**: mrb_task構造体の末尾に `struct mrb_context c` がインライン配置されている。MRB2TASKマクロ（37行目）はmrb->cからoffsetofでtaskポインタを逆算する。waitフィールドはunionでwakeup_tick/join/mutexを共有している。

#### Step 2: キュー操作を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | task.c | `mrbgems/mruby-task/src/task.c` | q_insert_task/q_delete_task（174-221行目）で優先度順リンクリスト操作を理解 |

#### Step 3: スケジューラコアを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | task.c | `mrbgems/mruby-task/src/task.c` | execute_task（337-401行目）でコンテキスト切替とVM実行を確認 |
| 3-2 | task.c | `mrbgems/mruby-task/src/task.c` | mrb_task_run（452-494行目）でメインスケジューラループを確認 |
| 3-3 | task.c | `mrbgems/mruby-task/src/task.c` | mrb_tick（404-449行目）でタイマー割り込みハンドラを確認 |

#### Step 4: タスクライフサイクルを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | task.c | `mrbgems/mruby-task/src/task.c` | mrb_task_s_new（656-728行目）でタスク生成を確認 |
| 4-2 | task.c | `mrbgems/mruby-task/src/task.c` | suspend/resume/terminate（1293-1411行目）でライフサイクル管理を確認 |

### プログラム呼び出し階層図

```
mrb_mruby_task_gem_init (1517-1561行目)
    │
    ├─ Task.new ──▶ mrb_task_s_new
    │                 ├─ task_alloc
    │                 ├─ task_init_context
    │                 ├─ mrb_data_object_alloc
    │                 └─ q_insert_task
    │
    ├─ Task.run ──▶ mrb_task_run
    │                 └─ execute_task
    │                      ├─ mrb_vm_exec
    │                      └─ wake_up_join_waiters
    │
    ├─ Task.pass ──▶ mrb_task_s_pass
    │                  └─ switching_ = TRUE
    │
    ├─ mrb_tick (HALから呼び出し)
    │     ├─ timeslice--
    │     └─ sleeping tasks wakeup
    │
    └─ sleep/usleep ──▶ sleep_us_impl
                         └─ q_delete/q_insert (WAITING)
```

### データフロー図

```
[入力]                   [処理]                          [出力]

Block + 設定 ───▶ task_alloc + init_context ───▶ Taskオブジェクト
                        │
                        ▼
              ┌─────────────────────┐
              │   4つのキュー         │
              │ DORMANT ←──── 完了  │
              │ READY ────▶ 実行    │
              │ WAITING ──── sleep  │
              │ SUSPENDED ── 停止   │
              └─────────────────────┘
                        │
                        ▼
              execute_task → mrb_vm_exec
                        │
                        ▼
              タスク結果 (state.result)
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| task.c | `mrbgems/mruby-task/src/task.c` | ソース | スケジューラコア実装（1574行） |
| task.h | `mrbgems/mruby-task/include/task.h` | ヘッダ | タスク構造体・API定義 |
| task_hal.h | `mrbgems/mruby-task/include/task_hal.h` | ヘッダ | HALインターフェース定義 |
| mrbgem.rake | `mrbgems/mruby-task/mrbgem.rake` | 設定 | gem仕様定義、HAL自動選択 |
| task.rb | `mrbgems/mruby-task/test/task.rb` | テスト | テストケース |
| README.md | `mrbgems/mruby-task/README.md` | ドキュメント | 詳細仕様・使用例 |
