TL

メモリ保護ユニット(MPU)

MMUのないマイコンでも、タスクの暴走やスタックオーバーフローをハードウェアで検出・封じ込められる。MPUの領域設定・特権制御・実行禁止の原理を押さえれば、堅牢な組込みシステムを設計できるようになる。

応用組込みMPUメモリ保護Cortex-MRTOSセキュリティ最終更新: 2026-06-21
TL;DR要点だけ先に
  • 1.MPUはアドレス空間をいくつかの領域に区切り、領域ごとに読み書き実行の権限と特権/非特権の別を与えるハードウェア。違反アクセスは即座にフォルト例外を発生させ、暴走を波及前に止める。
  • 2.MMUと違いアドレス変換(仮想→物理)は行わず、物理アドレスに対する保護のみを担う。ページテーブルもTLBもなく、レジスタ設定だけで動くため確定的かつ軽量で、RTOSのタスク分離やスタック境界の監視に使う。
  • 3.実行禁止(XN)でデータ領域からの命令実行を禁じ、コード注入攻撃やスタック実行を防ぐ。スタック直下にアクセス不可のガード領域を置けば、オーバーフローをHardFaultとして即検出できる。

MMUなきマイコンで、どうメモリを守るか

PC やサーバーでは MMU(メモリ管理ユニット)が仮想記憶とプロセス分離を一手に担い、あるプロセスのバグが他プロセスや OS を壊すことを防ぎます。ところがマイコン(MCU)の多くは、確定性と低消費電力を優先して MMU を持ちません(MCU と MPU の本質差は /embedded/microcontroller-architecture/ 参照)。すると、あるタスクの配列オーバーランが平然と別タスクの変数やカーネルのデータを書き換え、原因究明の難しいメモリ破壊が起こります。

この穴を、仮想記憶という重い仕組みなしに埋めるのが MPU(Memory Protection Unit) です。MPU はアドレス空間をいくつかの「領域(region)」に区切り、領域ごとにアクセス権限を設定します。CPU が発行するアドレスは、命令実行と同じサイクルで MPU の権限チェックを通り、許可されていないアクセスはその場でフォルト例外として弾かれます。バグの影響が波及する前に、ハードウェアが即座に止めるわけです。

「保護」だけを担う——変換はしない

MPU の役割はアクセス権限の検査に限られます。CPU が出した物理アドレスをそのまま使い、「この番地へこの権限でアクセスしてよいか」だけを判定します。番地の付け替え(仮想アドレスから物理アドレスへの変換)は一切行いません。この割り切りが、ページテーブル参照や TLB を不要にし、アクセス判定を1サイクル相当・完全に確定的な時間で終わらせる鍵です。

領域・属性・特権:MPUの3つの設定軸

MPU の設定は「どの範囲を」「どんな属性で」「誰に対して」保護するか、の3軸に集約されます。Arm Cortex-M を例に取ると、MPU は複数の領域(実装により 8 個や 16 個)を持ち、各領域に対して次を指定します。

設定項目指定する内容典型的な使い方
ベースアドレスとサイズ領域の開始番地と大きさコード・データ・スタック・ペリフェラルを別領域に切る
アクセス権限(AP)読み/書きの可否を特権・非特権それぞれにコードは読み取り専用、周辺は特権のみ書込可 等
実行許可(XN)その領域で命令フェッチを許すかデータ/スタック領域は XN=1 で実行禁止
メモリ属性キャッシュ可否・順序保証(Normal/Device/Strongly-ordered)SRAMはNormal、ペリフェラルはDeviceで投機を抑止

アクセス権限(AP: Access Permission)が保護の核心です。ここで登場するのが特権(privileged)と非特権(unprivileged)の2階層です。Cortex-M の CPU は、例外ハンドラや RTOS カーネルが動くハンドラモード/特権スレッドと、アプリタスクを動かす非特権スレッドを区別できます。AP はこの2階層それぞれに対して読み書きの可否を持つため、たとえば「カーネルのデータは特権では読み書き可、非特権ではアクセス不可」という設定が可能になります。代表的な権限の組み合わせは次の通りです。

