Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

例外

例外と割り込みは、プロセッサが非同期イベントと致命的なエラー(例えば、不正な命令の実行)を扱うためのハードウェアの仕組みです。 例外はプリエンプションを意味し、例外ハンドラを呼び出します。例外ハンドラは、イベントを引き起こした信号に応答して実行されるサブルーチンです。

cortex-m-rtクレートは、例外ハンドラを宣言するために、exceptionアトリビュートを提供しています。

// SysTick(システムタイマ)例外のための例外ハンドラ
#[exception]
fn SysTick() {
    // ..
}

exception属性の他は、例外ハンドラは普通の関数のように見えます。しかし、もう1つ違いがあります。 exceptionハンドラはソフトウェアから呼び出すことができません。前述の例では、SysTick();というステートメントは、 コンパイルエラーになります。

この動作は、非常に意図的なものです。 これはexceptionハンドラで宣言されたstatic mut変数の利用を安全にする、という機能を提供するためのものです。

#[exception]
fn SysTick() {
    static mut COUNT: u32 = 0;
    // `COUNT`は`&mut u32`の型をもっており、その利用は安全です
    *COUNT += 1;

ご存知かもしれませんが、static mut変数を関数内で使うことは、その関数を再入不可能にします。 直接的または間接的に、複数の例外・割り込みハンドラから、もしくは、mainと1つ以上の例外・割り込みハンドラから、 再進入不可能な関数を呼び出すことは、未定義動作です。

翻訳が古くなっています 安全なRustは、決して未定義動作になりません。そのため、再入不可能な関数は、unsafeとマークされなければなりません。 それでも、exceptionハンドラはstatic mutな変数を安全に使える、と述べました。これが可能なのは、どうしてでしょうか。 exceptionハンドラはソフトウェアから呼び出すことができないため、再入する可能性はありません。だから、安全に使えるのです。

As a result, in the context of exception handlers in embedded systems, the absence of concurrent invocations of the same handler ensures that there are no reentrancy issues, even if the handler uses static mutable variables.

In a multicore system, where multiple processor cores are executing code concurrently, the potential for reentrancy issues becomes relevant again, even within exception handlers. While each core may have its own set of exception handlers, there can still be scenarios where multiple cores attempt to execute the same exception handler simultaneously.
To address this concern in a multicore environment, proper synchronization mechanisms need to be employed within the exception handlers to ensure that access to shared resources is properly coordinated among the cores. This typically involves the use of techniques such as locks, semaphores, or atomic operations to prevent data races and maintain data integrity

Note that the exception attribute transforms definitions of static variables inside the function by wrapping them into unsafe blocks and providing us with new appropriate variables of type &mut of the same name. Thus we can dereference the reference via * to access the values of the variables without needing to wrap them in an unsafe block.

完全な例

SysTick例外を大体1秒毎に発生させるシステムタイマの例を使います。 SysTick例外ハンドラは、呼び出された回数をCOUNT変数に記録し、 セミホスティングを使ってホストコンソールにCOUNTの値を出力します。

注記:この例は、どのCortex-Mデバイスでも実行できます。QEMU上でも実行可能です。
#![deny(unsafe_code)]
#![no_main]
#![no_std]

use panic_halt as _;

use core::fmt::Write;

use cortex_m::peripheral::syst::SystClkSource;
use cortex_m_rt::{entry, exception};
use cortex_m_semihosting::{
    debug,
    hio::{self, HostStream},
};

#[entry]
fn main() -> ! {
    let p = cortex_m::Peripherals::take().unwrap();
    let mut syst = p.SYST;

    // 毎秒SysTick例外を起こすためのシステムタイマを設定します
    syst.set_clock_source(SystClkSource::Core);
    // デフォルトのCPUクロックが12MHzのLM3S6965向けの設定です
    syst.set_reload(12_000_000);
    syst.clear_current();
    syst.enable_counter();
    syst.enable_interrupt();

    loop {}
}

#[exception]
fn SysTick() {
    static mut COUNT: u32 = 0;
    static mut STDOUT: Option<HostStream> = None;

    *COUNT += 1;

    // 遅延初期化
    if STDOUT.is_none() {
        *STDOUT = hio::hstdout().ok();
    }

    if let Some(hstdout) = STDOUT.as_mut() {
        write!(hstdout, "{}", *COUNT).ok();
    }

    // 重要。実際のハードウェアで実行するときは`if`ブロックを削除して下さい。そうでなければ、
    // デバッガが不整合な状態に陥るでしょう。
    if *COUNT == 9 {
        // QEMUプロセスを終了します
        debug::exit(debug::EXIT_SUCCESS);
    }
}
tail -n5 Cargo.toml
[dependencies]
cortex-m = "0.5.7"
cortex-m-rt = "0.6.3"
panic-halt = "0.2.0"
cortex-m-semihosting = "0.3.1"
$ cargo run --release
     Running `qemu-system-arm -cpu cortex-m3 -machine lm3s6965evb (..)
123456789

Discoveryボードでこのコードを実行すると、OpenOCDコンソールに出力を確認できるでしょう。 プログラムは、カウントが9に到達しても停止しません

デフォルト例外ハンドラ

exceptionアトリビュートが実際に行っていることは、特定の例外を処理するデフォルト例外ハンドラのオーバーライドです。 特定の例外について、ハンドラをオーバーライドしない場合、DefaultHandler関数がその例外を処理します。 DefaultHandler関数は下記の通りです。

fn DefaultHandler() {
    loop {}
}

この関数は、cortex-m-rtクレートによって提供されており、#[no_mangle]とマークされています。 そのため、「DefaultHandler」にブレイクポイントを設定することができ、未処理の例外を捕捉することができます。

exceptionアトリビュートを使うことで、DefaultHandlerをオーバーライドできます。

#[exception]
fn DefaultHandler(irqn: i16) {
    // カスタムデフォルトハンドラ
}

irqn引数は、どの例外が処理されているかを示します。負の値は、Cortex-Mの例外が処理されていることを意味します。 ゼロまたは正の値は、デバイス固有の例外、すなわち、割り込みが処理されていること、を示しています。

ハードフォールトハンドラ

HardFault例外は、少し特別です。この例外は、プログラムが不正な状態になった場合に発生します。 そのため、このハンドラはリターンすることができず、未定義動作を引き起こす可能性があります。 ランタイムクレートは、デバッグ性を向上するために、ユーザ定義のHardFaultハンドラが呼び出される前に、少し仕事をします。

その結果、HardFaultハンドラは、fn(&ExceptionFrame) -> !のシグネチャを持つ必要があります。 ハンドラの引数は、例外によってスタックにプッシュされたレジスタへのポインタです。 これらのレジスタは、例外が発生した瞬間のプロセッサステートのスナップショットで、ハードフォールトの原因を突き止めるのに便利です。

不正な操作を行う例を示します。存在しないメモリ位置への読み込みです。

注記:このプログラムは、QEMU上ではうまく動きません。つまり、クラッシュしません。 qemu-system-arm -machine lm3s6965evbはメモリの読み込みをチェックしないため、 無効なメモリを読み込むと、幸いにも、0を返します。
#![no_main]
#![no_std]

use panic_halt as _;

use core::fmt::Write;
use core::ptr;

use cortex_m_rt::{entry, exception, ExceptionFrame};
use cortex_m_semihosting::hio;

#[entry]
fn main() -> ! {
    // 存在しないメモリ位置を読み込みます
    unsafe {
        ptr::read_volatile(0x3FFF_0000 as *const u32);
    }

    loop {}
}

#[exception]
fn HardFault(ef: &ExceptionFrame) -> ! {
    if let Ok(mut hstdout) = hio::hstdout() {
        writeln!(hstdout, "{:#?}", ef).ok();
    }

    loop {}
}

HardFaultハンドラは、ExceptionFrameの値を表示します。実行すると、 OpenOCDコンソールに次のような表示が見えるでしょう。

$ openocd
(..)
ExceptionFrame {
    r0: 0x3fff0000,
    r1: 0x00000003,
    r2: 0x080032e8,
    r3: 0x00000000,
    r12: 0x00000000,
    lr: 0x080016df,
    pc: 0x080016e2,
    xpsr: 0x61000000,
}

pcの値は、例外発生時のプログラムカウンタの値で、例外を引き起こした命令を指しています。

プログラムのディスアセンブル結果を見ます。

$ cargo objdump --bin app --release -- -d --no-show-raw-insn --print-imm-hex
(..)
ResetTrampoline:
 8000942:       movw    r0, #0xfffe
 8000946:       movt    r0, #0x3fff
 800094a:       ldr     r0, [r0]
 800094c:       b       #-0x4 <ResetTrampoline+0xa>

翻訳が古くなっています ロード命令(ldr r0, [r0])が例外を発生させたことがわかります。そして、この時のr0レジスタの値は、 0x3fff_fffeです。この値は、ExceptionFramer0フィールドと一致します。