Raspberry Pi 日記 (part2)

長嶋 洋一


2013年7月1日(月)

いよいよ7月、Shetching2013と前期終了の月に突入である。 主催者のMike Kuniavskyからは、"Hi! We're very excited here as we get the last pieces of Sketching in Hardware 2013 ready. Alex and Leah are staring down restaurants and drawing up contracts, while I put together the schedule." などというメイルも届いた。 おいちゃんからは、先週完成した映像作品のサウンドトラックについて改訂依頼が来て、改訂されたVコン映像も届いた。 これは今週中には改訂の作曲をするつもりで、Raspberry Piのpriorityを一つ下げることにした。

先週土曜日は、午後に「40虎」の新入生と、 こんなツアー に行ったりしたが、その後、学生が某秘蔵映像の上映会に集中している傍らで、あれこれ検索して、遂に以下のようなPythonプログラムで、C言語とほぼ同様のシステムコールが出来ることを突き止めていた。

#!/usr/bin/env python
import os
s = './GPIO_out' + ' ' + str(0) + ' ' + str(0) + ' ' + str(1)
os.system(s)
そこでこの日は、2限の「音楽情報科学」と5限のアカペラ補習特訓の合間に、おいちゃん作曲をメインに据えるとして、とりあえず上のPythonプログラムをC言語版と同じようにループで回して速度を測るところまで、やってみる事にした。 考えてみればきちんとPythonプログラムをゼロから書いたことは無かったので、 Python公式ページリファレンス を参照しつつ、という作業である。 for文のループは「in range(x1, x2)」で、初期値x1からX2未満まで繰り返すこと、最後にコロンが無いとエラーになる事(^_^;)などを経て、15分ほどで無事に以下のようなPython版のルーブ実験プログラムが完成した。
#!/usr/bin/env python
import os
for j in range(0, 128):
    s = './GPIO_out' + ' ' + str(1) + ' ' + str(j) + ' ' + str(1)
    os.system(s)
    for i in range(0, 256):
        s = './GPIO_out' + ' ' + str(0) + ' ' + str(i) + ' ' + str(1)
        os.system(s)

YouTube

上の動画のように、EXTポートの7ビットのうち「ビット0」をストップウォッチで測ったら、およそ3.1秒程度の点灯/消灯時間だった。 つまり、7ビットのLSBの点灯時間が3100ミリ秒とすると、この時間内に8ビットのGPIOポートを256回、アクセスしているので、計算上は内側のGPIO_out()をシステムコール経由で1回アクセスする処理時間は約12ミリ秒、という概算となる。 これはまぁ、C言語版とほぼ同様であり、つまりはLinuxのシステムコールにかかるオーバーヘッドが処理時間の大部分を占めている、上位階層の高級言語レベルでの差はほとんど無い、ということになる。 PythonでもC言語並みに処理してくれる、というのは、今後に向けての朗報である。 ここまで゛朝の8時前にアッサリと出来てしまったので、作曲は午後にまとめて行うことにして、2限までの時間、もう少し調べてみることにした。 遠い昔に、SGIのIndyワークステーション(Unix)でも実験していた事だが、完全に忘却した20年ぶりに挑戦して、どれだけ出来るか、成長しているのかいないのかが問われそうだ(^_^;)。

Raspberry Piは高機能なので、いくつもの仕事を時分割多重処理させたい。 LEDを連続点灯するジョブはシステムコールの末尾に「&」を添えればバックグラウンド処理で実行できるが、そのバックグラウンド処理に対して、他のプロセスからリアルタイムにパラメータを引き渡したいのである。 具体的には、Raspberry PiのGPIOポートに、Propeller/Arduino/AKI-H8/Gainerをホストとして開発した「SUAC board」を接続して、その先にある64個のLEDを、単純なON/OFFでなく、個別にPWM制御してみたい。 そのためには、Raspberry PiのPWM制御プログラムに対して、外部から、64個の個々のLEDのPWM値(0-255)を与える必要があるが、いちいち上記のようなシステムコールを経由したのでは、呼び出しのたびに不整脈となる可能性があるので、あくまでRaspberry Piプログラムは「実行しっ放し」にしたいのである。