Cortex-M のアクセス権限(AP)の主な設定パターン

  特権 = 読み書き / 非特権 = アクセス不可   → カーネル専用領域
  特権 = 読み書き / 非特権 = 読み書き        → タスク間の共有データ
  特権 = 読み書き / 非特権 = 読み取りのみ    → 設定値をタスクに見せるが変更不可
  特権 = 読み取り / 非特権 = 読み取り        → const・コード(書込禁止)
  アクセス不可(特権・非特権とも)           → ガード領域(後述)

非特権タスクが、権限で禁じられた番地へアクセスしようとした瞬間、MPU はフォルトを上げます。カーネルの完全性(integrity)を、CPU の実行そのもので担保できるのが MPU の価値です。

実行禁止(XN):データを命令として実行させない

権限とは独立した重要属性が XN(eXecute Never、実行禁止) です。XN=1 を設定した領域では、たとえ読み取りが許されていても、その番地からの命令フェッチが禁止されます。フェッチしようとするとフォルトになります。

これはセキュリティ上の要石です。バッファオーバーフローで攻撃者がスタックやヒープに機械語を送り込み、それを実行させる——という古典的なコード注入攻撃は、「データ領域のバイト列を CPU が命令として実行できる」ことに依存します。スタック・ヒープ・その他データ領域をすべて XN にしておけば、たとえ書き込めても実行はできず、この攻撃系統を根本から断てます(PC の世界の DEP / NX ビットと同じ発想を、MPU がハードウェアで提供します)。

裏返せば、コードを置く領域は書き込み禁止にします。コード領域を「読み取り専用かつ実行可」、データ領域を「読み書き可かつ実行禁止」と設定すれば、W^X(Write XOR Execute、書き込みと実行を同時に許さない)の原則が MPU 上で成立します。

W^X を MPU で徹底する

書き込み可能な領域はすべて XN(実行禁止)、実行可能な領域はすべて書き込み禁止——この2条件を全領域で守れば、「今まさに書き換えられるメモリを命令として走らせる」経路が消えます。ファームウェア更新中に一時的にフラッシュを書き換える等の例外はあり得ますが、通常運用ではデータと命令の領域属性を排他にするのが堅牢な既定設計です。フラッシュ書換えを伴う更新の作法は /embedded/bootloader-ota-update/ も参照。

タスク分離とスタックオーバーフロー検出

RTOS 上でのタスク分離が、MPU の代表的な実務用途です。各タスクは自分専用のスタックを持ちますが(RTOS のタスク管理は /embedded/rtos-scheduling-rms-edf/ 参照)、MMU がなければタスク A のスタックが溢れてタスク B のスタックや変数を静かに破壊し得ます。ベアメタルでスタック暴走が無警告で起こる事情は /embedded/bare-metal-firmware/ の通りで、MPU はこの検出をハードウェアに肩代わりさせます。

やり方は明快です。各タスクのスタックの直下(伸びていく先の隣)に、アクセス不可のガード領域を1つ置くのです。Cortex-M のスタックは番地の小さい方向へ伸びるので、スタックの底より下にガードを敷きます。スタックがガード領域へ踏み込んだ瞬間、その書き込みは権限違反となり MPU がフォルトを発生させます。破壊が起きた「後」にログから推測するのではなく、破壊の「起点」でハードウェアが停止するため、原因の特定が容易になります。

MPU によるスタックガードの配置(アドレスは下ほど小さい=スタックは下へ伸びる)

   高位アドレス ┌──────────────────────┐
               │  タスクA スタック上端  │  ← 初期 SP はここ付近
               │        (使用中)       │
               │          ↓ 伸長        │
               │  タスクA スタック下端  │
               ├──────────────────────┤
               │  ガード領域(数十〜数百B)│  ← MPU: アクセス不可
   低位アドレス └──────────────────────┘
               ここへ踏み込むと即 MemManage フォルト

さらに一歩進めて、コンテキストスイッチのたびに MPU 領域を切り替える設計もあります。タスクを走らせる直前に、そのタスク自身のスタックと専用データだけを非特権でアクセス可能にし、他タスクの領域は非特権からアクセス不可へ設定し直します。こうすると、あるタスクは他タスクのメモリに触れられなくなり、MMU なしでも「プロセスに近い分離」が実現します。RTOS のスケジューラ(特権側)がスイッチ時に MPU レジスタを書き換えるのが実装の勘所です。

