Skip to main content

atmos/kernel/
scheduler.rs

1//! # 優先度ベースのプリエンプティブ・ラウンドロビン・スケジューラ
2//!
3//! このモジュールは、各CPUコアごとに独立したランキューを持つ(Per-Core SMP)、優先度ベースのプリエンプティブ・ラウンドロビン・スケジューラの実装を提供します。
4//! EL1カーネルスレッドとEL0ユーザープロセスの両方のマルチプロセス環境をサポートしています。
5
6#![allow(dead_code)]
7#![allow(static_mut_refs)]
8extern crate alloc;
9use alloc::vec::Vec;
10use core::sync::atomic::{AtomicUsize, Ordering};
11use spin::Mutex;
12
13extern "C" {
14    fn switch_context(from_sp: *mut usize, to_sp: usize);
15    fn enter_userspace(user_entry: usize, user_sp: usize, kernel_sp: usize);
16}
17
18/// プロセスの状態を表す列挙型。
19#[derive(Debug, Clone, Copy, PartialEq, Eq)]
20pub enum ProcessState {
21    /// 実行中
22    Running,
23    /// 実行可能状態
24    Ready,
25    /// 待機状態 (スリープ等)
26    Waiting,
27    /// 終了状態
28    Terminated,
29}
30
31/// プロセスのスケジュール優先度を表す列挙型。
32#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
33pub enum Priority {
34    /// 低優先度
35    Low = 0,
36    /// 通常優先度
37    Normal = 1,
38    /// 高優先度
39    High = 2,
40}
41
42/// プロセス制御ブロック (PCB)。プロセスのメタデータ、実行コンテキスト、スタック情報を保持します。
43pub struct Process {
44    /// 一意のプロセスID (PID)
45    pub id: usize,
46    /// デバッグ用のプロセス名
47    pub name: &'static str,
48    /// 現在のプロセスの状態
49    pub state: ProcessState,
50    /// プロセスのスケジュール優先度
51    pub priority: Priority,
52    /// 保存されたスタックポインタの値 (コンテキストスイッチ時に書き換えられる)
53    pub stack_ptr: usize,
54    /// プロセスのスタックメモリ領域 (EL1スレッド用またはEL0ユーザースタック用)
55    pub stack_mem: Vec<u8>,
56    /// `Waiting` 状態時の残りの待機チック数
57    pub wait_ticks: usize,
58    /// プロセスに割り当てられたTTBR0ページテーブルの物理アドレス
59    pub page_table_phys_addr: u64,
60    /// 飢餓防止のための優先度スコアに加算されるカウント
61    pub starvation_count: usize,
62    /// カーネル空間 (EL1) で動作するか、ユーザー空間 (EL0) で動作するかのフラグ (true: EL1, false: EL0)
63    pub is_kernel: bool,
64    /// EL0プロセスのエントリポイント
65    pub user_entry: usize,
66    /// EL0プロセスのユーザースタックポインタ
67    pub user_stack_ptr: usize,
68    /// EL0プロセスがトラップした際に使用するEL1カーネルスタック
69    pub kernel_stack: Vec<u8>,
70    /// 残りのタイムスライス (チック数)
71    pub time_slice: usize,
72    /// 再スケジュールが必要であることを示すフラグ
73    pub need_resched: bool,
74}
75
76/// 各コアで動作するプロセスのランキュー構造体。
77pub struct CoreRunqueue {
78    /// キューに含まれるプロセスのリスト
79    pub processes: Vec<Process>,
80    /// 現在実行中のプロセスのインデックス
81    pub current_idx: Option<usize>,
82}
83
84/// 次に割り当てるべきプロセスIDを管理するアトミックカウンタ。
85static NEXT_PID: AtomicUsize = AtomicUsize::new(1);
86
87/// 各コア固有のランキュー (Per-Core Runqueues)。
88/// 各コアが独立してランキューに対する排他制御(Mutex)を行います。
89static RUNQUEUES: [Mutex<Option<CoreRunqueue>>; 4] = [
90    Mutex::new(None),
91    Mutex::new(None),
92    Mutex::new(None),
93    Mutex::new(None),
94];
95
96/// 終了したプロセスの「墓場」。
97/// `exit()` は現在実行中のプロセスのカーネルスタック上で動作するため、
98/// その場で `Process` をドロップすると自身のスタックを解放してしまい、Use-After-Free が発生します。
99/// そのため、一度この墓場領域に退避させ、別のプロセスへのコンテキストスイッチが行われた後
100///(そのスタックから外れた後)に、`reap_zombies()` を通じて安全にメモリを解放します。
101static ZOMBIES: [Mutex<Vec<Process>>; 4] = [
102    Mutex::new(Vec::new()),
103    Mutex::new(Vec::new()),
104    Mutex::new(Vec::new()),
105    Mutex::new(Vec::new()),
106];
107
108/// 現在のスタックポインタ (SP) の値を取得します。
109#[inline]
110fn current_sp() -> usize {
111    let sp: usize;
112    unsafe {
113        core::arch::asm!("mov {}, sp", out(reg) sp);
114    }
115    sp
116}
117
118// --- プリエンプティブ・スケジューリング制御用カウンタ・ヘルパー ---
119
120/// 各コアごとのプリエンプション禁止カウンタ。
121/// `preempt_count == 0` の時のみプリエンプション(強制タスク切り替え)が許可されます。
122static PREEMPT_COUNTS: [AtomicUsize; 4] = [
123    AtomicUsize::new(0),
124    AtomicUsize::new(0),
125    AtomicUsize::new(0),
126    AtomicUsize::new(0),
127];
128
129/// 現在のコアのプリエンプションを一時的に禁止します(カウントをインクリメント)。
130pub fn preempt_disable() {
131    let cid = core_id();
132    PREEMPT_COUNTS[cid].fetch_add(1, Ordering::SeqCst);
133}
134
135/// プリエンプション禁止を解除します(カウントをデクリメント)。
136/// カウントが `0` に戻り、かつ再スケジュール要求(`need_resched`)があれば即座に切り替えを実行します。
137pub fn preempt_enable() {
138    let cid = core_id();
139    let old = PREEMPT_COUNTS[cid].fetch_sub(1, Ordering::SeqCst);
140    if old == 1 {
141        // preempt_count が 0 に戻り、再スケジュールが必要なら yield_now()
142        if should_preempt() {
143            yield_now();
144        }
145    }
146}
147
148/// 現在のコアでプリエンプションが許可されている(preempt_count == 0)かを判定します。
149pub fn is_preempt_enabled() -> bool {
150    let cid = core_id();
151    PREEMPT_COUNTS[cid].load(Ordering::SeqCst) == 0
152}
153
154/// 現在のコアでプリエンプション(強制切り替え)を実行すべき状態かを判定します。
155pub fn should_preempt() -> bool {
156    if !is_preempt_enabled() {
157        return false;
158    }
159    let cid = core_id();
160    // RUNQUEUES に対するデッドロックを防ぐため try_lock を使用
161    if let Some(rq_lock) = RUNQUEUES[cid].try_lock() {
162        if let Some(rq) = rq_lock.as_ref() {
163            if let Some(idx) = rq.current_idx {
164                if idx < rq.processes.len() {
165                    return rq.processes[idx].need_resched;
166                }
167            }
168        }
169    }
170    false
171}
172
173/// タイマー割り込み(Tick)から毎チック呼び出され、プロセスの残りタイムスライスを管理します。
174pub fn scheduler_tick() {
175    let cid = core_id();
176    // preempt_disable であってもタイムスライス自体はデクリメントします。
177    // (実際のプリエンプションは preempt_enable または割り込み出口まで保留されます)
178    // 【2026-08-25 バグ修正】ここは `lock()` で**待って**いた。
179    //
180    // この関数はタイマ割り込みから呼ばれる(`interrupt.rs`)。
181    // ところがスレッド側は `RUNQUEUES[cid]` を割り込み許可のまま
182    // 取る箇所が 12 ある(`spawn`、`yield_now` の一部、`sleep` など)。
183    // そのスレッドを同じコアのタイマ割り込みが叩くと、
184    // ハンドラは自分が中断させた相手の持つロックを待つことになり、
185    // **そのコアが永久に固まる**(割り込み出口へ戻れないので、
186    // 相手が解放する機会も来ない)。
187    //
188    // 取り逃した 1 tick はタイムスライスの数え落としにすぎず、
189    // 次の tick で数え直される。**待たずに諦めるのが正しい。**
190    //
191    // 同じ理由で `should_preempt` とパニックハンドラ用の 3 関数は
192    // 既に `try_lock` を使っている。ここだけ作法が違っていた。
193    let Some(mut rq_lock) = RUNQUEUES[cid].try_lock() else {
194        return;
195    };
196    if let Some(rq) = rq_lock.as_mut() {
197        if let Some(idx) = rq.current_idx {
198            if idx < rq.processes.len() {
199                let p = &mut rq.processes[idx];
200                if p.id != 0 && p.name != "idle_core" {
201                    if p.time_slice > 0 {
202                        p.time_slice -= 1;
203                    }
204                    if p.time_slice == 0 {
205                        p.need_resched = true;
206                    }
207                }
208            }
209        }
210    }
211}
212
213/// 指定されたスタックポインタ (SP) が、メモリ領域に含まれるかを判定します。
214fn stack_contains(mem: &[u8], sp: usize) -> bool {
215    let base = mem.as_ptr() as usize;
216    sp >= base && sp < base + mem.len()
217}
218
219/// 現在のスタックポインタ (SP) がそのスタック上に存在しないゾンビプロセス(終了済みプロセス)を解放します。
220/// まだスタックが使用中のプロセス(exit直後の自分自身など)は、次回以降のクリーンアップに持ち越します。
221fn reap_zombies(cid: usize) {
222    let sp = current_sp();
223    let mut z = ZOMBIES[cid].lock();
224    z.retain(|p| stack_contains(&p.kernel_stack, sp) || stack_contains(&p.stack_mem, sp));
225}
226
227/// 現在実行中のCPUコアのID (0〜3) を取得します。
228/// `mpidr_el1` レジスタからアフィニティレベル0を取得します。
229#[inline]
230pub fn core_id() -> usize {
231    let mpidr: u64;
232    unsafe {
233        core::arch::asm!("mrs {}, mpidr_el1", out(reg) mpidr);
234    }
235    (mpidr & 0xFF) as usize
236}
237
238/// スケジューラの初期化処理。
239/// 起動コア (Core 0) 用の初期プロセス `kernel_main` を登録し、他のコアのランキューを空で初期化します。
240pub fn init() {
241    let current_ttbr0: u64;
242    unsafe {
243        core::arch::asm!("mrs {}, ttbr0_el1", out(reg) current_ttbr0);
244    }
245
246    let mut rq0 = RUNQUEUES[0].lock();
247    let processes = alloc::vec![Process {
248        id: 0,
249        name: "kernel_main",
250        state: ProcessState::Running,
251        priority: Priority::High,
252        stack_ptr: 0,
253        stack_mem: Vec::new(),
254        wait_ticks: 0,
255        page_table_phys_addr: current_ttbr0,
256        starvation_count: 0,
257        is_kernel: true,
258        user_entry: 0,
259        user_stack_ptr: 0,
260        kernel_stack: Vec::new(),
261        time_slice: 10,
262        need_resched: false,
263    }];
264
265    *rq0 = Some(CoreRunqueue {
266        processes,
267        current_idx: Some(0),
268    });
269
270    for i in 1..4 {
271        let mut rq = RUNQUEUES[i].lock();
272        *rq = Some(CoreRunqueue {
273            processes: Vec::new(),
274            current_idx: None,
275        });
276    }
277}
278
279/// カーネル空間 (EL1) で実行される新しいプロセス (カーネルスレッド) を生成し、
280/// ロードバランシングに基づいてプロセス数が最も少ないコアのランキューに登録します。
281///
282/// # 引数
283/// * `entry` - プロセスのメイン処理となる関数へのポインタ
284/// * `priority` - スケジュール優先度
285/// * `name` - プロセスの識別名
286///
287/// # 戻り値
288/// 生成されたプロセスの PID。OOMなどのメモリ不足により起動できなかった場合は `0` を返します。
289pub fn spawn(entry: fn(), priority: Priority, name: &'static str) -> usize {
290    spawn_with_stack(entry, priority, name, 1024 * 1024)
291}
292
293/// JS を走らせるスレッド(ブラウザのローダー・タブ)向けのスタックサイズ。
294/// 【2026-10-17】JS の 1 呼び出しはネイティブスタックを 3〜4 KiB 使うため、
295/// 1 MiB では再帰がおよそ 200 段で尽きる(実サイトのライブラリには足りない)。
296/// 8 MiB あれば呼び出し回数の上限(`DEFAULT_MAX_DEPTH` = 2000)が先に効く。
297pub const LARGE_STACK_SIZE: usize = 8 * 1024 * 1024;
298
299/// スタックサイズを指定してカーネルスレッドを生成する(それ以外は `spawn` と同じ)。
300pub fn spawn_with_stack(entry: fn(), priority: Priority, name: &'static str, stack_size: usize) -> usize {
301    let pid = NEXT_PID.fetch_add(1, Ordering::SeqCst);
302
303    // 空きヒープ容量をチェックし、OOMによるパニックを防ぐ
304    let (used, total) = crate::kernel::allocator::get_heap_stats();
305    let free = total.saturating_sub(used);
306    if free < stack_size + 16 * 1024 {
307        crate::println!("[scheduler] WARNING: Insufficient heap memory to spawn process '{}' (Free: {} KB, Need: {} KB)", name, free / 1024, stack_size / 1024);
308        return 0;
309    }
310
311    let stack_mem = alloc::vec![0u8; stack_size];
312
313    let stack_top = stack_mem.as_ptr() as usize + stack_size;
314    let aligned_top = stack_top & !0xF;
315    let initial_sp = aligned_top - 240;
316
317    let sp_ptr = initial_sp as *mut usize;
318    unsafe {
319        for j in 0..10 {
320            sp_ptr.add(j).write_volatile(0);
321        }
322        sp_ptr.add(10).write_volatile(0);
323        sp_ptr.add(11).write_volatile(entry as usize);
324    }
325
326    let pt_addr = crate::kernel::mmu::create_process_page_table();
327
328    let new_proc = Process {
329        id: pid,
330        name,
331        state: ProcessState::Ready,
332        priority,
333        stack_ptr: initial_sp,
334        stack_mem,
335        wait_ticks: 0,
336        page_table_phys_addr: pt_addr,
337        starvation_count: 0,
338        is_kernel: true,
339        user_entry: 0,
340        user_stack_ptr: 0,
341        kernel_stack: Vec::new(),
342        time_slice: 10,
343        need_resched: false,
344    };
345
346    // ロードバランサ: 最もプロセス数が少ないコアのRunqueueを探す
347    let mut best_core = 0;
348    let mut min_procs = usize::MAX;
349
350    // ロックを1つずつ順番にとって確認 (同時ロックは避ける)
351    for i in 0..4 {
352        let rq_opt = RUNQUEUES[i].lock();
353        if let Some(rq) = rq_opt.as_ref() {
354            let count = rq.processes.len();
355            if count < min_procs {
356                min_procs = count;
357                best_core = i;
358            }
359        }
360    }
361
362    // 選ばれたコアのキューにタスクを追加
363    let mut rq_lock = RUNQUEUES[best_core].lock();
364    if let Some(rq) = rq_lock.as_mut() {
365        rq.processes.push(new_proc);
366        crate::debug!(
367            "[SCHED] Spawned process '{}' (PID: {}, Priority: {:?}) onto Core {}",
368            name,
369            pid,
370            priority,
371            best_core
372        );
373    }
374
375    pid
376}
377
378/// 特定のコアをターゲットにしてカーネルプロセス (カーネルスレッド) を生成します。
379///
380/// # 引数
381/// * `entry` - プロセスのエントリポイント
382/// * `priority` - 優先度
383/// * `name` - プロセス名
384/// * `target_core` - 生成先のCPUコアのインデックス
385///
386/// # 戻り値
387/// 生成されたプロセスの PID。OOMなどのメモリ不足により起動できなかった場合は `0` を返します。
388pub fn spawn_on_core(
389    entry: fn(),
390    priority: Priority,
391    name: &'static str,
392    target_core: usize,
393) -> usize {
394    spawn_on_core_with_stack(entry, priority, name, target_core, 1024 * 1024)
395}
396
397/// スタックサイズを指定して特定のコアにカーネルスレッドを生成する(それ以外は `spawn_on_core` と同じ)。
398pub fn spawn_on_core_with_stack(
399    entry: fn(),
400    priority: Priority,
401    name: &'static str,
402    target_core: usize,
403    stack_size: usize,
404) -> usize {
405    let pid = NEXT_PID.fetch_add(1, Ordering::SeqCst);
406
407    // 空きヒープ容量をチェックし、OOMによるパニックを防ぐ
408    let (used, total) = crate::kernel::allocator::get_heap_stats();
409    let free = total.saturating_sub(used);
410    if free < stack_size + 16 * 1024 {
411        crate::println!("[scheduler] WARNING: Insufficient heap memory to spawn process '{}' (Free: {} KB, Need: {} KB)", name, free / 1024, stack_size / 1024);
412        return 0;
413    }
414
415    let stack_mem = alloc::vec![0u8; stack_size];
416
417    let stack_top = stack_mem.as_ptr() as usize + stack_size;
418    let aligned_top = stack_top & !0xF;
419    let initial_sp = aligned_top - 240;
420
421    let sp_ptr = initial_sp as *mut usize;
422    unsafe {
423        for j in 0..10 {
424            sp_ptr.add(j).write_volatile(0);
425        }
426        sp_ptr.add(10).write_volatile(0);
427        sp_ptr.add(11).write_volatile(entry as usize);
428    }
429
430    let pt_addr = crate::kernel::mmu::create_process_page_table();
431
432    let new_proc = Process {
433        id: pid,
434        name,
435        state: ProcessState::Ready,
436        priority,
437        stack_ptr: initial_sp,
438        stack_mem,
439        wait_ticks: 0,
440        page_table_phys_addr: pt_addr,
441        starvation_count: 0,
442        is_kernel: true,
443        user_entry: 0,
444        user_stack_ptr: 0,
445        kernel_stack: Vec::new(),
446        time_slice: 10,
447        need_resched: false,
448    };
449
450    let core = target_core.min(3);
451    let mut rq_lock = RUNQUEUES[core].lock();
452    if let Some(rq) = rq_lock.as_mut() {
453        rq.processes.push(new_proc);
454        crate::debug!(
455            "[SCHED] Spawned process '{}' (PID: {}, Priority: {:?}) onto Core {}",
456            name,
457            pid,
458            priority,
459            core
460        );
461    }
462
463    pid
464}
465
466/// コンテキストスイッチで最初に起動するトランポリン関数。
467/// レジスタ `x19` からエントリポイント、`x20` から引数を受け取り、対象の関数を実行します。
468/// 実行が終了したスレッドは、`exit()` を呼び出して自身を終了させます。
469extern "C" fn trampoline() {
470    let entry_ptr: usize;
471    let arg: usize;
472    unsafe {
473        core::arch::asm!(
474            "mov {}, x19",
475            "mov {}, x20",
476            out(reg) entry_ptr,
477            out(reg) arg,
478        );
479        let entry: fn(usize) = core::mem::transmute(entry_ptr);
480        entry(arg);
481    }
482    exit();
483}
484
485/// 引数 `usize` を1つ取るカーネルプロセス (カーネルスレッド) を生成し、
486/// ロードバランシングに基づいてプロセス数が最も少ないコアのランキューに登録します。
487///
488/// # 引数
489/// * `entry` - 引数を1つ取る関数のポインタ
490/// * `arg` - 関数に渡される引数
491/// * `priority` - 優先度
492/// * `name` - プロセス名
493///
494/// # 戻り値
495/// 生成されたプロセスの PID。OOMなどのメモリ不足により起動できなかった場合は `0` を返します。
496pub fn spawn_with_arg(
497    entry: fn(usize),
498    arg: usize,
499    priority: Priority,
500    name: &'static str,
501) -> usize {
502    let pid = NEXT_PID.fetch_add(1, Ordering::SeqCst);
503
504    let stack_size = 1024 * 1024;
505
506    // 空きヒープ容量をチェックし、OOMによるパニックを防ぐ
507    let (used, total) = crate::kernel::allocator::get_heap_stats();
508    let free = total.saturating_sub(used);
509    if free < stack_size + 16 * 1024 {
510        crate::println!("[scheduler] WARNING: Insufficient heap memory to spawn process '{}' (Free: {} KB, Need: {} KB)", name, free / 1024, stack_size / 1024);
511        return 0;
512    }
513
514    let stack_mem = alloc::vec![0u8; stack_size];
515
516    let stack_top = stack_mem.as_ptr() as usize + stack_size;
517    let aligned_top = stack_top & !0xF;
518    let initial_sp = aligned_top - 240; // 240バイトフレームに対応
519
520    let sp_ptr = initial_sp as *mut usize;
521    unsafe {
522        // x19: entry_ptr, x20: arg
523        sp_ptr.add(0).write_volatile(entry as usize); // x19
524        sp_ptr.add(1).write_volatile(arg); // x20
525
526        for j in 2..10 {
527            sp_ptr.add(j).write_volatile(0);
528        }
529        sp_ptr.add(10).write_volatile(0); // lr (x30) -> (switch_context内では使われないがアラインメントのため)
530        sp_ptr
531            .add(11)
532            .write_volatile(trampoline as *const () as usize); // pc (x30)
533    }
534
535    let pt_addr = crate::kernel::mmu::create_process_page_table();
536
537    let new_proc = Process {
538        id: pid,
539        name,
540        state: ProcessState::Ready,
541        priority,
542        stack_ptr: initial_sp,
543        stack_mem,
544        wait_ticks: 0,
545        page_table_phys_addr: pt_addr,
546        starvation_count: 0,
547        is_kernel: true,
548        user_entry: 0,
549        user_stack_ptr: 0,
550        kernel_stack: Vec::new(),
551        time_slice: 10,
552        need_resched: false,
553    };
554
555    let mut best_core = 0;
556    let mut min_procs = usize::MAX;
557    for i in 0..4 {
558        let rq_opt = RUNQUEUES[i].lock();
559        if let Some(rq) = rq_opt.as_ref() {
560            let count = rq.processes.len();
561            if count < min_procs {
562                min_procs = count;
563                best_core = i;
564            }
565        }
566    }
567
568    let mut rq_lock = RUNQUEUES[best_core].lock();
569    if let Some(rq) = rq_lock.as_mut() {
570        rq.processes.push(new_proc);
571        crate::debug!(
572            "[SCHED] Spawned process '{}' with arg (PID: {}, Core {})",
573            name,
574            pid,
575            best_core
576        );
577    }
578
579    pid
580}
581
582/// コアと引数 `usize` を指定してカーネルプロセス (カーネルスレッド) を生成し、
583/// 対象コアのランキューに登録します。
584pub fn spawn_with_arg_on_core(
585    entry: fn(usize),
586    arg: usize,
587    priority: Priority,
588    name: &'static str,
589    target_core: usize,
590) -> usize {
591    spawn_with_arg_on_core_with_stack(entry, arg, priority, name, target_core, 1024 * 1024)
592}
593
594/// スタックサイズを指定する `spawn_with_arg_on_core`。
595pub fn spawn_with_arg_on_core_with_stack(
596    entry: fn(usize),
597    arg: usize,
598    priority: Priority,
599    name: &'static str,
600    target_core: usize,
601    stack_size: usize,
602) -> usize {
603    let pid = NEXT_PID.fetch_add(1, Ordering::SeqCst);
604
605    // 空きヒープ容量をチェックし、OOMによるパニックを防ぐ
606    let (used, total) = crate::kernel::allocator::get_heap_stats();
607    let free = total.saturating_sub(used);
608    if free < stack_size + 16 * 1024 {
609        crate::println!("[scheduler] WARNING: Insufficient heap memory to spawn process '{}' (Free: {} KB, Need: {} KB)", name, free / 1024, stack_size / 1024);
610        return 0;
611    }
612
613    let stack_mem = alloc::vec![0u8; stack_size];
614
615    let stack_top = stack_mem.as_ptr() as usize + stack_size;
616    let aligned_top = stack_top & !0xF;
617    let initial_sp = aligned_top - 240; // 240バイトフレームに対応
618
619    let sp_ptr = initial_sp as *mut usize;
620    unsafe {
621        // x19: entry_ptr, x20: arg
622        sp_ptr.add(0).write_volatile(entry as usize); // x19
623        sp_ptr.add(1).write_volatile(arg); // x20
624
625        for j in 2..10 {
626            sp_ptr.add(j).write_volatile(0);
627        }
628        sp_ptr.add(10).write_volatile(0); // lr (x30)
629        sp_ptr
630            .add(11)
631            .write_volatile(trampoline as *const () as usize); // pc (x30)
632    }
633
634    let pt_addr = crate::kernel::mmu::create_process_page_table();
635
636    let new_proc = Process {
637        id: pid,
638        name,
639        state: ProcessState::Ready,
640        priority,
641        stack_ptr: initial_sp,
642        stack_mem,
643        wait_ticks: 0,
644        page_table_phys_addr: pt_addr,
645        starvation_count: 0,
646        is_kernel: true,
647        user_entry: 0,
648        user_stack_ptr: 0,
649        kernel_stack: Vec::new(),
650        time_slice: 10,
651        need_resched: false,
652    };
653
654    let core = target_core.min(3);
655    let mut rq_lock = RUNQUEUES[core].lock();
656    if let Some(rq) = rq_lock.as_mut() {
657        rq.processes.push(new_proc);
658        crate::debug!(
659            "[SCHED] Spawned process '{}' with arg (PID: {}, Core {})",
660            name,
661            pid,
662            core
663        );
664    }
665
666    pid
667}
668
669/// 指定した PID を持つプロセスの TTBR0 ページテーブル物理アドレスを取得します。
670///
671/// # 引数
672/// * `pid` - 対象プロセスの ID
673///
674/// # 戻り値
675/// プロセスのページテーブル物理アドレス。プロセスが見つからない場合は `None` を返します。
676pub fn get_process_page_table(pid: usize) -> Option<u64> {
677    for i in 0..4 {
678        let rq_opt = RUNQUEUES[i].lock();
679        if let Some(rq) = rq_opt.as_ref() {
680            for p in &rq.processes {
681                if p.id == pid {
682                    return Some(p.page_table_phys_addr);
683                }
684            }
685        }
686    }
687    None
688}
689
690/// 現在の CPU コアで実行されているプロセスの TTBR0 ページテーブル物理アドレスを取得します。
691///
692/// # 戻り値
693/// 現在プロセスのページテーブル物理アドレス。プロセスが存在しない場合は `None` を返します。
694pub fn get_current_page_table() -> Option<u64> {
695    let cid = core_id();
696    let rq_opt = RUNQUEUES[cid].lock();
697    if let Some(rq) = rq_opt.as_ref() {
698        if let Some(idx) = rq.current_idx {
699            return Some(rq.processes[idx].page_table_phys_addr);
700        }
701    }
702    None
703}
704
705/// 定期的に呼び出され(タイマー割り込みなど)、待機状態 (Waiting) にあるプロセスのチック数を更新し、
706/// 必要に応じて `schedule()` を実行してコンテキストスイッチを行います。
707pub fn tick() {
708    let cid = core_id();
709    {
710        let mut rq_opt = RUNQUEUES[cid].lock();
711        if let Some(rq) = rq_opt.as_mut() {
712            for p in &mut rq.processes {
713                if p.state == ProcessState::Waiting {
714                    if p.wait_ticks > 0 {
715                        p.wait_ticks -= 1;
716                    }
717                    if p.wait_ticks == 0 {
718                        p.state = ProcessState::Ready;
719                    }
720                }
721            }
722        }
723    }
724    schedule();
725}
726
727/// 待機状態 (Waiting) にある全プロセスの待ち時間チック数をデクリメントします。
728/// チック数が0になったプロセスは実行可能状態 (Ready) に戻ります。
729pub fn decrement_wait_ticks() {
730    let cid = core_id();
731    let mut rq_opt = RUNQUEUES[cid].lock();
732    if let Some(rq) = rq_opt.as_mut() {
733        for p in &mut rq.processes {
734            if p.state == ProcessState::Waiting {
735                if p.wait_ticks > 0 {
736                    p.wait_ticks -= 1;
737                }
738                if p.wait_ticks == 0 {
739                    p.state = ProcessState::Ready;
740                }
741            }
742        }
743    }
744}
745
746/// 指定した期間(タイマーチック数)だけ、呼び出し元のプロセスを待機状態 (Waiting) に移行させます。
747///
748/// # 引数
749/// * `ticks` - 待機させるチック数。0 の場合は即時に戻ります。
750pub fn sleep(ticks: usize) {
751    if ticks == 0 {
752        return;
753    }
754    crate::kernel::interrupt::disable_interrupts();
755
756    let cid = core_id();
757    {
758        let mut rq_opt = RUNQUEUES[cid].lock();
759        if let Some(rq) = rq_opt.as_mut() {
760            if let Some(idx) = rq.current_idx {
761                let curr = &mut rq.processes[idx];
762                curr.state = ProcessState::Waiting;
763                curr.wait_ticks = ticks;
764            }
765        }
766    }
767
768    yield_now();
769    crate::kernel::interrupt::enable_interrupts();
770}
771
772/// 呼び出し元のプロセスを一時的に実行可能状態 (Ready) に戻し、即座に再スケジュールを実行して
773/// CPUを他のプロセスに譲ります(コオペラティブ・マルチタスク)。
774pub fn yield_now() {
775    crate::kernel::interrupt::disable_interrupts();
776    schedule();
777    crate::kernel::interrupt::enable_interrupts();
778}
779
780/// スケジューリングのコア処理。
781/// 飢餓防止(starvation_count)を考慮した、優先度ベースのプリエンプティブ・ラウンドロビンアルゴリズムを適用し、
782/// 最もスコアの高い Ready 状態のプロセスを選択してコンテキストスイッチを行います。
783pub(crate) fn schedule() {
784    let cid = core_id();
785    // 別プロセスへ切替済みで、もうそのスタック上に居ない墓場プロセスをここで解放する。
786    reap_zombies(cid);
787    let mut rq_opt = RUNQUEUES[cid].lock();
788    let Some(rq) = rq_opt.as_mut() else {
789        return;
790    };
791    if rq.processes.is_empty() {
792        return;
793    }
794
795    let old_idx = rq.current_idx;
796
797    if let Some(idx) = old_idx {
798        if rq.processes[idx].state == ProcessState::Running {
799            rq.processes[idx].state = ProcessState::Ready;
800        }
801    }
802
803    let mut best_idx = None;
804    let mut best_score = 0;
805
806    let num_proc = rq.processes.len();
807    let start_idx = old_idx.unwrap_or(0);
808
809    /*
810    if num_proc >= 3 {
811        crate::println!("--- SCHEDULER DEBUG (Core {}) ---", cid);
812        for p in &rq.processes {
813            crate::println!("  Proc '{}' (PID {}), State: {:?}, Pri: {:?}, Starve: {}, PT: 0x{:x}, Score: {}",
814                p.name, p.id, p.state, p.priority, p.starvation_count, p.page_table_phys_addr,
815                (p.priority as usize * 10) + p.starvation_count);
816        }
817    }
818    */
819
820    for i in 1..=num_proc {
821        let idx = (start_idx + i) % num_proc;
822        let p = &mut rq.processes[idx];
823        if p.state == ProcessState::Ready {
824            // 基本優先度 (Low=0, Normal=10, High=20) に待機回数を加算
825            let score = (p.priority as usize * 10).saturating_add(p.starvation_count);
826            if best_idx.is_none() || score > best_score {
827                best_idx = Some(idx);
828                best_score = score;
829            }
830            // Readyのプロセスは一律でstarvation_countをインクリメント(飽和加算で安全化)
831            p.starvation_count = p.starvation_count.saturating_add(1);
832        }
833    }
834
835    if let Some(new_idx) = best_idx {
836        rq.processes[new_idx].starvation_count = 0; // 選ばれたらリセット
837        rq.processes[new_idx].state = ProcessState::Running;
838        rq.processes[new_idx].time_slice = 10;      // タイムスライスを10チックに補充
839        rq.processes[new_idx].need_resched = false; // 再スケジュール要求をクリア
840        rq.current_idx = Some(new_idx);
841
842        let new_pt = rq.processes[new_idx].page_table_phys_addr;
843
844        if let Some(o_idx) = old_idx {
845            if o_idx != new_idx {
846                let old_sp_ptr = &mut rq.processes[o_idx].stack_ptr as *mut usize;
847                let new_sp = rq.processes[new_idx].stack_ptr;
848                drop(rq_opt);
849
850                unsafe {
851                    let cur_pt: u64;
852                    core::arch::asm!("mrs {}, ttbr0_el1", out(reg) cur_pt);
853                    if cur_pt != new_pt {
854                        core::arch::asm!(
855                            "msr ttbr0_el1, {}",
856                            "isb",
857                            "tlbi vmalle1is",
858                            "dsb sy",
859                            "isb",
860                            in(reg) new_pt
861                        );
862                    }
863                    switch_context(old_sp_ptr, new_sp);
864                }
865            }
866        } else {
867            // Core 1-3 first switch
868            let mut dummy_sp: usize = 0;
869            let new_sp = rq.processes[new_idx].stack_ptr;
870            drop(rq_opt);
871            unsafe {
872                let cur_pt: u64;
873                core::arch::asm!("mrs {}, ttbr0_el1", out(reg) cur_pt);
874                if cur_pt != new_pt {
875                    core::arch::asm!(
876                        "msr ttbr0_el1, {}",
877                        "isb",
878                        "tlbi vmalle1is",
879                        "dsb sy",
880                        "isb",
881                        in(reg) new_pt
882                    );
883                }
884                switch_context(&mut dummy_sp as *mut usize, new_sp);
885            }
886        }
887    } else {
888        if let Some(o_idx) = old_idx {
889            if rq.processes[o_idx].state == ProcessState::Ready {
890                rq.processes[o_idx].state = ProcessState::Running;
891                rq.processes[o_idx].time_slice = 10;      // 継続実行時もタイムスライスを補充
892                rq.processes[o_idx].need_resched = false;
893            } else {
894                rq.current_idx = None;
895            }
896        }
897    }
898}
899
900/// 呼び出し元のプロセスを終了させます。
901///
902/// 自身のプロセススタック上でドロップ処理を行うと、スタック領域自体が解放されて
903/// Use-After-Free などのパニックを引き起こすため、一旦 `ZOMBIES` にプロセスを退避し、
904/// 別のプロセスにコンテキストスイッチした後に `reap_zombies` を用いて遅延解放させます。
905pub fn exit() -> ! {
906    crate::kernel::interrupt::disable_interrupts();
907    let cid = core_id();
908    {
909        let mut rq_opt = RUNQUEUES[cid].lock();
910        if let Some(rq) = rq_opt.as_mut() {
911            if let Some(idx) = rq.current_idx {
912                // remove() でその場ドロップすると、今実行中の自身のカーネルスタックを
913                // 解放してしまう (use-after-free)。墓場へ退避してドロップを遅延する。
914                let dead = rq.processes.remove(idx);
915                crate::info!(
916                    "[SCHED] Process '{}' (PID: {}) exited on core {}.",
917                    dead.name,
918                    dead.id,
919                    cid
920                );
921                ZOMBIES[cid].lock().push(dead);
922                rq.current_idx = None;
923            }
924        }
925    }
926
927    schedule();
928
929    loop {
930        unsafe {
931            core::arch::asm!("wfe");
932        }
933    }
934}
935
936/// ユーザー空間 (EL0) で動作する新しいユーザープロセスを生成し、
937/// ロードバランシングに基づいてプロセス数が最も少ないコアのランキューに登録します。
938///
939/// # 引数
940/// * `entry` - ユーザープロセスのエントリポイント関数
941/// * `priority` - スケジュール優先度
942/// * `name` - プロセスの識別名
943///
944/// # 戻り値
945/// 生成されたプロセスの PID
946pub fn spawn_user(entry: fn(), priority: Priority, name: &'static str) -> usize {
947    let pid = NEXT_PID.fetch_add(1, Ordering::SeqCst);
948
949    // ユーザースタック (EL0 で使用する SP_EL0)
950    let user_stack_size = 256 * 1024;
951    let user_stack_mem = alloc::vec![0u8; user_stack_size];
952    let user_stack_top = user_stack_mem.as_ptr() as usize + user_stack_size;
953    let user_sp = user_stack_top & !0xF;
954
955    // カーネルスタック (EL0→EL1 トラップ時に使用する SP_EL1)
956    let kernel_stack_size = 64 * 1024;
957    let kernel_stack_mem = alloc::vec![0u8; kernel_stack_size];
958
959    // カーネルスレッドとしての初期スタック (user_trampoline を実行するためのもの)
960    // switch_context で user_trampoline に飛ぶ
961    let trampoline_stack_size = 32 * 1024;
962    let trampoline_stack_mem = alloc::vec![0u8; trampoline_stack_size];
963    let trampoline_stack_top = trampoline_stack_mem.as_ptr() as usize + trampoline_stack_size;
964    let aligned_top = trampoline_stack_top & !0xF;
965    let initial_sp = aligned_top - 240;
966
967    let sp_ptr = initial_sp as *mut usize;
968    unsafe {
969        // x19 = user_entry, x20 = user_sp, x21 = kernel_stack_top
970        let kernel_stack_top = kernel_stack_mem.as_ptr() as usize + kernel_stack_size;
971        sp_ptr.add(0).write_volatile(entry as usize); // x19: user_entry
972        sp_ptr.add(1).write_volatile(user_sp); // x20: user_sp
973        sp_ptr.add(2).write_volatile(kernel_stack_top & !0xF); // x21: kernel_stack_top
974        for j in 3..10 {
975            sp_ptr.add(j).write_volatile(0);
976        }
977        sp_ptr.add(10).write_volatile(0); // x29
978        sp_ptr
979            .add(11)
980            .write_volatile(user_trampoline as *const () as usize); // x30 (ret先)
981    }
982
983    let pt_addr = crate::kernel::mmu::create_user_page_table();
984
985    let new_proc = Process {
986        id: pid,
987        name,
988        state: ProcessState::Ready,
989        priority,
990        stack_ptr: initial_sp,
991        stack_mem: user_stack_mem, // ユーザースタックのメモリを保持
992        wait_ticks: 0,
993        page_table_phys_addr: pt_addr,
994        starvation_count: 0,
995        is_kernel: false,
996        user_entry: entry as usize,
997        user_stack_ptr: user_sp,
998        kernel_stack: kernel_stack_mem,
999        time_slice: 10,
1000        need_resched: false,
1001    };
1002
1003    // ロードバランサ
1004    let mut best_core = 0;
1005    let mut min_procs = usize::MAX;
1006    for i in 0..4 {
1007        let rq_opt = RUNQUEUES[i].lock();
1008        if let Some(rq) = rq_opt.as_ref() {
1009            let count = rq.processes.len();
1010            if count < min_procs {
1011                min_procs = count;
1012                best_core = i;
1013            }
1014        }
1015    }
1016
1017    let mut rq_lock = RUNQUEUES[best_core].lock();
1018    if let Some(rq) = rq_lock.as_mut() {
1019        rq.processes.push(new_proc);
1020        crate::debug!(
1021            "[SCHED] Spawned USER process '{}' (PID: {}, Priority: {:?}) onto Core {} [EL0]",
1022            name,
1023            pid,
1024            priority,
1025            best_core
1026        );
1027    }
1028
1029    // トランポリン用スタックメモリを解放されないようリーク (プロセス終了時に回収)
1030    core::mem::forget(trampoline_stack_mem);
1031
1032    pid
1033}
1034
1035/// ユーザープロセスのトランポリン関数。
1036/// `switch_context` から `ret` で呼ばれ、`SP_EL1` をカーネルスタックに、
1037/// `SP_EL0` をユーザースタックに設定した上で、`enter_userspace` を用いて EL0 に降下します。
1038extern "C" fn user_trampoline() {
1039    let user_entry: usize;
1040    let user_sp: usize;
1041    let kernel_sp: usize;
1042    unsafe {
1043        core::arch::asm!(
1044            "mov {}, x19",
1045            "mov {}, x20",
1046            "mov {}, x21",
1047            out(reg) user_entry,
1048            out(reg) user_sp,
1049            out(reg) kernel_sp,
1050        );
1051    }
1052
1053    crate::info!(
1054        "[SCHED] Entering EL0 (entry=0x{:x}, user_sp=0x{:x}, kernel_sp=0x{:x})",
1055        user_entry,
1056        user_sp,
1057        kernel_sp
1058    );
1059
1060    // 割り込みを有効化してから EL0 に降下
1061    crate::kernel::interrupt::enable_interrupts();
1062
1063    // enter_userspace は eret で EL0 に降下するので、ここには戻らない
1064    unsafe {
1065        enter_userspace(user_entry, user_sp, kernel_sp);
1066    }
1067
1068    // ここには到達しないはずだが、万一のためのフォールバック
1069    crate::error!("[SCHED] ERROR - returned from enter_userspace!");
1070    exit();
1071}
1072
1073/// 指定したコアをターゲットにして、ユーザー空間 (EL0) で動作するユーザープロセスを生成します。
1074///
1075/// # 引数
1076/// * `entry` - ユーザープロセスのエントリポイント関数
1077/// * `priority` - スケジュール優先度
1078/// * `name` - プロセスの識別名
1079/// * `target_core` - ターゲットとなる CPU コアのインデックス
1080///
1081/// # 戻り値
1082/// 生成されたプロセスの PID
1083pub fn spawn_user_on_core(
1084    entry: fn(),
1085    priority: Priority,
1086    name: &'static str,
1087    target_core: usize,
1088) -> usize {
1089    let pid = NEXT_PID.fetch_add(1, Ordering::SeqCst);
1090
1091    // ユーザースタック (EL0 で使用する SP_EL0)
1092    let user_stack_size = 256 * 1024;
1093    let user_stack_mem = alloc::vec![0u8; user_stack_size];
1094    let user_stack_top = user_stack_mem.as_ptr() as usize + user_stack_size;
1095    let user_sp = user_stack_top & !0xF;
1096
1097    // カーネルスタック (EL0→EL1 トラップ時に使用する SP_EL1)
1098    let kernel_stack_size = 64 * 1024;
1099    let kernel_stack_mem = alloc::vec![0u8; kernel_stack_size];
1100
1101    // カーネルスレッドとしての初期スタック (user_trampoline を実行するためのもの)
1102    let trampoline_stack_size = 32 * 1024;
1103    let trampoline_stack_mem = alloc::vec![0u8; trampoline_stack_size];
1104    let trampoline_stack_top = trampoline_stack_mem.as_ptr() as usize + trampoline_stack_size;
1105    let aligned_top = trampoline_stack_top & !0xF;
1106    let initial_sp = aligned_top - 240;
1107
1108    let sp_ptr = initial_sp as *mut usize;
1109    unsafe {
1110        let kernel_stack_top = kernel_stack_mem.as_ptr() as usize + kernel_stack_size;
1111        sp_ptr.add(0).write_volatile(entry as usize); // x19: user_entry
1112        sp_ptr.add(1).write_volatile(user_sp); // x20: user_sp
1113        sp_ptr.add(2).write_volatile(kernel_stack_top & !0xF); // x21: kernel_stack_top
1114        for j in 3..10 {
1115            sp_ptr.add(j).write_volatile(0);
1116        }
1117        sp_ptr.add(10).write_volatile(0); // x29
1118        sp_ptr
1119            .add(11)
1120            .write_volatile(user_trampoline as *const () as usize); // x30 (ret先)
1121    }
1122
1123    let pt_addr = crate::kernel::mmu::create_user_page_table();
1124
1125    let new_proc = Process {
1126        id: pid,
1127        name,
1128        state: ProcessState::Ready,
1129        priority,
1130        stack_ptr: initial_sp,
1131        stack_mem: user_stack_mem,
1132        wait_ticks: 0,
1133        page_table_phys_addr: pt_addr,
1134        starvation_count: 0,
1135        is_kernel: false,
1136        user_entry: entry as usize,
1137        user_stack_ptr: user_sp,
1138        kernel_stack: kernel_stack_mem,
1139        time_slice: 10,
1140        need_resched: false,
1141    };
1142
1143    let core = target_core.min(3);
1144    let mut rq_lock = RUNQUEUES[core].lock();
1145    if let Some(rq) = rq_lock.as_mut() {
1146        rq.processes.push(new_proc);
1147        crate::debug!(
1148            "[SCHED] Spawned USER process '{}' (PID: {}, Priority: {:?}) onto Core {} [EL0]",
1149            name,
1150            pid,
1151            priority,
1152            core
1153        );
1154    }
1155
1156    // トランポリン用スタックメモリをリーク
1157    core::mem::forget(trampoline_stack_mem);
1158
1159    pid
1160}
1161
1162/// アイドルプロセス (`idle_core`) を特定のコアに登録し、そのコアの初期プロセスとします。
1163pub fn register_idle_process(core_id: usize) {
1164    let current_ttbr0: u64;
1165    unsafe {
1166        core::arch::asm!("mrs {}, ttbr0_el1", out(reg) current_ttbr0);
1167    }
1168
1169    let mut rq_lock = RUNQUEUES[core_id].lock();
1170    if let Some(rq) = rq_lock.as_mut() {
1171        rq.processes.push(Process {
1172            id: 0,
1173            name: "idle_core",
1174            state: ProcessState::Running,
1175            priority: Priority::Low,
1176            stack_ptr: 0,
1177            stack_mem: Vec::new(),
1178            wait_ticks: 0,
1179            page_table_phys_addr: current_ttbr0,
1180            starvation_count: 0,
1181            is_kernel: true,
1182            user_entry: 0,
1183            user_stack_ptr: 0,
1184            kernel_stack: Vec::new(),
1185            time_slice: 10,
1186            need_resched: false,
1187        });
1188        rq.current_idx = Some(0);
1189    }
1190}
1191
1192/// パニックハンドラ等から、現在のプロセスが主要なシステムプロセス(PID 0 または gui_shell など)であるかどうかを判定します。
1193/// デッドロックを防ぐため、try_lock を使用します。
1194pub fn is_current_process_critical() -> bool {
1195    let cid = core_id();
1196    if let Some(rq_lock) = RUNQUEUES[cid].try_lock() {
1197        if let Some(rq) = rq_lock.as_ref() {
1198            if let Some(idx) = rq.current_idx {
1199                if idx < rq.processes.len() {
1200                    let p = &rq.processes[idx];
1201                    // PID 0 (kernel_main / idle), もしくは gui_shell (シェル) をクリティカルとみなす
1202                    return p.id == 0 || p.name == "gui_shell" || p.name == "idle_core";
1203                }
1204            }
1205        }
1206    }
1207    // ロックが取得できなかった場合は、主要プロセスがランキュー操作中等だったと推測されるため、
1208    // 安全側に倒してクリティカル(OS全体を停止)と判断する
1209    true
1210}
1211
1212/// 現在実行中のプロセスの名前を取得します。
1213/// try_lock が失敗した場合は None を返します。
1214pub fn get_current_process_name() -> Option<&'static str> {
1215    let cid = core_id();
1216    if let Some(rq_lock) = RUNQUEUES[cid].try_lock() {
1217        if let Some(rq) = rq_lock.as_ref() {
1218            if let Some(idx) = rq.current_idx {
1219                if idx < rq.processes.len() {
1220                    return Some(rq.processes[idx].name);
1221                }
1222            }
1223        }
1224    }
1225    None
1226}
1227
1228/// 現在実行中のカーネルスレッドのスタック範囲 `(下端, 上端)` を返す。
1229/// SP がその範囲内にない(ブート直後のコア・例外スタック等)か、ロックが取れなければ None。
1230pub fn current_stack_bounds() -> Option<(usize, usize)> {
1231    let sp: usize;
1232    // SAFETY: SP を読むだけ。
1233    unsafe { core::arch::asm!("mov {}, sp", out(reg) sp, options(nomem, nostack, preserves_flags)) };
1234    // CPU0 のブートスタック(起動処理・セルフテストはここで走る。`link.ld`)。
1235    extern "C" {
1236        static __cpu0_stack_bottom: u8;
1237        static __stack_cpu0_top: u8;
1238    }
1239    let (b0, t0) = (
1240        core::ptr::addr_of!(__cpu0_stack_bottom) as usize,
1241        core::ptr::addr_of!(__stack_cpu0_top) as usize,
1242    );
1243    if sp > b0 && sp <= t0 {
1244        return Some((b0, t0));
1245    }
1246    let cid = core_id();
1247    let rq_lock = RUNQUEUES[cid].try_lock()?;
1248    let rq = rq_lock.as_ref()?;
1249    let p = rq.processes.get(rq.current_idx?)?;
1250    let lo = p.stack_mem.as_ptr() as usize;
1251    let hi = lo + p.stack_mem.len();
1252    if sp > lo && sp <= hi { Some((lo, hi)) } else { None }
1253}
1254
1255/// 現在実行中のプロセスのPIDを取得します。
1256/// try_lock が失敗した場合は None を返します。
1257pub fn get_current_process_id() -> Option<usize> {
1258    let cid = core_id();
1259    if let Some(rq_lock) = RUNQUEUES[cid].try_lock() {
1260        if let Some(rq) = rq_lock.as_ref() {
1261            if let Some(idx) = rq.current_idx {
1262                if idx < rq.processes.len() {
1263                    return Some(rq.processes[idx].id);
1264                }
1265            }
1266        }
1267    }
1268    None
1269}