背景
Linuxで使うことが前提の Raspberry Pi ではユーザのプログラムは当然のことながら OS のスケジューラのもとに動きます。そのため時間に厳密なプログラムを書くのはとても難しいです。端的に言うと、正確に一定間隔で何かを処理するのが苦手です。こんなときはタイマ割り込みもしくは時計のポーリングなのですが、オーバーヘッドの少なさとかコンテキストの単純さを考えて後者を使いたい場合も多いと思います。その際、各種割り込みが邪魔をします。何か単純な一時的な割り込みの禁止ができたらいいなと思っていろいろやってみたらできたので記事にしました。おっさんにはおなじみのZ80のDI/EIです。
できること
gettimeofday()
をポーリングして100 usの周期動作をするプログラム (後述) を書きました。標準の場合と割り込みを禁止した場合の結果 (実測した周期) がこちらです。
どうですか? 一目瞭然ですね。割り込みを禁止すればタイミングにシビアないろいろな制御ができそうです。例えば 赤外線リモコン とか。(注: もちろん割り込みを禁止にすることはたくさんの副作用を伴います。禁止時間は最小限にとどめましょう。)
やりかたの概要
Raspberry Pi の SoC BCM2835/2837 のマニュアルによれば "interrupt disable register" と "interrupt enable register" というのがあるようなのでこれを直接叩きます。GPIOでおなじみの /dev/mem
を mmap()
するあれです。
-
mmap()
して物理アドレスを直接叩けるようにする。 - レジスタを読み出して、現在の設定情報を保存しておく。
- interrupt disable register にオール1を書き込んですべての割り込みを禁止する。
- 時間にシビアな必要な処理を行う。
- interrupt enable register に 2. で保存した値を書き込み最初の状態に戻す。
とこれだけです。Z80的に言うと、2〜3がDI
, 5がEI
です。
コード例
上記のグラフのデータを作成するのに使ったコードです。お使いのRaspberry Pi に合わせて #define PERI_BASE PI?_PERI_BASE
の部分を変えてください。
#include <stdio.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/time.h>
#define BLOCK_SIZE (4 * 1024)
#define PI2_PERI_BASE 0x3f000000
#define PI1_PERI_BASE 0x20000000
#define PERI_BASE PI1_PERI_BASE // Raspberry 0/1 or 2/3
// 指定時間 (us) ポーリングで待つ
inline static void waitUs(struct timeval tv_zero, int until_us) {
struct timeval tv;
int us;
while (1) {
gettimeofday(&tv, NULL);
us = (tv.tv_sec - tv_zero.tv_sec) * 1000000 + (tv.tv_usec - tv_zero.tv_usec);
if (us >= until_us) break;
}
}
int main() {
int mem_fd;
char *map;
int i;
int us_next;
struct timeval tv0, tv1;
int dt[10000];
int irq1, irq2, irq3; // 割り込み情報保存よう (最後に irqen に書き戻す)
volatile unsigned int *irqen1;
volatile unsigned int *irqen2;
volatile unsigned int *irqen3;
volatile unsigned int *irqdi1;
volatile unsigned int *irqdi2;
volatile unsigned int *irqdi3;
if ((mem_fd = open("/dev/mem", O_RDWR|O_SYNC)) < 0) return -1;
map = (char*) mmap(NULL,
BLOCK_SIZE,
PROT_READ | PROT_WRITE,
MAP_SHARED,
mem_fd,
PERI_BASE + 0xb000
);
if (map == MAP_FAILED) return -1;
irqen1 = (volatile unsigned int *) (map + 0x210);
irqen2 = (volatile unsigned int *) (map + 0x214);
irqen3 = (volatile unsigned int *) (map + 0x218);
irqdi1 = (volatile unsigned int *) (map + 0x21c);
irqdi2 = (volatile unsigned int *) (map + 0x220);
irqdi3 = (volatile unsigned int *) (map + 0x224);
gettimeofday(&tv0, NULL);
irq1 = *irqen1;
irq2 = *irqen2;
irq3 = *irqen3;
// 以下3行で全部の割り込みを禁止する
*irqdi1 = 0xffffffff;
*irqdi2 = 0xffffffff;
*irqdi3 = 0xffffffff;
for (i = 0, us_next = 100; i < 10000; i++, us_next += 100) {
waitUs(tv0, us_next);
gettimeofday(&tv1, NULL);
dt[i] = (tv1.tv_usec - tv0.tv_usec) + (tv1.tv_sec - tv0.tv_sec) * 1000000;
}
// 以下3行で割り込み許可フラグをもとに戻す。
*irqen1 = irq1;
*irqen2 = irq2;
*irqen3 = irq3;
// 結果表示
for (i = 1; i < 10000; i++) {
printf("%d\n", dt[i] - dt[i - 1]);
}
return 0;
}