Bladeren bron

New: tk::Thread

Satoshi Yoneda 2 weken geleden
bovenliggende
commit
bee019ca2c

+ 86 - 0
README.md

@@ -112,6 +112,58 @@ int main(void) {
 
 ---
 
+## 動的スレッドとシグナル通知 (`thread.hpp`)
+
+`tk::Thread<StackSize>` を継承することで、**自身のメンバとしてスタックを内包し、シグナルによるスリープ早期中断と自動コールバックを備えたスレッド**を定義できます(`tk::Actor` もこの `tk::Thread` を継承しています)。
+
+- **`tk::start()` の前でも後(走行中)でも起動可能**: 一時的なワーカーを動的に起動できます。
+- **ユーザー定義シグナル**: `tk::SIG_STOP` や `tk::SIG_INTR` のシステムシグナルに加え、`tk::SIG_USER` (16) 以降の任意の値で独自シグナルを定義できます。
+- **安全な RAII ライフサイクル**: `stop()` や `run()` 完了時は一時停止(`State::Terminated`)状態でスロットを保持し、**スコープを抜けるか `delete` でデストラクタが呼ばれた時点で初めてスロットが解放(`State::Free`)** されます。
+
+```cpp
+#include "tinykernel/thread.hpp"
+
+// ユーザー定義シグナル (tk::SIG_USER 以降を使用)
+enum AppSignal : uint32_t {
+    SIG_WAKE_NOW = tk::SIG_USER,
+    SIG_DUMP     = tk::SIG_USER + 1,
+};
+
+class MyWorker : public tk::Thread<1024> { // 1KB スタックを内包
+protected:
+    void run() override {
+        while (!should_stop()) {
+            // 5秒スリープ。途中でシグナルが来ると即座に起床して on_signal() が自動実行され、false が返る
+            if (sleep_ms(5000)) {
+                // 5秒満了時の処理
+            }
+        }
+    }
+
+    void on_signal(uint32_t sig) override {
+        if (sig == SIG_WAKE_NOW) {
+            // スリープを中断して即座に実行される処理
+        }
+    }
+};
+
+// 他タスク内での動的な生成・シグナル送信・破棄の例
+void manager_task(void*) {
+    {
+        MyWorker worker;
+        worker.start(tk::PRIO_NORMAL);       // 動的に起動
+
+        tk::sleep_ms(500);
+        worker.send_signal(SIG_WAKE_NOW);    // 5秒スリープを即座に中断して on_signal() を発火!
+
+        worker.stop();                       // SIG_STOP 送信 -> 一時停止 (Terminated)
+        worker.join();                       // 停止完了を待機
+    } // スコープを抜けて ~MyWorker() が呼ばれ、タスクスロットが安全に解放 (Free) される
+}
+```
+
+---
+
 ## セマフォ / ミューテックス (`sync.hpp`)
 
 スレッド間の排他制御・同期プリミティブとして以下が用意されています。
@@ -289,6 +341,40 @@ int main(void) {
 
 > **Point**: The sender does not need to know the instance or name of the Actor. The EventHub delivers messages directly to the appropriate Actors based solely on the message type.
 
+## Dynamic Threads and Signals (`thread.hpp`)
+
+By inheriting from `tk::Thread<StackSize>`, you can define threads with an embedded member stack, early sleep interruption via signals, and automatic `on_signal()` callbacks (`tk::Actor` also inherits from `tk::Thread`).
+
+- **Start anytime**: Can be started both before `tk::start()` (static) and dynamically after the scheduler is running.
+- **User-defined signals**: In addition to system signals (`tk::SIG_STOP`, `tk::SIG_INTR`), custom signals can be defined starting from `tk::SIG_USER` (16).
+- **Safe RAII lifecycle**: When stopped via `stop()` or when `run()` returns, the thread halts in `State::Terminated` while retaining its slot. The slot is safely reclaimed (`State::Free`) only when the instance goes out of scope or is deleted (`~Thread()`).
+
+```cpp
+#include "tinykernel/thread.hpp"
+
+enum AppSignal : uint32_t {
+    SIG_WAKE_NOW = tk::SIG_USER,
+};
+
+class MyWorker : public tk::Thread<1024> {
+protected:
+    void run() override {
+        while (!should_stop()) {
+            // Sleeps for 5s; if a signal arrives, wakes immediately, runs on_signal(), and returns false
+            if (sleep_ms(5000)) {
+                // Periodic work after full 5s sleep
+            }
+        }
+    }
+
+    void on_signal(uint32_t sig) override {
+        if (sig == SIG_WAKE_NOW) {
+            // Handled immediately upon breaking sleep
+        }
+    }
+};
+```
+
 ## Semaphores / Mutexes (`sync.hpp`)
 
 The following primitives are provided for mutual exclusion and synchronization between threads:

+ 74 - 2
main.cpp

@@ -162,10 +162,80 @@ static void task_preempt_demo(void* arg) {
     }
 }
 
+// ---------------------------------------------------------------------------
+// Dynamic Thread & Signal demonstration:
+// User-defined signals starting from tk::SIG_USER
+enum AppSignal : uint32_t {
+    SIG_WAKE_ACTION = tk::SIG_USER,
+    SIG_STATUS_DUMP = tk::SIG_USER + 1,
+};
+
+class EphemeralWorker : public tk::Thread<2048> {
+public:
+    explicit EphemeralWorker(uint32_t id) : id_(id) {}
+
+protected:
+    void on_start() override {
+        P("Worker#%lu [slot=%u]: started!\n", (unsigned long)id_, (unsigned)handle().slot);
+    }
+
+    void run() override {
+        while (!should_stop()) {
+            // Long 5000ms sleep; will be broken immediately when a signal arrives!
+            if (sleep_ms(5000)) {
+                P("Worker#%lu: 5000ms sleep completed naturally\n", (unsigned long)id_);
+            }
+        }
+    }
+
+    void on_signal(uint32_t sig) override {
+        if (sig == SIG_WAKE_ACTION) {
+            P("Worker#%lu: >>> interrupted sleep! Received SIG_WAKE_ACTION at %llu ms\n",
+              (unsigned long)id_, (unsigned long long)tk::now_ms());
+        } else if (sig == SIG_STATUS_DUMP) {
+            P("Worker#%lu: >>> interrupted sleep! Received SIG_STATUS_DUMP at %llu ms\n",
+              (unsigned long)id_, (unsigned long long)tk::now_ms());
+        } else if (sig == tk::SIG_STOP) {
+            P("Worker#%lu: >>> received SIG_STOP, exiting run() loop...\n", (unsigned long)id_);
+        }
+    }
+
+    void on_stop() override {
+        P("Worker#%lu [slot=%u]: halted (Terminated), waiting for destructor cleanup\n",
+          (unsigned long)id_, (unsigned)handle().slot);
+    }
+
+private:
+    uint32_t id_;
+};
+
+static void task_thread_manager(void* arg) {
+    (void)arg;
+    uint32_t gen = 1;
+    for (;;) {
+        tk::sleep_ms(1500);
+        {
+            // Scoped dynamic thread: stack lives in 'worker', slot is freed when scope exits!
+            EphemeralWorker worker(gen++);
+            worker.start(tk::PRIO_NORMAL);
+
+            tk::sleep_ms(600);
+            worker.send_signal(SIG_WAKE_ACTION);  // Breaks 5000ms sleep early!
+
+            tk::sleep_ms(600);
+            worker.send_signal(SIG_STATUS_DUMP);  // Breaks 5000ms sleep early!
+
+            tk::sleep_ms(400);
+            worker.stop();                        // Sends SIG_STOP -> halts in Terminated
+            worker.join();
+        }  // ~EphemeralWorker() runs here -> reclaims task slot back to State::Free!
+    }
+}
+
 int main() {
     stdio_init_all();
 
-    printf("tinykernel: registering dynamic tasks...\n");
+    printf("tinykernel: registering tasks...\n");
     g_led_actor.start(tk::PRIO_NORMAL);
     g_log_actor.start(tk::PRIO_NORMAL);
     auto h_sender  = tk::create(task_sender, nullptr, tk::PRIO_NORMAL);
@@ -174,11 +244,13 @@ int main() {
     auto h_waiter  = tk::create(task_sem_waiter, nullptr, tk::PRIO_HIGH);
     auto h_sig     = tk::create(task_sem_signaler, nullptr, tk::PRIO_NORMAL);
     auto h_preempt = tk::create(task_preempt_demo, nullptr, tk::PRIO_NORMAL);
+    auto h_thrmgr  = tk::create(task_thread_manager, nullptr, tk::PRIO_NORMAL);
 
     if (!g_led_actor.handle().is_valid() || !g_log_actor.handle().is_valid() ||
         !h_sender.is_valid() ||
         !h_mtx_a.is_valid() || !h_mtx_b.is_valid() ||
-        !h_waiter.is_valid() || !h_sig.is_valid() || !h_preempt.is_valid()) {
+        !h_waiter.is_valid() || !h_sig.is_valid() ||
+        !h_preempt.is_valid() || !h_thrmgr.is_valid()) {
         printf("tinykernel: task creation FAILED\n");
         for (;;) {
         }

+ 67 - 42
tinykernel/include/tinykernel/actor.hpp

@@ -1,6 +1,7 @@
 #pragma once
 
 #include "kernel.hpp"
+#include "thread.hpp"
 #include <cstdint>
 #include <cstddef>
 #include <type_traits>
@@ -44,16 +45,16 @@ public:
      */
     static bool subscribe(void* actor, bool (*post_fn)(void*, const MsgType&)) {
         uint32_t save = detail::sched_lock();
-        if (count_ >= MaxSubscribers) {
-            detail::sched_unlock(save);
-            return false;
-        }
         for (size_t i = 0; i < count_; ++i) {
             if (subs_[i].actor == actor) {
                 detail::sched_unlock(save);
                 return true;  // 登録済み
             }
         }
+        if (count_ >= MaxSubscribers) {
+            detail::sched_unlock(save);
+            return false;
+        }
         subs_[count_++] = {actor, post_fn};
         detail::sched_unlock(save);
         return true;
@@ -148,13 +149,18 @@ inline size_t publish(uint32_t id, uintptr_t arg = 0) {
 }
 
 /**
- * @brief メッセージ駆動型タスク (アクター) の基底クラス
+ * @brief メッセージ駆動型アクターの基底クラス (`tk::Thread` 派生)
  * 
- * 固定長リングバッファ付きのメッセージキューを持ち、メッセージ到着時に自動起床して
- * `on_message()` を実行します。メッセージが無い時は CPU を消費せずブロック待機します。
+ * `tk::Thread<StackSize>` を継承しており、固定長リングバッファ付きのメッセージキューを持ちます。
+ * メッセージ到着時に自動起床して `on_message()` を実行し、メッセージが無い時は CPU を消費せずブロック待機します。
+ * 
+ * - `tk::start()` の前 (静的登録) でも、スケジューラ起動後 (動的生成) でも `start()` で起動できます。
+ * - `send_signal()` による緊急シグナルの受信 (`on_signal()`) や、`stop()` による安全な停止にも対応しています。
+ * - 停止時は `State::Terminated` で待機し、スコープ離脱や `delete` (デストラクタ) 時にスロットが解放されます。
  * 
  * @tparam MsgType 受信するメッセージの型 (デフォルト: tk::Message)
  * @tparam QueueSize メッセージキューの最大保持件数 (デフォルト: 8)
+ * @tparam StackSize スタックサイズ (バイト単位、デフォルト: TK_DEFAULT_STACK_SIZE)
  * 
  * @code
  * struct SensorMsg { float temp; };
@@ -170,30 +176,54 @@ inline size_t publish(uint32_t id, uintptr_t arg = 0) {
  * };
  * @endcode
  */
-template <typename MsgType = Message, size_t QueueSize = 8>
-class Actor {
+template <typename MsgType = Message,
+          size_t QueueSize = 8,
+          size_t StackSize = TK_DEFAULT_STACK_SIZE>
+class Actor : public Thread<StackSize> {
     static_assert(QueueSize > 0, "QueueSize must be greater than 0");
 
 public:
     Actor() = default;
-    virtual ~Actor() {
+
+    /**
+     * @brief デストラクタ
+     * 
+     * `EventHub` からの購読を解除し、アクターが実行中であれば停止 (`SIG_STOP`) と完了待ち (`join`) を
+     * 行ってからタスクスロットを解放 (`State::Free`) します。
+     */
+    ~Actor() override {
         EventHub<MsgType>::unsubscribe(this);
+        this->release_slot();
     }
 
     /**
-     * @brief Actor をタスクとして生成し、EventHub へ自動登録します
+     * @brief Actor をタスクとして起動 (または再起動) し、`EventHub` へ自動登録します
      * 
-     * `tk::start()` を呼び出す前に登録する必要があります。
+     * `tk::start()` の前でも、スケジューラ起動後でも動的に呼び出すことができます。
      * 
      * @param prio タスク優先度 (省略時: tk::PRIO_NORMAL)
-     * @return true 登録成功, false 登録失敗 (タスク枠上限など)
+     * @return true 起動成功, false 起動失敗 (すでに実行中、またはタスク枠上限など)
      */
-    bool start(uint32_t prio = PRIO_NORMAL) {
+    bool start(uint32_t prio = PRIO_NORMAL) override {
         EventHub<MsgType>::subscribe(this, [](void* obj, const MsgType& m) {
-            return static_cast<Actor<MsgType, QueueSize>*>(obj)->post(m);
+            return static_cast<Actor<MsgType, QueueSize, StackSize>*>(obj)->post(m);
         });
-        handle_ = tk::create(task_entry, this, prio);
-        return handle_.is_valid();
+        if (!Thread<StackSize>::start(prio)) {
+            EventHub<MsgType>::unsubscribe(this);
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * @brief Actor の購読を解除し、停止シグナル (`tk::SIG_STOP`) を送ってイベントループを終了させます
+     * 
+     * メッセージ待ち (`State::Blocked`) や `sleep_ms()` 中であっても即座に起床して停止 (`State::Terminated`) します。
+     * スロット自体はデストラクタが呼ばれるまで保持され、スコープ内であれば再度 `start()` で再開可能です。
+     */
+    void stop() override {
+        EventHub<MsgType>::unsubscribe(this);
+        Thread<StackSize>::stop();
     }
 
     /**
@@ -215,7 +245,7 @@ public:
         count_++;
         if (waiting_) {
             waiting_ = false;
-            detail::wake_task_locked(handle_);
+            detail::wake_task_locked(this->handle_);
         }
         detail::sched_unlock(save);
         return true;
@@ -238,7 +268,7 @@ public:
         count_++;
         if (waiting_) {
             waiting_ = false;
-            detail::wake_task_locked(handle_);
+            detail::wake_task_locked(this->handle_);
         }
         detail::sched_unlock(save);
         return true;
@@ -257,18 +287,7 @@ public:
         return post(Message{id, arg});
     }
 
-    /** この Actor のタスクハンドルを取得 */
-    TaskHandle handle() const { return handle_; }
-
 protected:
-    /**
-     * @brief タスク起動時、イベントループ突入直前に1度だけ呼ばれる初期化関数
-     * 
-     * タスクコンテキスト内で実行されるため、`tk::sleep_ms()` を使った初期化ウェイトも安全に行えます。
-     * 必要に応じて派生クラスでオーバーライドします。
-     */
-    virtual void on_start() {}
-
     /**
      * @brief メッセージ受信時に呼ばれるハンドラ関数
      * 
@@ -279,28 +298,34 @@ protected:
      */
     virtual void on_message(const MsgType& msg) = 0;
 
-private:
-    static void task_entry(void* arg) {
-        static_cast<Actor<MsgType, QueueSize>*>(arg)->event_loop();
-    }
-
-    void event_loop() {
-        on_start();
+    /**
+     * @brief `Thread::run()` の実装 (メッセージ待ち受け&ディスパッチループ)
+     * 
+     * メッセージ到着、または `send_signal()` / `stop()` によって起床します。
+     */
+    void run() override {
         for (;;) {
             MsgType msg{};
             uint32_t save = detail::sched_lock();
-            while (count_ == 0) {
+            while (count_ == 0 && !detail::has_pending_signal_or_stop_locked(this->handle_)) {
                 waiting_ = true;
                 detail::block_current_task_locked();
                 detail::sched_unlock(save);
-                // コンテキストスイッチされ、他のタスクが走る。
-                // wake_task_locked で起こされたらここに戻る。
+                // post() または send_signal() / stop() で起床
                 save = detail::sched_lock();
             }
+            waiting_ = false;
+            if (detail::has_pending_signal_or_stop_locked(this->handle_)) {
+                detail::sched_unlock(save);
+                this->check_signals();
+                if (this->should_stop()) {
+                    break;
+                }
+                continue;
+            }
             msg = std::move(queue_[head_]);
             head_ = (head_ + 1) % QueueSize;
             count_--;
-            waiting_ = false;
             detail::sched_unlock(save);
 
             // メッセージディスパッチ(ロック解除状態で実行)
@@ -308,12 +333,12 @@ private:
         }
     }
 
+private:
     MsgType queue_[QueueSize]{};
     size_t head_{0};
     size_t tail_{0};
     size_t count_{0};
     bool waiting_{false};
-    TaskHandle handle_{};
 };
 
 }  // namespace tk

+ 43 - 4
tinykernel/include/tinykernel/kernel.hpp

@@ -52,6 +52,24 @@ enum Prio : uint32_t {
     PRIO_LOW      = 13,  ///< 低優先度
 };
 
+/**
+ * @brief スレッド・アクター制御用のシステム定義シグナル
+ * 
+ * ユーザー定義シグナルは `tk::SIG_USER` (16) 以降の任意の値 (uint32_t) を使用できます。
+ * @code
+ * enum MySignal : uint32_t {
+ *     SIG_RELOAD = tk::SIG_USER,
+ *     SIG_FLUSH  = tk::SIG_USER + 1,
+ * };
+ * @endcode
+ */
+enum Signal : uint32_t {
+    SIG_NONE = 0,   ///< シグナルなし
+    SIG_STOP = 1,   ///< スレッド・アクターの停止要求
+    SIG_INTR = 2,   ///< スリープ・待機の中断要求 (終了はしない)
+    SIG_USER = 16,  ///< ユーザー定義シグナルの開始番号 (16以降を自由に使用可能)
+};
+
 /**
  * @brief タスクの識別ハンドル
  */
@@ -65,7 +83,7 @@ struct TaskHandle {
 /**
  * @brief タスクを生成・登録します
  * 
- * tk::start() を呼び出す前に登録する必要があります。
+ * `tk::start()` の前でも、スケジューラ起動後でも動的に呼び出すことができます。
  * 
  * @param fn タスク関数ポインタ (void (*)(void* arg))
  * @param arg タスク関数に渡すユーザー引数ポインタ (省略時: nullptr)
@@ -77,7 +95,7 @@ TaskHandle create(TaskFn fn, void* arg = nullptr, uint32_t prio = PRIO_NORMAL);
 /**
  * @brief tinykernel スケジューラを起動します
  * 
- * Core 0 のスケジューラを開始し、Core 1 を自動ブートしてデュアルコア並行実行を開始します。
+ * Core 0 のスケジューラを開始し、SMP有効時は Core 1 を自動ブートしてデュアルコア並行実行を開始します。
  * この関数から制御が戻ることはありません (Never returns)。
  */
 void start();
@@ -85,6 +103,8 @@ void start();
 /**
  * @brief 現在実行中のタスクを一時中断し、同優先度の他のタスクへ CPU を譲渡します
  * 
+ * 保留中のシグナルがある場合は、復帰時にシグナルコールバック (`on_signal`) が自動実行されます。
+ * 
  * @note タスクコンテキスト内から呼び出す必要があります。
  */
 void yield();
@@ -93,14 +113,18 @@ void yield();
  * @brief 現在のタスクを指定時間スリープさせます
  * 
  * スリープ中はタスクが休止状態になり、CPU は他のタスクへ自動的に割り当てられます (CPU消費ゼロ)。
+ * スリープ中に外部からシグナルが送信された場合は、即座にスリープを中断して起床し、
+ * シグナルコールバック (`on_signal`) を自動実行した上で `false` を返します。
  * 
  * @param ms スリープする時間 (ミリ秒)
+ * @return true 指定時間が満了して正常に起床した場合
+ * @return false シグナルによってスリープが早期中断された場合
  * @note タスクコンテキスト内から呼び出す必要があります。
  */
-void sleep_ms(uint32_t ms);
+bool sleep_ms(uint32_t ms);
 
 /**
- * @brief 現在のタスクを終了・破棄します
+ * @brief 現在のタスクを終了し、停止状態 (`Terminated`) に遷移させます
  * 
  * @param code 終了コード (予約用、省略時: 0)
  * @note タスク関数の末尾に到達した場合は自動的に呼ばれます。
@@ -174,6 +198,8 @@ public:
 using task_mask_t = uint32_t;
 
 namespace detail {
+using SignalHandlerFn = void (*)(void* obj, uint32_t sig);
+
 void static_register(const char* name, TaskFn fn, uint32_t prio);
 uint32_t sched_lock();
 void sched_unlock(uint32_t save);
@@ -181,6 +207,19 @@ void block_current_task_locked();
 void wake_task_locked(TaskHandle handle);
 uint8_t current_task_slot();
 uint8_t find_highest_prio_waiter(task_mask_t wait_mask);
+
+// Thread / Actor lifecycle and signal helpers
+bool start_or_restart_task(TaskHandle* inout_handle, TaskFn fn, void* arg,
+                           SignalHandlerFn sig_fn, uint32_t prio,
+                           uint8_t* stack_base, size_t stack_size,
+                           const char* name);
+bool send_task_signal(TaskHandle handle, uint32_t sig);
+void dispatch_pending_signal_current();
+bool has_pending_signal_or_stop_locked(TaskHandle handle);
+bool is_task_stop_requested(TaskHandle handle);
+bool is_task_running(TaskHandle handle);
+void join_task(TaskHandle handle);
+void release_task_slot(TaskHandle* inout_handle);
 }  // namespace detail
 
 }  // namespace tk

+ 229 - 0
tinykernel/include/tinykernel/thread.hpp

@@ -0,0 +1,229 @@
+#pragma once
+
+#include "kernel.hpp"
+#include <cstdint>
+#include <cstddef>
+
+namespace tk {
+
+/**
+ * @brief 独自スタックとシグナル受信機構を備えたスレッド基底クラス
+ * 
+ * 派生クラスで `run()` をオーバーライドしてスレッドの処理を記述します。
+ * シグナルを受信したい場合は `on_signal(uint32_t sig)` をオーバーライドします。
+ * 
+ * 主な特徴:
+ * - **静的メンバスタック**: スタック (`StackSize` バイト) をインスタンス内部に保持するため `malloc` 不要・断片化ゼロ。
+ * - **起動時期を問わない**: `tk::start()` の前 (静的登録) でも、スケジューラ起動後 (動的一時スレッド) でも `start()` 可能。
+ * - **スリープ早期中断+自動コールバック**: `sleep_ms()` 中に `send_signal()` を受けると即座に起床し、自動で `on_signal()` を実行。
+ * - **安全なRAIIライフサイクル**: `stop()` や `run()` 完了時は一時停止 (`State::Terminated`) 状態となり、
+ *   インスタンスがスコープから外れるか `delete` されてデストラクタが呼ばれた時点で初めてスロットが解放 (`State::Free`) されます。
+ *   スコープ内であれば、停止したスレッドに対して再度 `start()` を呼んで再実行することも可能です。
+ * 
+ * @tparam StackSize スレッドのスタックサイズ (バイト単位、8の倍数、デフォルト: TK_DEFAULT_STACK_SIZE)
+ * 
+ * @code
+ * enum MySignals : uint32_t {
+ *     SIG_PING = tk::SIG_USER,
+ * };
+ * 
+ * class WorkerThread : public tk::Thread<2048> {
+ * protected:
+ *     void run() override {
+ *         while (!should_stop()) {
+ *             // シグナルが来たら5秒待たずに即起床し、自動で on_signal() が呼ばれてから false が返る
+ *             if (sleep_ms(5000)) {
+ *                 // 5秒経過時の定期処理
+ *             }
+ *         }
+ *     }
+ *     void on_signal(uint32_t sig) override {
+ *         if (sig == SIG_PING) {
+ *             // シグナル受信時の処理
+ *         }
+ *     }
+ * };
+ * @endcode
+ */
+template <size_t StackSize = TK_DEFAULT_STACK_SIZE>
+class Thread {
+    static_assert(StackSize >= 256, "StackSize must be at least 256 bytes");
+    static_assert((StackSize % 8) == 0, "StackSize must be a multiple of 8 bytes");
+
+public:
+    Thread() = default;
+
+    /**
+     * @brief デストラクタ
+     * 
+     * スレッドが実行中の場合は停止シグナル (`SIG_STOP`) を送って完了 (`join`) を待ち、
+     * CPU から完全に降りたことを確認してからカーネルのタスクスロットを解放 (`State::Free`) します。
+     */
+    virtual ~Thread() {
+        release_slot();
+    }
+
+    Thread(const Thread&) = delete;
+    Thread& operator=(const Thread&) = delete;
+
+    /**
+     * @brief スレッドを起動 (または停止状態から再起動) します
+     * 
+     * `tk::start()` の前でも後でも呼び出すことができます。
+     * 一度終了 (`Terminated`) したインスタンスに対して再度呼び出すと、同じスロットで最初から再実行されます。
+     * 
+     * @param prio タスク優先度 (省略時: tk::PRIO_NORMAL)
+     * @return true 起動成功, false 起動失敗 (すでに実行中、または空きスロットなし)
+     */
+    virtual bool start(uint32_t prio = PRIO_NORMAL) {
+        return detail::start_or_restart_task(
+            &handle_,
+            &Thread::task_entry,
+            this,
+            &Thread::signal_trampoline,
+            prio,
+            stack_,
+            StackSize,
+            "thr"
+        );
+    }
+
+    /**
+     * @brief このスレッドへシグナルを送信します
+     * 
+     * スレッドが `sleep_ms()` やメッセージ待機で休止中の場合、即座に休止を打ち切って起床させ、
+     * スレッド自身のコンテキストで `on_signal(sig)` を自動実行します。
+     * 
+     * @param sig 送信するシグナル (`tk::SIG_STOP`, `tk::SIG_INTR`, または `tk::SIG_USER` 以降のユーザー定義値)
+     * @return true 送信成功, false スレッドが未起動または既に停止済み
+     */
+    bool send_signal(uint32_t sig) {
+        return detail::send_task_signal(handle_, sig);
+    }
+
+    /**
+     * @brief スレッドに停止要求 (`tk::SIG_STOP`) を送ります
+     * 
+     * スリープやブロック待機を即座に解除し、`should_stop()` を `true` にします。
+     * スレッドが終了してもスロットは `State::Terminated` として保持され、デストラクタが呼ばれるまで他タスクに横取りされません。
+     */
+    virtual void stop() {
+        detail::send_task_signal(handle_, SIG_STOP);
+    }
+
+    /**
+     * @brief スレッドの処理が完了して停止 (`State::Terminated`) するまで待機します
+     */
+    void join() {
+        detail::join_task(handle_);
+    }
+
+    /**
+     * @brief スレッドが現在稼働中かどうかを判定します
+     * @return true 稼働中 (`Ready`, `Running`, `Sleeping`, `Blocked`)
+     * @return false 未起動または停止済み (`Terminated`, `Free`)
+     */
+    bool is_running() const {
+        return detail::is_task_running(handle_);
+    }
+
+    /**
+     * @brief 停止要求 (`stop()` または `SIG_STOP`) が出されているかどうかを判定します
+     * 
+     * `run()` のループ条件 (`while (!should_stop())`) で使用します。
+     */
+    bool should_stop() const {
+        return detail::is_task_stop_requested(handle_);
+    }
+
+    /** `should_stop()` の別名エイリアス */
+    bool is_interrupted() const {
+        return should_stop();
+    }
+
+    /** このスレッドのタスクハンドルを取得 */
+    TaskHandle handle() const { return handle_; }
+
+protected:
+    /**
+     * @brief スレッド開始時、`run()` の直前に1度だけ呼ばれる初期化フック
+     */
+    virtual void on_start() {}
+
+    /**
+     * @brief スレッドのメイン処理 (派生クラスで実装)
+     */
+    virtual void run() = 0;
+
+    /**
+     * @brief シグナル受信時に自動実行されるコールバック関数
+     * 
+     * `sleep_ms()` の早期中断時や `yield()`、`check_signals()` 呼び出し時に
+     * このスレッド自身のコンテキストで実行されます。
+     * 
+     * @param sig 受信したシグナル値
+     */
+    virtual void on_signal(uint32_t sig) { (void)sig; }
+
+    /**
+     * @brief `run()` が終了した直後に1度だけ呼ばれる終了フック
+     */
+    virtual void on_stop() {}
+
+    /**
+     * @brief 指定ミリ秒スリープします (シグナルによる早期中断&自動コールバック付き)
+     * 
+     * @param ms スリープ時間 (ミリ秒)
+     * @return true 指定時間が満了して起床した
+     * @return false シグナルを受信して早期起床した (`on_signal` 実行済み)
+     */
+    bool sleep_ms(uint32_t ms) {
+        return tk::sleep_ms(ms);
+    }
+
+    /**
+     * @brief 同優先度の他タスクへ CPU を譲渡し、保留中のシグナルがあれば `on_signal()` を実行します
+     */
+    void yield() {
+        tk::yield();
+    }
+
+    /**
+     * @brief スリープを挟まない計算ループ中に、保留中のシグナルがあれば `on_signal()` を実行します
+     */
+    void check_signals() {
+        detail::dispatch_pending_signal_current();
+    }
+
+    /**
+     * @brief 派生クラスのデストラクタから安全にスレッド停止・スロット解放を行うヘルパー
+     */
+    void release_slot() {
+        if (handle_.is_valid()) {
+            detail::release_task_slot(&handle_);
+        }
+    }
+
+    TaskHandle handle_{};
+
+private:
+    static void task_entry(void* arg) {
+        auto* self = static_cast<Thread<StackSize>*>(arg);
+        self->on_start();
+        if (!self->should_stop()) {
+            self->run();
+        }
+        self->on_stop();
+        tk::exit(0);
+    }
+
+    static void signal_trampoline(void* obj, uint32_t sig) {
+        auto* self = static_cast<Thread<StackSize>*>(obj);
+        self->on_signal(sig);
+    }
+
+    alignas(8) uint8_t stack_[StackSize]{};
+};
+
+}  // namespace tk
+

+ 236 - 30
tinykernel/src/scheduler.cpp

@@ -21,11 +21,17 @@ struct TaskSlot {
     State state;
     tk::TaskFn fn;
     void* arg;
+    tk::detail::SignalHandlerFn sig_fn;
     const char* name;
-    uint32_t prio;      // 0..15, lower is higher priority
-    uintptr_t sp;       // saved SP (points at the core8 frame); 0 = not started yet
+    uint32_t prio;          // 0..15, lower is higher priority
+    uintptr_t sp;           // saved SP (points at the core8 frame); 0 = not started yet
+    uint8_t* stack_base;    // nullptr = use default g_stacks[slot]
+    size_t stack_size;      // 0 = use TK_DEFAULT_STACK_SIZE
     int64_t wake_tick;
     int32_t slice_left;
+    uint32_t pending_signal;// 0 = no pending signal
+    bool sleep_interrupted; // true if sleep/block was broken early by a signal
+    bool stop_requested;    // true if SIG_STOP was sent
     bool is_idle;
     FpuCtx fpu;
 };
@@ -65,6 +71,21 @@ static inline uint32_t sched_lock(void) {
 
 static inline void sched_unlock(uint32_t save) {
     spin_unlock(g_sched_lock, save);
+    __asm__ volatile("isb" ::: "memory");
+}
+
+static inline uintptr_t init_task_frame(uint32_t slot_idx, TaskSlot& s) {
+    uint8_t* base = s.stack_base ? s.stack_base : reinterpret_cast<uint8_t*>(&g_stacks[slot_idx][0]);
+    uint32_t sz   = s.stack_size ? (uint32_t)s.stack_size : (uint32_t)TK_DEFAULT_STACK_SIZE;
+    return tk_port_make_frame(base, sz, reinterpret_cast<void*>(s.fn), s.arg);
+}
+
+static inline bool is_slot_on_cpu_locked(uint8_t slot) {
+    if (!g_started) return false;
+    for (uint32_t c = 0; c < TK_NUM_CORES; ++c) {
+        if (g_cur[c] == slot) return true;
+    }
+    return false;
 }
 
 static void idle_fn(void* arg) {
@@ -79,6 +100,13 @@ static inline bool is_runnable(uint32_t slot_idx, uint32_t core) {
     if (s.is_idle) {
         return slot_idx == core; // Core 0 only runs idle 0, Core 1 only runs idle 1
     }
+#if TK_NUM_CORES > 1
+    for (uint32_t c = 0; c < TK_NUM_CORES; ++c) {
+        if (c != core && g_cur[c] == slot_idx) {
+            return false; // Still on the other CPU core (e.g. entering PendSV)
+        }
+    }
+#endif
     return s.state == State::Ready;
 }
 
@@ -172,8 +200,7 @@ extern "C" uintptr_t tk_switch_context(uintptr_t cur_sp) {
     uint32_t next = select_next(core);
     TaskSlot& nxt = g_slots[next];
     if (nxt.sp == 0) {
-        nxt.sp = tk_port_make_frame(reinterpret_cast<uint8_t*>(&g_stacks[next][0]),
-                                    TK_DEFAULT_STACK_SIZE, reinterpret_cast<void*>(nxt.fn), nxt.arg);
+        nxt.sp = init_task_frame(next, nxt);
     }
     nxt.state = State::Running;
     memcpy(&tk_fpu_scratch[core], &nxt.fpu, sizeof(nxt.fpu));
@@ -188,36 +215,182 @@ extern "C" uintptr_t tk_switch_context(uintptr_t cur_sp) {
 namespace tk::detail {
 
 void static_register(const char* name, TaskFn fn, uint32_t prio) {
+    TaskHandle h{0xFF};
+    start_or_restart_task(&h, fn, nullptr, nullptr, prio, nullptr, 0, name);
+}
+
+bool start_or_restart_task(TaskHandle* inout_handle, TaskFn fn, void* arg,
+                           SignalHandlerFn sig_fn, uint32_t prio,
+                           uint8_t* stack_base, size_t stack_size,
+                           const char* name) {
+    if (!inout_handle) return false;
     if (prio >= kIdlePrio) prio = 14u;
+
+    // Case 1: Instance already owns a slot (restarting a Terminated thread/actor)
+    if (inout_handle->is_valid() && inout_handle->slot < TK_MAX_TASKS) {
+        uint8_t slot = inout_handle->slot;
+        for (;;) {
+            uint32_t save = ::sched_lock();
+            State st = g_slots[slot].state;
+            if (st != State::Terminated && st != State::Free) {
+                ::sched_unlock(save);
+                return false;  // Already running
+            }
+            if (!is_slot_on_cpu_locked(slot)) {
+                TaskSlot& s = g_slots[slot];
+                s = TaskSlot{};
+                s.fn = fn;
+                s.arg = arg;
+                s.sig_fn = sig_fn;
+                s.name = name ? name : "thr";
+                s.prio = prio;
+                s.stack_base = stack_base;
+                s.stack_size = stack_size;
+                s.state = State::Ready;
+                if (g_started) {
+                    tk_port_trigger_pendsv();
+                    __sev();
+                }
+                ::sched_unlock(save);
+                return true;
+            }
+            ::sched_unlock(save);
+            tk::yield();
+        }
+    }
+
+    // Case 2: Allocate a free slot
+    uint32_t save = ::sched_lock();
     for (uint32_t i = TK_NUM_CORES; i < TK_MAX_TASKS; ++i) {
-        if (g_slots[i].state == State::Free) {
-            g_slots[i] = TaskSlot{};
-            g_slots[i].fn = fn;
-            g_slots[i].name = name;
-            g_slots[i].prio = prio;
-            g_slots[i].state = State::Ready;
-            return;
+        if (g_slots[i].state == State::Free && !is_slot_on_cpu_locked((uint8_t)i)) {
+            TaskSlot& s = g_slots[i];
+            s = TaskSlot{};
+            s.fn = fn;
+            s.arg = arg;
+            s.sig_fn = sig_fn;
+            s.name = name ? name : "thr";
+            s.prio = prio;
+            s.stack_base = stack_base;
+            s.stack_size = stack_size;
+            s.state = State::Ready;
+            inout_handle->slot = (uint8_t)i;
+            if (g_started) {
+                tk_port_trigger_pendsv();
+                __sev();
+            }
+            ::sched_unlock(save);
+            return true;
         }
     }
+    ::sched_unlock(save);
+    return false;
+}
+
+bool send_task_signal(TaskHandle handle, uint32_t sig) {
+    if (handle.slot >= TK_MAX_TASKS || sig == SIG_NONE) return false;
+    uint32_t save = ::sched_lock();
+    TaskSlot& s = g_slots[handle.slot];
+    if (s.state == State::Free || s.state == State::Terminated) {
+        ::sched_unlock(save);
+        return false;
+    }
+    if (sig == SIG_STOP) {
+        s.stop_requested = true;
+    }
+    if (s.pending_signal != SIG_STOP) {
+        s.pending_signal = sig;
+    }
+    if (s.state == State::Sleeping || s.state == State::Blocked) {
+        s.sleep_interrupted = true;
+        s.state = State::Ready;
+        if (g_started) {
+            tk_port_trigger_pendsv();
+            __sev();
+        }
+    }
+    ::sched_unlock(save);
+    return true;
+}
+
+void dispatch_pending_signal_current() {
+    if (!g_started) return;
+    uint32_t save = ::sched_lock();
+    TaskSlot& cur = g_slots[g_cur[get_core_num()]];
+    uint32_t sig = cur.pending_signal;
+    cur.pending_signal = 0;
+    SignalHandlerFn sig_fn = cur.sig_fn;
+    void* arg = cur.arg;
+    ::sched_unlock(save);
+
+    if (sig != 0 && sig_fn != nullptr) {
+        sig_fn(arg, sig);
+    }
+}
+
+bool has_pending_signal_or_stop_locked(TaskHandle handle) {
+    if (handle.slot >= TK_MAX_TASKS) return false;
+    const TaskSlot& s = g_slots[handle.slot];
+    return s.pending_signal != 0 || s.stop_requested;
+}
+
+bool is_task_stop_requested(TaskHandle handle) {
+    if (handle.slot >= TK_MAX_TASKS) return true;
+    uint32_t save = ::sched_lock();
+    bool stopped = g_slots[handle.slot].stop_requested ||
+                   g_slots[handle.slot].state == State::Terminated ||
+                   g_slots[handle.slot].state == State::Free;
+    ::sched_unlock(save);
+    return stopped;
+}
+
+bool is_task_running(TaskHandle handle) {
+    if (handle.slot >= TK_MAX_TASKS) return false;
+    uint32_t save = ::sched_lock();
+    State st = g_slots[handle.slot].state;
+    bool running = (st != State::Free && st != State::Terminated) || is_slot_on_cpu_locked(handle.slot);
+    ::sched_unlock(save);
+    return running;
+}
+
+void join_task(TaskHandle handle) {
+    if (!handle.is_valid() || handle.slot >= TK_MAX_TASKS) return;
+    if (!g_started) return;
+    if (g_cur[get_core_num()] == handle.slot) return;  // Avoid self-join
+
+    for (;;) {
+        uint32_t save = ::sched_lock();
+        State st = g_slots[handle.slot].state;
+        bool on_cpu = is_slot_on_cpu_locked(handle.slot);
+        ::sched_unlock(save);
+
+        if ((st == State::Terminated || st == State::Free) && !on_cpu) {
+            break;
+        }
+        tk::sleep_ms(1);
+    }
+}
+
+void release_task_slot(TaskHandle* inout_handle) {
+    if (!inout_handle || !inout_handle->is_valid()) return;
+    uint8_t slot = inout_handle->slot;
+    if (slot >= TK_MAX_TASKS) {
+        inout_handle->slot = 0xFF;
+        return;
+    }
+    send_task_signal(*inout_handle, SIG_STOP);
+    join_task(*inout_handle);
+
+    uint32_t save = ::sched_lock();
+    g_slots[slot] = TaskSlot{};  // State::Free
+    ::sched_unlock(save);
+    inout_handle->slot = 0xFF;
 }
 
 }  // namespace tk::detail
 
 tk::TaskHandle tk::create(TaskFn fn, void* arg, uint32_t prio) {
     TaskHandle h{0xFF};
-    if (prio >= kIdlePrio) prio = 14u;
-    for (uint32_t i = TK_NUM_CORES; i < TK_MAX_TASKS; ++i) {
-        if (g_slots[i].state == State::Free) {
-            g_slots[i] = TaskSlot{};
-            g_slots[i].fn = fn;
-            g_slots[i].arg = arg;
-            g_slots[i].name = "dyn";
-            g_slots[i].prio = prio;
-            g_slots[i].state = State::Ready;
-            h.slot = (uint8_t)i;
-            return h;
-        }
-    }
+    detail::start_or_restart_task(&h, fn, arg, nullptr, prio, nullptr, 0, "dyn");
     return h;
 }
 
@@ -228,8 +401,7 @@ static void core_start_routine(uint32_t core) {
     uint32_t next = select_next(core);
     TaskSlot& nxt = g_slots[next];
     if (nxt.sp == 0) {
-        nxt.sp = tk_port_make_frame(reinterpret_cast<uint8_t*>(&g_stacks[next][0]),
-                                    TK_DEFAULT_STACK_SIZE, reinterpret_cast<void*>(nxt.fn), nxt.arg);
+        nxt.sp = init_task_frame(next, nxt);
     }
     nxt.state = State::Running;
     memcpy(&tk_fpu_scratch[core], &nxt.fpu, sizeof(nxt.fpu));
@@ -283,20 +455,50 @@ void tk::yield() {
     g_slots[g_cur[core]].slice_left = 0;
     tk_port_trigger_pendsv();
     sched_unlock(save);
+    detail::dispatch_pending_signal_current();
 }
 
-void tk::sleep_ms(uint32_t ms) {
+bool tk::sleep_ms(uint32_t ms) {
     uint64_t ticks = ((uint64_t)ms * TK_TICKS_PER_SEC + 999u) / 1000u;
     if (ticks < 1u) ticks = 1u;
     int32_t sleep_ticks = (int32_t)(ticks > 0x7FFFFFFFu ? 0x7FFFFFFFu : ticks);
 
-    uint32_t core = get_core_num();
     uint32_t save = sched_lock();
-    TaskSlot& cur = g_slots[g_cur[core]];
+    TaskSlot& cur = g_slots[g_cur[get_core_num()]];
+    if (cur.pending_signal != 0 || cur.stop_requested) {
+        uint32_t sig = cur.pending_signal;
+        cur.pending_signal = 0;
+        cur.sleep_interrupted = false;
+        detail::SignalHandlerFn sig_fn = cur.sig_fn;
+        void* arg = cur.arg;
+        sched_unlock(save);
+        if (sig != 0 && sig_fn != nullptr) {
+            sig_fn(arg, sig);
+        }
+        return false;
+    }
+
+    cur.sleep_interrupted = false;
     cur.state = State::Sleeping;
     cur.wake_tick = g_tick + sleep_ticks;
     tk_port_trigger_pendsv();
     sched_unlock(save);
+
+    // Resumed after sleep or early signal wakeup (re-query core in case of SMP migration)
+    save = sched_lock();
+    TaskSlot& resumed = g_slots[g_cur[get_core_num()]];
+    bool interrupted = resumed.sleep_interrupted;
+    resumed.sleep_interrupted = false;
+    uint32_t sig = resumed.pending_signal;
+    resumed.pending_signal = 0;
+    detail::SignalHandlerFn sig_fn = resumed.sig_fn;
+    void* arg = resumed.arg;
+    sched_unlock(save);
+
+    if (sig != 0 && sig_fn != nullptr) {
+        sig_fn(arg, sig);
+    }
+    return !interrupted && (sig == 0);
 }
 
 void tk::exit(int code) {
@@ -305,6 +507,7 @@ void tk::exit(int code) {
     uint32_t save = sched_lock();
     g_slots[g_cur[core]].state = State::Terminated;
     tk_port_trigger_pendsv();
+    __sev();
     sched_unlock(save);
     for (;;) __asm__ volatile("wfi");
 }
@@ -346,7 +549,10 @@ void tk::detail::wake_task_locked(TaskHandle handle) {
     TaskSlot& s = g_slots[handle.slot];
     if (s.state == State::Blocked) {
         s.state = State::Ready;
-        tk_port_trigger_pendsv();
+        if (g_started) {
+            tk_port_trigger_pendsv();
+            __sev();
+        }
     }
 }