· 3分で読了

avr-libcにおけるATOMIC_BLOCK

この記事は中国語から自動翻訳されたものです。翻訳によりニュアンスが失われている場合があります。

avr-libc の中に <util/atomic.h> というものがある。僕の最初の反応は「AVRはシングルコアのはずなのに、なぜatomicが必要なんだ?」というものだった。そこで思わずドキュメントを確認してみた。

The macros in this header file deal with code blocks that are guaranteed to be excuted Atomically or Non-Atmomically. The term “Atomic” in this context refers to the unability of the respective code to be interrupted.

マイクロコントローラであっても様々な割り込み(interrupt)が存在するため、コードの実行が途中で中断される可能性がある。atomicを使うことで、中に囲まれたコードブロックが割り込みの影響を受けない(一時的に割り込みを無効化する)ことを保証できる。

ATOMIC_BLOCK(ATOMIC_FORCEON) {
  // do something critical
}

感覚的には cli()sei() の組み合わせのように見えるが、ドキュメントの説明によると他にも色々と行っているようだ。内部の実装は以下のようになっている:

#define ATOMIC_BLOCK(type) for ( type, __ToDo = __iCliRetVal(); \
                           __ToDo ; __ToDo = 0 )

type を引数として受け取ることができ、渡せる引数には ATOMIC_RESTORESTATEATOMIC_FORCEON がある。

#define ATOMIC_RESTORESTATE uint8_t sreg_save \
    __attribute__((__cleanup__(__iRestore))) = SREG

見たところ sei のような効果だが、面白いのは __attribute__ の使い方だ。GCCでは、__attribute__ を使って変数や関数をどのように配置・管理するかを指定できる。この挙動は libc の実装で定義されており、例えばAVRではこのように書くことができる:

#include <avr/pgmspace.h>
const int my_var[2] PROGMEM = { 1, 2 };

ここでの PROGMEM は、実際には __attribute__ の1つだ:

#ifndef __ATTR_PROGMEM__
#define __ATTR_PROGMEM__ __attribute__((__progmem__))
#endif

通常、変数はメモリ(RAM)に格納されるが、マイクロコントローラではメモリが非常に少ないことが多い。しかし一般的なコンピュータとは異なり、マイクロコントローラのコードは通常、事前にコンパイルされてプログラムメモリ(フラッシュメモリ)に直接書き込まれる。コードの量がそれほど多くなければ、フラッシュメモリのほうに余裕が残ることが多い。そんな時、PROGMEM を使うことで変数をフラッシュメモリ内に配置し、メモリの消費量を抑えることができる。読み出し時には pgm_read_word を使って変数を読み出す。

話を ATOMIC_BLOCK に戻すと、これをすべて展開したコードは次のようになる:

for (uint8_t sreg_save __attribute__((__cleanup__(__iRestore))) = 0;  __ToDo = __iCliRetVal(); __ToDo ; __ToDo = 0) {
   // do something critical
}

これにより、コードの実行が割り込みの影響を絶対に受けないように保証している。for loop を使うこのアプローチはなかなか見事で、こんな応用ができるとは僕もまったく思いつかなかった。

関連記事

他のトピックを探索