MPUのサイズ・整列制約に注意(特に旧アーキ)

Armv7-M(Cortex-M3/M4/M7)の MPU は制約が強く、各領域のサイズは 2 のべき乗(32 バイト以上)で、しかもベースアドレスがそのサイズの倍数に整列していなければなりません。半端な大きさの構造体をぴったり保護しようとすると、この制約でうまく囲えないことがあります。新しい Armv8-M(Cortex-M23/M33 等)の MPU はこの縛りが緩み、32 バイト単位の任意の開始・終了番地で領域を指定できます。対象コアの世代で設定の作法が変わる点に注意してください。

MMUとの違いを正確に押さえる

MPU と MMU はどちらもメモリを守りますが、機構も守備範囲も別物です。混同は設計判断を誤らせるため、要点を対比します。

観点MPU(保護ユニット)MMU(管理ユニット)
アドレス変換行わない(物理アドレスを直接使用)仮想→物理へ変換する(ページ単位)
管理の粒度と機構少数の領域をレジスタで直接設定ページテーブル+TLBで広大な空間を管理
提供する機能アクセス権限・XN・メモリ属性の検査変換に加え仮想記憶・ページング・プロセスごとの独立空間
時間的性質確定的(判定はほぼ1サイクル相当)TLBミス時にテーブルウォークが入り可変遅延
主な搭載先Cortex-M(マイコン, RTOS/ベアメタル)Cortex-A(アプリプロセッサ, Linux等)

決定的な差はアドレス変換の有無です。MMU は仮想アドレスを物理アドレスへ翻訳する過程で保護も掛けますが、MPU は翻訳をせず物理アドレスに保護だけを重ねます。この違いから、MMU はデマンドページングやスワップ、プロセスごとに独立した仮想空間といった大規模 OS 向けの機能を持つ一方、TLB ミス時のテーブルウォークで遅延が変動します。MPU にはページテーブルも TLB もないため、アクセス判定は常に一定時間で、リアルタイム性を崩しません。組込み Linux(MMU 前提)と RTOS(MPU で足りる)の選び分けは /embedded/embedded-linux-vs-rtos/ が詳しいです。要するに、MPU は MMU の機能サブセットではなく、「変換を捨て、確定的な保護だけを取った」別カテゴリと理解するのが正確です。

フォルトの発生と原因の切り分け

MPU が違反を検出すると、Cortex-M では MemManage フォルト(メモリ管理フォルト、例外番号 4)が上がります。これは割り込みと同じ例外機構で処理され、専用のハンドラ MemManage_Handler へ制御が移ります(例外・ベクタテーブルの一般論は /embedded/interrupt-handling-isr/ 参照)。

原因の切り分けには、フォルトステータスレジスタを読むのが定石です。Cortex-M では SCB->CFSR(Configurable Fault Status Register)の下位バイト(MMFSR)に、何が起きたかを示すビットが立ちます。

MemManage フォルトの主因(MMFSR の主要ビット)

  IACCVIOL : 命令アクセス違反。XN 領域から命令をフェッチしようとした
  DACCVIOL : データアクセス違反。権限のない領域を読み書きしようとした
  MSTKERR  : 例外入口の自動スタッキング時にフォルト(スタック溢れの兆候)
  MUNSTKERR: 例外復帰の自動アンスタッキング時にフォルト
  MMARVALID: MMFAR に違反アドレスが格納されている(=下の番地が有効)

  違反した番地は SCB->MMFAR(MemManage Fault Address Register)で読める

つまり、MMARVALID が立っていれば MMFAR からどの番地へのアクセスが弾かれたかを、IACCVIOL/DACCVIOL から実行違反か読み書き違反かを、その場で特定できます。スタックガードに踏み込んだケースなら、フォルト発生時にハンドラでこれらを吸い上げ、どのタスクが境界を越えたかをログに残せます。これが「破壊の起点で止める」デバッグの実像です。

MemManage を有効化しないとHardFaultに化ける

