割り込み禁止と排他制御の基本。セマフォ、ミューテックス、スピンロック解説

ソフトウェア開発

こんにちは!現役の組み込みエンジニアのです。

今回は、新人エンジニアが現場で必ずぶつかる「割り込み」や「排他制御」の基本について解説します。特に、これから本格的にマルチタスクやマルチコアCPUの開発に関わる方には、必ず理解しておきたい内容です。文章はできるだけわかりやすく、例え話も交えて説明していきますので、ぜひ最後までお付き合いください!



組み込みソフトウェア開発の基本、割り込みとは?

組み込みソフトウェアにおいて「割り込み(Interrupt)」は、処理の流れを途中で中断し、緊急の処理を優先的に行うための仕組みです。例えるなら、あなたがテレビを見ているときに、電話がかかってきて、一旦テレビを止めて電話に出るようなイメージです。

組み込み開発では、センサーからのデータ受信、ボタン押下、タイマーイベントなど、即時に対応すべき事象に対して割り込み処理が使われます。CPUは通常の処理を行っていますが、割り込み信号を受け取ると、現在の処理を一時停止し、割り込みハンドラと呼ばれる特別な関数を実行します。

割り込み禁止とは?

割り込み処理は便利ですが、問題もあります。たとえば、割り込み中にさらに割り込みが発生すると、予期しない動作や競合が発生することがあります。そのため、割り込み禁止というテクニックがあります。これは、重要な処理をしている間は割り込みを一時的に無効にして、割り込み処理が割り込んでこないようにする方法です。基本的にマイコンの持っている機能で実装します。

__disable_irq(); // 割り込み禁止
// クリティカルセクション
__enable_irq();  // 割り込み許可(禁止解除)

ただし、割り込みを長時間禁止すると、他の割り込みによって実現している重要な処理が遅れてしまうことがあります。したがって、割り込み禁止の範囲はできるだけ短くするのが原則です。

セマフォ、ミューテックスとは?

割り込み禁止と深い関係性をもつ制御として、リソースへのアクセス制限を制御する「セマフォ」や「ミューテックス」があります。

セマフォ(Semaphore)

セマフォは、複数のタスクが同じリソースを使う際に、後から使用を試みたタスクに対して「使用中だから待ってね」といった信号を出す比較的簡単な仕組みです。

xSemaphoreTake(sema, portMAX_DELAY); // セマフォ取得
// リソース使用
xSemaphoreGive(sema); // セマフォ解放

セマフォはカウンタを持ち、値がゼロのときはリソースを取得できません。また、バイナリーセマフォとカウンティングセマフォの2種類があり、バイナリーセマフォは、0または1の値を持ち、リソースの状態を表します。カウンティングセマフォは、整数のカウンターを持ち、リソースの使用可能な数を表します。共有リソースに対応する1つの変数を用意しておき、共有リソースにアクセスするプロセスがその変数をインクリメント(加算)することで自分が占有していることを明示することで他のプロセスが対象の共有リソースにアクセスすることを未然に防止する仕組みです。

ミューテックス(Mutex)

Mutual Exclusion(相互排除)の略で、Mut-ex。ミューテックスは、カウンタが0/1しかないバイナリーセマフォに似た仕組みですが、セマフォよりも厳格で、リソースのロックを解除できるのはリソースをロックしたタスクだけです。

マルチコアCPUの排他制御とは?

最近のマイコンやSoCでは、複数のCPUコア(マルチコア)が搭載されることが増えてきました。複数のコアが同じメモリ空間や周辺機器を同時に使うと、やはり競合が起きます。

シングルコアでは割り込み禁止やミューテックスで何とかなっていたものも、マルチコアになると通用しない場合があります。なぜなら、割り込み禁止は自分のコア内だけの制御だからです。

このため、マルチコア対応の排他制御が必要になります。

代表的な方法のひとつが次に紹介する「スピンロック」です。

スピンロックとは?

スピンロック(Spinlock)は、ロックが解放されるまで何度もループして待ち続ける排他制御の手法で、複数のコアが共有して利用するリソースに対してアクセスする際に使用することを想定した制御です。例えるならせっかちな我慢できない子供がガン見しながら待ってるみたいなものです。一方のコアが共有リソースを書き換えている最中に他方のコアが中途半端に書き換え途中のデータを読み取ったりすることを防ぐ、という使い方が代表的です。

while (__sync_lock_test_and_set(&lock, 1)) {
    // ロックが解放されるまで待つ(スピン)
}
// クリティカルセクション
__sync_lock_release(&lock);

この方式は割り込み禁止のように手軽で、OSを使わなくてもマルチコアで使えるのがメリットです。

ただし、ロックが長く保持されると、他のコアが無限ループに陥りCPUリソースを浪費するため、短時間のロックに限定して使うのがベストです。どうしてもロック時間を長くとらなければならない場合でも、1回のに長くロックするのではなく、短い時間で複数回ロックする方がパフォーマンスへの影響を小さくできるといわれています。

デッドロックとは?

排他制御を適切に行わないと発生するトラブルの1つが「デッドロック」です。デッドロックとは、複数のタスクが互いにリソースの解放を待ち続けることで、永遠に処理が進まなくなる状態のことを指します。

例えば、

  • タスクAがリソース1を取得
  • タスクBがリソース2を取得
  • その後、タスクAがリソース2を待ち、タスクBがリソース1を待つ

という状況になると、どちらも相手が解放してくれるのを待っているため、永久に処理が進まなくなります。

デッドロックを防ぐには?

  1. リソース取得ルールを設ける:例えば、1回の排他制御の中で複数の共有リソースへのアクセスを禁止する、あるいは、すべてのタスクが同じ順番でリソースを取得するようにすれば、循環待ちを避けられます。
  2. タイムアウトを設ける:スピンロック取得時に待機スピン回数にタイムアウトを設定し、一定回数内に取得できなければ処理を中断することで、システム全体の停止を防ぎます。
  3. なるべくロックの数を減らす:複数のロックを扱う設計そのものを見直し、単一のリソースで済むように構成するのも有効です。

デッドロックは発見しにくく、再現性も低いため、事前の設計段階で防ぐことが非常に重要です。

まとめ

今回は、組み込み開発で避けて通れない「割り込み」や「排他制御」について、基本的な考え方から具体的なテクニックまで紹介しました。

  • 割り込みとは、緊急の処理を優先する仕組み
  • 割り込み禁止は短時間で使うのが原則
  • セマフォやミューテックスはRTOSを活用した柔軟な排他制御
項目ミューテックスバイナリー
セマフォ
カウンティング
セマフォ
値の範囲0または10または10以上の整数
所有権あり
(ロック者のみ解除可能)
なし
(誰でも解除可能)
なし
(誰でも解除可能)
  • マルチコア環境ではスピンロックなどの別の排他制御が必要
項目まとめ
スピンロックリソース解放されるまで待機
メリット高速かつ実装がシンプル
デメリットCPU処理能力の無駄遣いが発生する
用途短時間のロック

これらは一見難しく感じるかもしれませんが、何度もコードを書いて、動かして、トラブルにぶつかって…を繰り返すうちに、自然と身についていきます。

それでは、よいデバッグライフを!

コメント

タイトルとURLをコピーしました