Skip to main content

atmos/kernel/
reclock.rs

1//! パニック回復に対応したロック(Recoverable Mutex)。
2//!
3//! AtmOS は `panic = "abort"` + 自前 `#[panic_handler]` でプロセス単位にパニックを隔離するが、
4//! 巻き戻し(unwind)が無いため、ロック保持中に死ぬと `MutexGuard` の Drop が走らずロックが
5//! 永久に保持されてしまう(以後 `NET_LOCK` 等を取ろうとした全プロセスがデッドロック)。
6//!
7//! `RecoverableMutex` は保持中のコア ID を記録し、パニックハンドラが
8//! `force_release_if_core(cid)` で **そのコアが保持していたロックだけ** を強制解放できる。
9//! 他コアが正当に保持しているロックには触れない(クリティカルセクション破壊を避ける)。
10
11use core::sync::atomic::{AtomicI16, Ordering};
12use spin::{Mutex, MutexGuard};
13
14/// どのコアも保持していないことを表す番兵値。
15const FREE: i16 = -1;
16
17pub struct RecoverableMutex<T> {
18    inner: Mutex<T>,
19    /// 保持中のコア ID(FREE = 未保持)。
20    holder: AtomicI16,
21}
22
23unsafe impl<T: Send> Sync for RecoverableMutex<T> {}
24unsafe impl<T: Send> Send for RecoverableMutex<T> {}
25
26impl<T> RecoverableMutex<T> {
27    pub const fn new(value: T) -> Self {
28        Self {
29            inner: Mutex::new(value),
30            holder: AtomicI16::new(FREE),
31        }
32    }
33
34    /// ロックを取得する。`spin::Mutex` 同様 RAII ガードを返す(API 互換)。
35    pub fn lock(&self) -> RecGuard<'_, T> {
36        let guard = self.inner.lock();
37        self.holder.store(
38            crate::kernel::scheduler::core_id() as i16,
39            Ordering::Relaxed,
40        );
41        RecGuard {
42            guard: Some(guard),
43            holder: &self.holder,
44        }
45    }
46
47    /// 取得を試み、すでにロック中なら `None` を返す(デッドロックしない。検証/回復確認用)。
48    pub fn try_lock(&self) -> Option<RecGuard<'_, T>> {
49        let guard = self.inner.try_lock()?;
50        self.holder.store(
51            crate::kernel::scheduler::core_id() as i16,
52            Ordering::Relaxed,
53        );
54        Some(RecGuard {
55            guard: Some(guard),
56            holder: &self.holder,
57        })
58    }
59
60    /// パニック回復専用: コア `cid` が保持中ならロックを強制解放する。
61    ///
62    /// # Safety
63    /// 呼び出し元(パニックハンドラ)は、コア `cid` の現在の実行が直後に破棄される
64    /// (`scheduler::exit()` で別プロセスへ切替わる)ことを保証すること。保持していた
65    /// クリティカルセクションの不変条件は失われる前提で呼ぶ。
66    pub unsafe fn force_release_if_core(&self, cid: i16) {
67        if self.holder.load(Ordering::Relaxed) == cid {
68            self.holder.store(FREE, Ordering::Relaxed);
69            self.inner.force_unlock();
70        }
71    }
72}
73
74pub struct RecGuard<'a, T> {
75    guard: Option<MutexGuard<'a, T>>,
76    holder: &'a AtomicI16,
77}
78
79impl<'a, T> Drop for RecGuard<'a, T> {
80    fn drop(&mut self) {
81        // 先に holder をクリアしてから spin ロックを解放する。
82        self.holder.store(FREE, Ordering::Relaxed);
83        self.guard.take(); // MutexGuard を drop してロック解放
84    }
85}
86
87impl<'a, T> core::ops::Deref for RecGuard<'a, T> {
88    type Target = T;
89    fn deref(&self) -> &T {
90        // guard は Drop まで常に Some。
91        self.guard.as_deref().unwrap_or_else(|| unreachable_guard())
92    }
93}
94
95impl<'a, T> core::ops::DerefMut for RecGuard<'a, T> {
96    fn deref_mut(&mut self) -> &mut T {
97        match self.guard.as_mut() {
98            Some(g) => g,
99            None => unreachable_guard(),
100        }
101    }
102}
103
104#[inline(never)]
105#[cold]
106fn unreachable_guard() -> ! {
107    // ここに来ることはない(guard は Drop 直前まで Some)。unwrap を避けるための分岐。
108    loop {
109        core::hint::spin_loop();
110    }
111}
112
113/// パニック回復ロックの自己テスト。実際にパニックを起こさず、ガードを leak して
114/// 「保持したまま死んだ」状態を作り、`force_release_if_core` が正しく回復することを確認する。
115/// 起動時に呼ばれ `RECLOCK_SELFTEST: PASS/FAIL` を出力(CI スモークの判定マーカー)。
116pub fn selftest() -> bool {
117    // ガードを leak(保持したまま死んだ相当)するため、'static な対象を使う。
118    static M: RecoverableMutex<u32> = RecoverableMutex::new(0);
119    let cid = crate::kernel::scheduler::core_id() as i16;
120
121    // 1) 通常の取得→解放で再取得できる
122    {
123        let mut g = M.lock();
124        *g = 7;
125    }
126    if M.try_lock().is_none() {
127        return false;
128    }
129
130    // 2) ガードを leak(= 保持したまま死んだ相当)すると取得不能になる
131    let g = M.lock();
132    core::mem::forget(g); // Drop させない(保持しっぱなし)
133    if M.try_lock().is_some() {
134        return false;
135    } // 解放されていないはず
136
137    // 3) 別コア ID 指定では解放されない(他コアのクリティカルセクションを壊さない)
138    unsafe {
139        M.force_release_if_core(cid.wrapping_add(1));
140    }
141    if M.try_lock().is_some() {
142        return false;
143    }
144
145    // 4) 当該コア ID 指定で強制解放 → 再取得できる(回復成功)
146    unsafe {
147        M.force_release_if_core(cid);
148    }
149    M.try_lock().is_some()
150}