Code study (09.11 ver)

Nitroblue 1·2025년 9월 11일

전체 구조

  1. board : STM32F446 내장 레지스터에 직접 접근(MMIO)하여 GPIO/버튼/타이밍 기능 사용

    • MMIO? CPU가 입출력(I/O) 장치에 접근할 때, 메모리(RAM)와 I/O 장치 모두를 동일한 주소 공간에서 취급하는 방식
    • PMIO는 I/O장치가 별도로 분리된 주소 공간을 사용한다.
  2. OS : 앱 인터페이스(App), 시스템콜 경계(Syscalls trait), 앱 실행자(Os)

  3. apps : 실제 동작(Heartbeat, SOS) - Syscalls만 사용해서 하드웨어를 건드린다.

  4. main() : 보드 초기화 -> 앱 배열 구성 -> Os::run(AppCall::SwitchOnButton{...})로 실행한다.


OS 모듈 : App interface와 실행기

하드웨어 핀 식별

#[derive(copy, Clone, Debug)]
pub enum GpioPin { Led1, Led2 }

실제 보드에 매핑하기 전에 logical하게 매핑하는 과정으로, 내 코드에서는 Led 2개 다 PA5으로 매핑해줄 예정.

Syscalls trait (앱이 호출하는 '시스템콜' 경계)

pub trait Syscalls {
	fn gpio_write(&mut self, pin: GpioPin, high: bool);
    fn gpio_toggle(&mut self, pin: GpioPin);
    fn sleep_ms(&mut self, ms: u32);
    fn now_ms(&self) -> u64;
    fn user_button_pressed(&self) -> bool;	// B1(PC13)
}

실제 SVC로 바꿀 때 이 trait 호출부를 SVC 클라이언트로 대체하고, 현재의 보드 구현은 커널(SVC Handler)쪽으로 옮기면 된다. 앱/OS 코드는 거의 그대로 둬도 될듯.

App trait (앱 표준 인터페이스)

pub trait App {
	fn name(&self) -> &`static str;
    fn init(&mut self, _sys: &mut dyn Syscalls) {}
    fn tick(&mut self, sys: &mut dyn Stscalls);		// &mut dyn : 런타임에 실제 타입이 결정되는 동적 디스패치 의미.
}
  • init()은 1회 초기화 옵션, tick()은 주기적으로 호출하는 함수.
  • &mut dyn Syscalls는 trait 객체(런타임 디스패치). 여기서는 동적 디스패치로 유연성 확보.

실행 모드 선택 AppCall

pub enum AppCall<'a> {
	ByName(&'a str),
    ByIndex(usize),
    All,
    SwitchOnButton { primary: usize, secondary: usize },
}
  • 앱을 콜하는 방식에 대해서 이름으로 호출하거나, 인덱스로 호출하거나, 모두 호출하거나, 버튼으로 호출하거나 하는 방식들을 매핑한 함수이다.
    Switch~~에서는 버튼이 안눌렸을 때 primary, 눌리면 secondary를 한다.

Os 실행기

pub struct Os<'a> {
	apps: &'a mut [&'a mut dyn App],
    sys: &'a mut dyn Syscalls,
    started: bool,
}
  • apps는 &mut [&mut dyn App] 형태의 trait 객체 배열
  • 라이프타임 'a는 Os가 참조로 받은 앱/시스템콜 객체보다 오래 살지 않도록 보장하여 두 개 이상의 앱/시스템콜이 겹치지 않도록 해준다.
impl<'a> Os<'a> {
        pub fn new(apps: &'a mut [&'a mut dyn App], sys: &'a mut dyn Syscalls) -> Self {
            Self { apps, sys, started: false }
        }
        pub fn run(&'a mut self, call: AppCall<'a>) -> ! {
            // One-time init for all apps
            if !self.started { for a in self.apps.iter_mut() { a.init(self.sys); } self.started = true; }

            assert!(!self.apps.is_empty(), "no apps to run");

            match call {
                AppCall::ByIndex(mut i) => {
                    i %= self.apps.len();
                    loop { self.apps[i].tick(self.sys); }
                }
                AppCall::ByName(name) => {
                    let mut idx = 0usize;
                    for (i, a) in self.apps.iter().enumerate() { if a.name() == name { idx = i; break; } }
                    loop { self.apps[idx].tick(self.sys); }
                }
                AppCall::All => {
                    loop { for a in self.apps.iter_mut() { a.tick(self.sys); } }
                }
                AppCall::SwitchOnButton { mut primary, mut secondary } => {
                    let len = self.apps.len();
                    primary %= len; secondary %= len;
                    loop {
                        if self.sys.user_button_pressed() {
                            self.apps[secondary].tick(self.sys);
                        } else {
                            self.apps[primary].tick(self.sys);
                        }
                        self.sys.sleep_ms(1); // debounce / cooperative yield
                    }
                }
            }
        }
    }

0개의 댓글