Ver Fonte

Fix: シグナルがSemaphore/Mutexの待機を起こしてしまう問題を修正。on_stop()の注意書きを追加。

Satoshi Yoneda há 2 semanas atrás
pai
commit
c3d8be9a54

+ 29 - 3
README.md

@@ -119,6 +119,7 @@ int main(void) {
 - **`tk::start()` の前でも後(走行中)でも起動可能**: 一時的なワーカーを動的に起動できます。
 - **ユーザー定義シグナル**: `tk::SIG_STOP` や `tk::SIG_INTR` のシステムシグナルに加え、`tk::SIG_USER` (16) 以降の任意の値で独自シグナルを定義できます。
 - **安全な RAII ライフサイクル**: `stop()` や `run()` 完了時は一時停止(`State::Terminated`)状態でスロットを保持し、**スコープを抜けるか `delete` でデストラクタが呼ばれた時点で初めてスロットが解放(`State::Free`)** されます。
+- **終了時クリーンアップの保証 (`on_stop`)**: 外部からの `stop()` による停止であっても、`run()` の自然終了であっても、**どのような終了経路でも `on_stop()` は 100% 確実に呼ばれます**(※スレッドが `while (!should_stop())` で即座にループを抜けた場合など、`on_signal(SIG_STOP)` は通らないことがあります。リソース解放や終了処理は `on_signal` ではなく必ず `on_stop` で行ってください)。
 
 ```cpp
 #include "tinykernel/thread.hpp"
@@ -142,9 +143,13 @@ protected:
 
     void on_signal(uint32_t sig) override {
         if (sig == SIG_WAKE_NOW) {
-            // スリープを中断して即座に実行される処理
+            // スリープを中断して即座に実行される処理 (走行中のイベント処理)
         }
     }
+
+    void on_stop() override {
+        // ★ 終了処理はここで行う (stop() でも自然終了でも必ず呼ばれる)
+    }
 };
 
 // 他タスク内での動的な生成・シグナル送信・破棄の例
@@ -351,15 +356,18 @@ By inheriting from `tk::Thread<StackSize>`, you can define threads with an embed
 - **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()`).