ふーみん本などではこのような場合、Unixでは定番であるが、この64個のLEDのPWM値を保持するサーバをまず走らせて、PWM点灯プログラムはそのサーバの値を参照しては点灯処理するクライアントとして走らせ、さらにリアルタイムにパラメータを変更するためにサーバにアクセスする別のクライアントも走らせる、という事になる。 これはUnix屋さんにとってはもっとも正当的で、定番の実現手法であり、勉強としてもこの定番をやるべきなのかもしれない。 しかし、過去の経験ではライブComputer Musicの領域では、この手法は「遅い」という先入観があるので(^_^;)、まずは「シェアードメモリ(共有メモリ)」の手法に挑戦してみたい、という事なのである。

さっそく「linux shared memory」調べてみると、 Wikipedia では、 "Both the RedHat and Debian based distributions include it by default. Support for this type of RAM disk is completely optional within the kernel configuration file."とあり、RAM diskという形でdefaultでサポートされている模様である。 そして「ramdisk linux」で検索すると、 これ とか これ とか これ とか これ とか これ とか これ とか、たくさん出て来た。 また、「ramdisk Raspberry Pi」で検索しても、 こんなページ が出て来た。 ただし、 このページ では、何故あなたはRamDiskを使おうとするのか、考え直してみては(^_^;)、という記載もあった。 確かにRaspberry Piでは、HDDでなくSDカード(フラッシュメモリ)を使っているので、よほど多量の読み書きを連続しない限りは、普通にディスクアクセスしたものとしても同じなのかもしれないし、RAMディスクだと、いちいちRaspberry Piが起動するたびに設定する必要がある。

しかしまぁ、使う時に一度だけ「おまじない」で起動する、というのは別に構わない(なんなら起動時の自動実行プログラムとしてRAMdiskの生成処理を加えるのもアリ)という事で、とりあえずやってみる事にした。 kernel.org の記述は難しそうだったので、とりあえずもっともアッサリと記述している ここ に従ってみることにした。 必要なのはたった3行である。 まずは以下のように「/media」の下にディレクトリを作る。 ここでは「/media/ramdisk」とした。

sudo mkdir -p /media/<FOLDERNAME>
次はマウントである。 以下のサンプルは2GBだったが、小さくていいので1KBとしてみた(^_^;)。
sudo mount -t tmpfs -o size=1K tmpfs /media/<FOLDERNAME>
以下はアンマウントである。 これは最後に行うだけである。
sudo umount /media/<FOLDERNAME>
これだけとシンプル過ぎるのが逆に心配だが(^_^;)、とりあえずsshしているターミナルからやってみると、以下のようにちゃんと出来た。 root権限(sudo)で作ったRAMディスクのディレクトリだが、一般ユーザのpiでアクセス出来ることも判った。 そして以下のように、新たに作ってマウントしたRAMディスクには、1つだけしかファイルが置けないらしい事が判明した。 ファイル管理のディレクトリ構造を持たないシンプルなものでも、必要に応じてどんどん「/media」の下にディレクトリを作ればいいので、これは問題ない。(^_^)
pi@raspberrypi ~ $ ls -l /media
合計 0

pi@raspberrypi ~ $ sudo mkdir -p /media/ramdisk
pi@raspberrypi ~ $ ls -l /media
合計 4
drwxr-xr-x 2 root root 4096  6月 23 08:07 ramdisk

pi@raspberrypi ~ $ sudo mount -t tmpfs -o size=1K tmpfs /media/ramdisk
pi@raspberrypi ~ $ ls -l /media
合計 0
drwxrwxrwt 2 root root 40  6月 23 08:07 ramdisk