MemManage フォルトは既定で無効なことがあり、その場合 MPU 違反はすべて HardFault へエスカレートします。HardFault は最上位のまとめ役の例外で、MMFSR/MMFAR による細かな原因情報が得られにくく、切り分けが一気に難しくなります。SCB->SHCSRMEMFAULTENA ビットを立てて MemManage を明示的に有効化しておくのが、意味のあるフォルト解析の前提です。なお、フォルトハンドラ自身がさらにフォルトを起こすと二重フォルト(HardFault へ)となるため、ハンドラ内では保護対象外の安全な領域だけを触るのが鉄則です。最終防壁としての自動リセットは /embedded/watchdog-timer/ と組み合わせます。

資格・面接で問われる勘どころ

「MPU と MMU の違い」は『MMU は仮想→物理のアドレス変換を行い仮想記憶を提供する。MPU は変換をせず、物理アドレスへのアクセス権限・実行禁止・メモリ属性の検査だけを確定的な時間で行う』が採点ポイント。「MPU で何ができるか」は『領域ごとに特権/非特権の読み書き権限と XN を設定し、違反を MemManage フォルトで即検出。タスク分離やスタックガードに使う』。「スタックオーバーフローの検出法」は『スタックの伸長先にアクセス不可のガード領域を MPU で置き、踏み込んだ瞬間にフォルトを起こす』と答えます。

まとめ

  • MPU はアドレス空間を少数の領域に区切り、領域ごとにアクセス権限・実行禁止(XN)・メモリ属性を設定するハードウェア。違反アクセスは即座にフォルト例外で弾かれ、暴走の波及をハードウェアが止める。
  • アドレス変換は行わない点が MMU との決定的な差。ページテーブルも TLB もなく、物理アドレスに保護だけを重ねるため、判定は確定的な時間で終わりリアルタイム性を崩さない。MPU は MMU のサブセットではなく別カテゴリ。
  • 特権/非特権の2階層に対して読み書き可否を設定でき、カーネル領域を非特権から遮断してシステムの完全性を守る。XN はデータ領域からの命令実行を禁じ、コード注入攻撃と W^X 違反を封じる。
  • タスク分離では、各スタックの伸長先にアクセス不可のガード領域を置いてオーバーフローをフォルトで検出し、コンテキストスイッチごとに MPU を切り替えれば MMU なしでもタスク間分離が実現する。
  • 違反時は MemManage フォルトが上がり、CFSR(MMFSR) と MMFAR から違反種別と番地を特定できる。ただし MemManage を有効化しないと HardFault に化けて解析が難しくなる。前提知識は /embedded/microcontroller-architecture//embedded/interrupt-handling-isr/ を参照。

組込み・IoT Article

メモリ保護ユニット(MPU)を実務で読む

TL;DRは入口です。実際に選ぶ・使う段階では、何を解決するか、何と比較するか、導入後にどこで詰まるかまで見る必要があります。

解決すること

組込み

比較で見る軸

難易度: advanced / カテゴリ: 組込み・IoT / タグ数: 6

導入後に効く点

MMUと違いアドレス変換(仮想→物理)は行わず、物理アドレスに対する保護のみを担う。ページテーブルもTLBもなく、レジスタ設定だけで動くため確定的かつ軽量で、RTOSのタスク分離やスタック境界の監視に使う。

先に潰すリスク

用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。

数字・仕様の読み方
難易度
advanced
カテゴリ
組込み・IoT
タグ数
6

判断チェックリスト

  • 自社の用途が「組込み / MPU」に近いか確認する。
  • 強みである「MPUはアドレス空間をいくつかの領域に区切り、領域ごとに読み書き実行の権限と特権/非特権の別を与えるハードウェア。違反アクセスは即座にフォルト例外を発生させ、暴走を波及前に止める。」が本当に評価軸になるか確認する。
  • 注意点の「用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。」を運用で吸収できるか確認する。
  • 公開値や仕様値は、対象プラン・対象機種・対象リージョンまで確認する。
  • 既存システム、ID、ネットワーク、監視、バックアップとの接続方法を先に洗い出す。
  • 小さく試してから、本番移行、権限設計、障害時手順、コスト監視を決める。

次に確認する観点

組込みMPUメモリ保護Cortex-MRTOS組込みMPUメモリ保護