+- **Guaranteed cleanup on termination (`on_stop`)**: Whether terminated externally via `stop()` or completing naturally when `run()` returns, **`on_stop()` is guaranteed to be called 100% of the time** (*Note: If a thread exits immediately upon `while (!should_stop())`, `on_signal(SIG_STOP)` may not be called. Always perform cleanup and resource deallocation in `on_stop()`, not in `on_signal()*).
 
 ```cpp
 #include "tinykernel/thread.hpp"
 
+// User-defined signals (use tk::SIG_USER and above)
 enum AppSignal : uint32_t {
     SIG_WAKE_NOW = tk::SIG_USER,
+    SIG_DUMP     = tk::SIG_USER + 1,
 };
 
-class MyWorker : public tk::Thread<1024> {
+class MyWorker : public tk::Thread<1024> { // Embedded 1KB stack
 protected:
     void run() override {
         while (!should_stop()) {
@@ -372,10 +380,28 @@ protected:
 
     void on_signal(uint32_t sig) override {
         if (sig == SIG_WAKE_NOW) {
-            // Handled immediately upon breaking sleep
+            // Handled immediately upon breaking sleep (runtime events)
         }
     }
+
+    void on_stop() override {
+        // Cleanup resources here (guaranteed to be called on stop() or run() exit)
+    }
 };
+
+// Example of dynamic creation, signalling, and RAII cleanup in another task:
+void manager_task(void*) {
+    {
+        MyWorker worker;
+        worker.start(tk::PRIO_NORMAL);       // Start dynamically
+
+        tk::sleep_ms(500);
+        worker.send_signal(SIG_WAKE_NOW);    // Wake sleep immediately and trigger on_signal()
+
+        worker.stop();                       // Send SIG_STOP -> paused (Terminated)
+        worker.join();                       // Wait for termination
+    } // Scope exit calls ~MyWorker(), safely freeing the task slot (Free)
+}
 ```
 
 ## Semaphores / Mutexes (`sync.hpp`)

+ 1 - 1
tinykernel/include/tinykernel/actor.hpp

@@ -311,7 +311,7 @@ private:
             uint32_t save = detail::sched_lock();
             while (count_ == 0 && !detail::has_pending_signal_or_stop_locked(this->handle_)) {
                 waiting_ = true;
-                detail::block_current_task_locked();
+                detail::block_current_task_interruptible_locked();
                 detail::sched_unlock(save);
                 // post() または send_signal() / stop() で起床
                 save = detail::sched_lock();

+ 1 - 0
tinykernel/include/tinykernel/kernel.hpp

@@ -204,6 +204,7 @@ void static_register(const char* name, TaskFn fn, uint32_t prio);
 uint32_t sched_lock();
 void sched_unlock(uint32_t save);
 void block_current_task_locked();
+void block_current_task_interruptible_locked();
 void wake_task_locked(TaskHandle handle);
 uint8_t current_task_slot();
 uint8_t find_highest_prio_waiter(task_mask_t wait_mask);

+ 14 - 2
tinykernel/include/tinykernel/thread.hpp

@@ -19,6 +19,8 @@ namespace tk {
  * - **安全なRAIIライフサイクル**: `stop()` や `run()` 完了時は一時停止 (`State::Terminated`) 状態となり、
  *   インスタンスがスコープから外れるか `delete` されてデストラクタが呼ばれた時点で初めてスロットが解放 (`State::Free`) されます。
  *   スコープ内であれば、停止したスレッドに対して再度 `start()` を呼んで再実行することも可能です。
+ * - **終了クリーンアップの保証 (`on_stop`)**: 外部からの `stop()` や自然終了など、いかなる終了経路でも `on_stop()` が
+ *   100% 確実に呼ばれます。終了処理(ハードウェア停止やリソース解放)は `on_signal()` ではなく必ず `on_stop()` で行います。
  * 
  * @tparam StackSize スレッドのスタックサイズ (バイト単位、8の倍数、デフォルト: TK_DEFAULT_STACK_SIZE)
  * 
@@ -159,14 +161,24 @@ protected:
      * @brief シグナル受信時に自動実行されるコールバック関数
      * 
      * `sleep_ms()` の早期中断時や `yield()`、`check_signals()` 呼び出し時に
-     * このスレッド自身のコンテキストで実行されます。
+     * このスレッド自身のコンテキストで実行されます。走行中の非同期イベント処理に使用します。
      * 
      * @param sig 受信したシグナル値
+     * 
+     * @warning **終了処理には使用しないでください**:
+     * スレッド停止時 (`stop()` / `SIG_STOP`)、スレッドが `while (!should_stop())` の条件判定で
+     * 即座にループを抜けて終了した場合など、`on_signal(SIG_STOP)` は呼ばれないことがあります。
+     * また、`run()` が自然に終了した場合もシグナルは送られません。
+     * **リソース解放や終了時の後始末は、必ず 100% 確実に呼び出される `on_stop()` で行ってください。**
      */
     virtual void on_signal(uint32_t sig) { (void)sig; }
 
     /**
-     * @brief `run()` が終了した直後に1度だけ呼ばれる終了フック
+     * @brief スレッド終了時にタスクコンテキストで1度だけ確実に呼ばれるクリーンアップフック
+     * 
+     * 外部からの `stop()` による停止であっても、`run()` の自然な正常終了であっても、
+     * **どのような終了経路であってもタスク終了直前に 100% 確実に呼び出されます**。
+     * ハードウェアの停止、ファイルのフラッシュ、バッファ解放などの終了処理は必ずこの関数をオーバーライドして記述してください。
      */
     virtual void on_stop() {}
 

+ 14 - 1
tinykernel/src/scheduler.cpp

@@ -32,6 +32,7 @@ struct TaskSlot {
     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 block_interruptible; // true if Blocked state can be interrupted by signals
     bool is_idle;
     FpuCtx fpu;
 };
@@ -300,9 +301,11 @@ bool send_task_signal(TaskHandle handle, uint32_t sig) {
     if (s.pending_signal != SIG_STOP) {
         s.pending_signal = sig;
     }
-    if (s.state == State::Sleeping || s.state == State::Blocked) {
+    // Sleeping, または中断可能 (block_interruptible) な Blocked の場合のみ即時起床
+    if (s.state == State::Sleeping || (s.state == State::Blocked && s.block_interruptible)) {
         s.sleep_interrupted = true;
         s.state = State::Ready;
+        s.block_interruptible = false;
         if (g_started) {
             tk_port_trigger_pendsv();
             __sev();
@@ -541,6 +544,15 @@ void tk::detail::block_current_task_locked() {
     uint32_t core = get_core_num();
     TaskSlot& cur = g_slots[g_cur[core]];
     cur.state = State::Blocked;
+    cur.block_interruptible = false;
+    tk_port_trigger_pendsv();
+}
+
+void tk::detail::block_current_task_interruptible_locked() {
+    uint32_t core = get_core_num();
+    TaskSlot& cur = g_slots[g_cur[core]];
+    cur.state = State::Blocked;
+    cur.block_interruptible = true;
     tk_port_trigger_pendsv();
 }
 
@@ -549,6 +561,7 @@ void tk::detail::wake_task_locked(TaskHandle handle) {
     TaskSlot& s = g_slots[handle.slot];
     if (s.state == State::Blocked) {
         s.state = State::Ready;
+        s.block_interruptible = false;
         if (g_started) {
             tk_port_trigger_pendsv();
             __sev();