pi@raspberrypi ~ $ sudo rmdir /media/ramdisk
rmdir: `/media/ramdisk' を削除できません: デバイスもしくはリソースがビジー状態です

pi@raspberrypi ~ $ sudo umount /media/ramdisk
pi@raspberrypi ~ $ sudo rmdir /media/ramdisk
pi@raspberrypi ~ $ ls -l /media
合計 0

pi@raspberrypi ~ $ sudo mkdir -p /media/ramdisk
pi@raspberrypi ~ $ ls -l /media
合計 4
drwxr-xr-x 2 root root 4096  6月 23 08:09 ramdisk

pi@raspberrypi ~ $ sudo mount -t tmpfs -o size=1K tmpfs /media/ramdisk
pi@raspberrypi ~ $ ls -l /media
合計 0
drwxrwxrwt 2 root root 40  6月 23 08:09 ramdisk

pi@raspberrypi ~ $ cp test.py /media/ramdisk/
pi@raspberrypi ~ $ ls -l /media/ramdisk
合計 4
-rwxr-xr-x 1 pi pi 258  6月 23 08:14 test.py

pi@raspberrypi ~ $ cp test.c /media/ramdisk/
cp: `/media/ramdisk/test.c' を書き込んでいます: デバイスに空き領域がありません
cp: `/media/ramdisk/test.c' の拡張に失敗しました: デバイスに空き領域がありません
pi@raspberrypi ~ $ sudo cp test.c /media/ramdisk/
cp: `/media/ramdisk/test.c' を書き込んでいます: デバイスに空き領域がありません
cp: `/media/ramdisk/test.c' の拡張に失敗しました: デバイスに空き領域がありません

pi@raspberrypi ~ $ rm /media/ramdisk/test.py
pi@raspberrypi ~ $ ls -l /media/ramdisk
合計 0
-rwxr-xr-x 1 pi pi 0  6月 23 08:17 test.c

pi@raspberrypi ~ $ cp test.py /media/ramdisk/
pi@raspberrypi ~ $ ls -l /media/ramdisk
合計 4
-rwxr-xr-x 1 pi pi   0  6月 23 08:17 test.c
-rwxr-xr-x 1 pi pi 258  6月 23 08:18 test.py

pi@raspberrypi ~ $ cp test.c /media/ramdisk/
cp: `/media/ramdisk/test.c' を書き込んでいます: デバイスに空き領域がありません
cp: `/media/ramdisk/test.c' の拡張に失敗しました: デバイスに空き領域がありません

pi@raspberrypi ~ $ rm /media/ramdisk/test.py
pi@raspberrypi ~ $ ls -l /media/ramdisk
合計 0
-rwxr-xr-x 1 pi pi 0  6月 23 08:19 test.c

pi@raspberrypi ~ $ rm /media/ramdisk/test.c
pi@raspberrypi ~ $ ls -l /media/ramdisk
合計 0

pi@raspberrypi ~ $ cp test.c /media/ramdisk/
pi@raspberrypi ~ $ ls -l /media/ramdisk
合計 4
-rwxr-xr-x 1 pi pi 232  6月 23 08:19 test.c

pi@raspberrypi ~ $ cp test.py /media/ramdisk/
cp: `/media/ramdisk/test.py' を書き込んでいます: デバイスに空き領域がありません
cp: `/media/ramdisk/test.py' の拡張に失敗しました: デバイスに空き領域がありません

pi@raspberrypi ~ $ ls -l /media/ramdisk
合計 4
-rwxr-xr-x 1 pi pi 232  6月 23 08:19 test.c
-rwxr-xr-x 1 pi pi   0  6月 23 08:19 test.py

pi@raspberrypi ~ $ 
上にあるように、RAMディスク領域には1つしかファイルが置けず、新たに加えようとすると、システム的には加わったようでサイズが0の幽霊状態になっている。 また、cでもpyでも、書き込み権限が無いというのはもしかすると困るので、ここで、このRAMディスク領域(/media/ramdisk)に対して、パラメータファイルの読み書きを実験することにした。 データはバイナリでなくても、CでもPythonでも文字列と数値の相互変換は簡単なので、ここではテキストエディタで読み書きできるように、16進数のテキストファイルとする事にした。
pi@raspberrypi ~ $ rcp nagasm@172.16.65.31:Desktop/test.c .
Password:
test.c                                                           100%  325     0.3KB/s   00:00    

pi@raspberrypi ~ $ cat test.c
#include <stdio.h>
#include <stdlib.h>

int main(void)
{
	FILE *fp;	
	if ((fp = fopen("/media/ramdisk/test.txt", "w")) == NULL) {
		printf("file open error!!\n");
		exit(EXIT_FAILURE);
	}
	fputs("0123abc", fp);
	fclose(fp);	
	return 0;
}
pi@raspberrypi ~ $ cc test.c
pi@raspberrypi ~ $ ./a.out
pi@raspberrypi ~ $ ls -l /media/ramdisk
合計 4
-rw-r--r-- 1 pi pi 7  6月 23 08:49 test.txt

pi@raspberrypi ~ $ cat /media/ramdisk/test.txt
0123abc

pi@raspberrypi ~ $ rcp nagasm@172.16.65.31:Desktop/test.c .
Password:
test.c                                                           100%  341     0.3KB/s   00:00    

pi@raspberrypi ~ $ cat test.c
#include <stdio.h>
#include <stdlib.h>

int main(void)
{
	FILE *fp;	
	if ((fp = fopen("/media/ramdisk/test.txt", "w")) == NULL) {
		printf("file open error!!\n");
		exit(EXIT_FAILURE);
	}
	fputs("0123abc9876543210ABCDEF", fp);
	fclose(fp);	
	return 0;
}

pi@raspberrypi ~ $ cc test.c
pi@raspberrypi ~ $ ./a.out
pi@raspberrypi ~ $ ls -l /media/ramdisk
合計 4
-rw-r--r-- 1 pi pi 23  6月 23 08:53 test.txt

pi@raspberrypi ~ $ cat /media/ramdisk/test.txt
0123abc9876543210ABCDEF

pi@raspberrypi ~ $ 
そして上のように、root権限でなく一般ユーザのpiでも、RAMディスクに上書きによって新しいファイルを書き出すことが出来る、と確認できたところで1限が終わり、2限の講義が終わって、午後にはもう少しだけ、これを進めることにした。 ファイルの書き出しと読み込みとを2つのプロセスで同時に実行した場合のセマフォが必要かどうか、というのが最後のチェックポイントだからである。 ネットからC言語のファイルアクセスの記録などを20年ぶりに発掘して(^_^;)、以下のように無事に、RAMディスクに64データを16進数の128文字として書き出すプログラム「test1.c」と、それを読み出して表示するプログラム「test2.c」が完成した。
pi@raspberrypi ~ $ rcp nagasm@172.16.65.31:Desktop/test1.c .
Password:
test1.c                                                          100%  381     0.4KB/s   00:00    

pi@raspberrypi ~ $ cat test1.c
#include <stdio.h>
#include <stdlib.h>

int main(void)
{
	FILE *fp;
	char ss[3], s[130];
	int i;
	for(i=0; i<64; i++){
		sprintf(ss, "%02x", i*4+3);
		s[2*i] = ss[0];
		s[2*i+1] = ss[1];
	}
	s[128] = '\n';	
	s[129] = 0;	
	if ((fp = fopen("/media/ramdisk/test.txt", "w")) == NULL) {
		printf("file open error!!\n");
		exit(EXIT_FAILURE);
	}
	fputs(s, fp);
	fclose(fp);
	return 0;
}

pi@raspberrypi ~ $ ./test1
pi@raspberrypi ~ $ ls -l /media/ramdisk
合計 4
-rw-r--r-- 1 pi pi 129  6月 23 12:28 test.txt

pi@raspberrypi ~ $ cat /media/ramdisk/test.txt
03070b0f13171b1f23272b2f33373b3f43474b4f53575b5f63676b6f73777b
7f83878b8f93979b9fa3a7abafb3b7bbbfc3c7cbcfd3d7dbdfe3e7ebeff3f7fbff

pi@raspberrypi ~ $ rcp nagasm@172.16.65.31:Desktop/test2.c .
Password:
test2.c                                                          100%  324     0.3KB/s   00:00    

pi@raspberrypi ~ $ cat test2.c
#include <stdio.h>
#include <stdlib.h>

int main(void)
{
	FILE *fp;
	char s[130];
	int i;
	if ((fp = fopen("/media/ramdisk/test.txt", "rb")) == NULL) {
		printf("file open error!!\n");
		exit(EXIT_FAILURE);
	}
	for(i=0; i<129; i++){
		s[i] = fgetc(fp);
	}
	s[129] = 0;	

	printf("result :\n%s",s);
	fclose(fp);
	return 0;
}

pi@raspberrypi ~ $ cc test2.c -o test2
pi@raspberrypi ~ $ ./test2
result :
03070b0f13171b1f23272b2f33373b3f43474b4f53575b5f63676b6f73777b
7f83878b8f93979b9fa3a7abafb3b7bbbfc3c7cbcfd3d7dbdfe3e7ebeff3f7fbff

pi@raspberrypi ~ $ 
そこでいよいよ実験である。 作戦としては、RAMディスクのデータファイルを読み出しアクセスするプロセスを無限ループで回して、そこにもう1つのプロセスから同じRAMディスクのデータファイルを書き換えに行ったその瞬間にカチ合うとエラーが出るか、どのくらいの頻度で起きうるか、を調べたいのである。 予備的に調べてみると、システム表示の「printf()」は、相当に中身のデータが溜まるまでバッファリングしていて、刻々のモニタに使えないと判明した。 そこで、「RAMディスクのデータファイルを読み出しアクセスするプロセス」として、以下のように「test2.c」を改訂して、これをRaspberry Piにrcpして、「gcc test2.c -l bcm2835 -o test2」でコンパイルして、「sudo ./test2 &」でバックグラウンド実行させた。 128バイトのファイル読み込みをopenして、読み込みして、closeして、というループを100回。回したところでようやく、GPIO+EXTの15ビットのLSBをインクリメントする、過酷な事実上の無限ループ(GPIO+EXTの15ビットが全てカウントアップするループを10回、回すので1時間以上かかる)である。
#include <stdio.h>
#include <stdlib.h>
#include <bcm2835.>

int assign[15] = {17,18,27,22,23,24,25,4,2,3,10,9,11,8,7};
FILE *fp;
char s[130];

void GPIO_out(int data, int mode){
	int i;
	mode = mode & 1;	// mode=0 : active high / mode=1 : active low
	data = data & 255;
	for (i=0; i<8; i++) {
		bcm2835_gpio_write( assign[i], ( ( (data >>i) & 1) ^ mode ) );
	}
}

void EXT_out(int data, int mode){
	int i;
	mode = mode & 1;	// mode=0 : active high / mode=1 : active low
	data = data & 127;
	for (i=0; i<7; i++) {
		bcm2835_gpio_write( assign[i+8], ( ( (data >>i) & 1) ^ mode ) );
	}
}

void data_file_read(){
	int i;
	if ((fp = fopen("/media/ramdisk/test.txt", "rb")) == NULL) {
		printf("\nread error!!\n");
	}
	else{
		for(i=0; i<129; i++){
			s[i] = fgetc(fp);
		}
		s[129] = 0;	
		fclose(fp);
	}
}

int main(void)
{
	int i, j, l, k=0;
	if (!bcm2835_init()){
		printf("GPIO is not found.\n");
		return 1;
	}
	for (i=0; i<15; i++){
		bcm2835_gpio_fsel(assign[i], BCM2835_GPIO_FSEL_OUTP);
	}
	EXT_out(0, 1);
	GPIO_out(0, 1);
	for (l=0; l<10; l++) {
		for (j=0; j<128; j++) {
			EXT_out(j, 1);
			for (i=0; i<256; i++) {
				GPIO_out(i, 1);
				for (k=0; k<100; k++) {
					data_file_read();
				}
			}
		}
	}
	GPIO_out(0, 1);
	EXT_out(0, 1);
	bcm2835_close();
	return 0;
}
これを走らせている間に、「RAMディスクのデータファイルを上書きで書き換えに行く」という、先に作った「./test1」を、ターミナルからのコピペでマニュアル連打したが、まったく「printf("\nread error!!\n");」が表示されなかった。 ただしこのメッセージも256バイトあたりのバッファに積まれている可能性があるので、いじわるテストとして、「test1.c」を以下のように改訂して実験してみた。
#include <stdio.h>
#include <stdlib.h>

int main(void)
{
	FILE *fp;
	char ss[3], s[130];
	int i, j;
	for(i=0; i<64; i++){
		sprintf(ss, "%02x", i*4+3);
		s[2*i] = ss[0];
		s[2*i+1] = ss[1];
	}
	s[128] = '\n';	
	s[129] = 0;
	for(j=0; j<50000; j++){
		if ((fp = fopen("/media/ramdisk/test.txt", "w")) == NULL) {
			printf("file open error!!\n");
			exit(EXIT_FAILURE);
		}
		fputs(s, fp);
		fclose(fp);
	}
	printf("50000 times write access done.\n");
	return 0;
}
すると、「sudo ./test2 &」で過酷な無限ループが回っているところに、「RAMディスクのデータファイルを上書きで50000回、書き換えに行く」という「./test1」を実行すると、10数秒で「50000 times write access done.」が返ってくるが、何度やっても書き込みopenも読み出しopenもエラーが表示されることは無かった。 もしかすると、Linuxが内部的に待たせているのかもしれないが、とりあえず、このRAMディスクを経由して2つのプロセス間のパラメータ通信を出来そうだ、と確認できた。 ここで4限も半ばとなり、明日の企画立案演習の準備もあるので、今日はここまでである。 なかなかに進んだし、何よりC言語のリハビリとなった。(^_^)

「Raspberry Pi日記」トップに戻る