RPiでAMP環境を構築してみる
今回は、Crosstool-NGで作ったRPiのベアメタル開発環境を使用して、Raspberry Pi(以降、RPi)でAMPシステムを作ってみたいと思います(・`ω´・)b
- AMPシステムとは
- Raspberry Pi OSをインストールしてみる
- Raspberry Pi OSが使用するメモリとCPUコア数を制限してみる
- ベアメタルアプリケーションを作成する
- ベアメタルアプリケーションをAMPで動かす
- Raspberry Pi OSとベアメタルアプリケーションで共有メモリを試す
AMPシステムとは
AMPとはAsymmetrical Multi Processingの略で、マルチコアプロセッサにおいてそれぞれの演算コアで別々のプログラムを動作させることを言います。
最近のRPi(RPi2、RPi3、RPi4のModel B、およびB+など)には、ARM Cortex-AシリーズというMPUが搭載されています。
さらに、これらのMPUはクアッドコアになっているので、これらの演算コアを使用して別々のプログラムを動かしてみたいと思います。
https://ja.wikipedia.org/wiki/Raspberry_Pi
これが今回構築するAMPシステムのイメージです。
今回は、4つの演算コアの内、3コアを使用してRaspberry Pi OSを動作させ、その上でユーザプログラムを動作させます。
※ちなみに複数の演算コアを使用して同じプログラム動作させる方式はSMP(Symmetric Multi Processing)と呼ばれているようです。
残りの1コアを使用してベアメタルアプリケーションを実行します。
さらにRaspberry Pi OSで動作しているユーザプログラムと別コアで動作しているベアメタルアプリケーション間で共有メモリを使用してIPC(Inter Process Communication)させることを目指します。
ちなみに今回はRPi3を使って、32bit環境を対象にシステム構築したいと思います。
なおAMPとSMPの説明は以下がとても分かりやすかったです。
https://news.mynavi.jp/article/20071205-mp/3
Raspberry Pi OSをインストールしてみる
まずは普通にRaspberry Pi OSをインストールします。インストール自体は以下を参考にしてやりました。
最近はより簡単にインストールできるRaspberry Pi Imagerというツールが公式から登場しました。
とても簡単にインストールすることができますヽ(*´∀`)ノ
※そのほか、SSHを使用できるようにしたり、vimをインストールしたりしましたがここでは説明を割愛します。
https://www.indoorcorgielec.com/resources/raspberry-pi/raspberry-pi-osのインストール
準備ができたらRPiにRaspberry Pi OSをインストールしたSDカード、HDMIケーブル、LANケーブル、USBキーボードを接続してUSBケーブルをPCにつないで電源ONします。
電源が入ると起動画面が表示されます。
このときベリーの絵が4つ表示されました。
これはRaspberry Pi OSが演算コアを4つ使用して動作していることを示しているようです。(・・・と、どこかに書いてあったような気がします。)
一応、以下のコマンドを使用してメモリ使用量とCPUコア使用数を確認しておきます。
$ free total used free shared buffer/cache available Mem: 948304 50288 819920 6308 78096 840372 Swap: 102396 0 102396
$ cat /proc/cpuinfo processor : 0 model name : ARMv7 Processor rev 4 (v71) BogoMIPS : 38.40 Features : half thumb fastmult vfp edsp neon vfpv3 tls vfpv4 idiva idivt vfpd32 lpae evtstrm crc32 CPU implementer : 0x41 CPU architecture: 7 CPU variant : 0x0 CPU part : 0xd03 CPU revision : 4 processor : 1 model name : ARMv7 Processor rev 4 (v71) BogoMIPS : 38.40 Features : half thumb fastmult vfp edsp neon vfpv3 tls vfpv4 idiva idivt vfpd32 lpae evtstrm crc32 CPU implementer : 0x41 CPU architecture: 7 CPU variant : 0x0 CPU part : 0xd03 CPU revision : 4 processor : 2 model name : ARMv7 Processor rev 4 (v71) BogoMIPS : 38.40 Features : half thumb fastmult vfp edsp neon vfpv3 tls vfpv4 idiva idivt vfpd32 lpae evtstrm crc32 CPU implementer : 0x41 CPU architecture: 7 CPU variant : 0x0 CPU part : 0xd03 CPU revision : 4 processor : 3 model name : ARMv7 Processor rev 4 (v71) BogoMIPS : 38.40 Features : half thumb fastmult vfp edsp neon vfpv3 tls vfpv4 idiva idivt vfpd32 lpae evtstrm crc32 CPU implementer : 0x41 CPU architecture: 7 CPU variant : 0x0 CPU part : 0xd03 CPU revision : 4
これによるとメモリを1GBフル活用(RPi3の実装RAMは1GB)しており、CPUも4コア使用していることがわかります。
これでRaspberry Pi OSのインストールは完了です(・`ω´・)b
Raspberry Pi OSが使用するメモリとCPUコア数を制限してみる
さて、Raspberry Pi OSにリソースを制限させて動作させるためには、起動時に使用するメモリの量や範囲、演算コアの個数を伝えてやる必要があります。
そのために今回はu-Bootを使用することにしました。
U-Bootは、多種のプラットフォームに対応したブートローダです。当然RPiもサポートされています。
https://ja.wikipedia.org/wiki/Das_U-Boot
https://elinux.org/RPi_U-Boot
u-bootをビルドする
まずはu-bootをビルドします。
コンパイラはCrosstool-NGで作ったRPiのベアメタル開発環境を使います。
ソースはgitから落とします。本家のgit://git.denx.de/u-boot.gitもありますが、ダウンロードが遅いです(・´ω`・)
$ git clone git://github.com/swarren/u-boot.git $ make clean $ make distclean $ make rpi_3_32b_defconfig $ make all
これでRPi3のu-boot(boot.bin)ができます。
なおmakeするとき、使用したいRPiのモデルを指定します。
指定できるdefconfigには以下のものがあります。
- rpi_defconfig : 初代Rpi用
- rpi_2_defconfig : RPi2用
- rpi_3_32b_defconfig : 32bit RPi3用
- rpi_3_defconfig : 64bit RPi3用
- rpi_4_32b_defconfig : 32bit RPi4用
- rpi_4_defconfig : 64bit RPi4用
bootスクリプトを書く
続いてu-bootが使用するbootスクリプトを書きます。
このu-bootはこのスクリプトを使ってRaspberry Pi OSをbootします。
スクリプトは以下のような感じになります。
mmc dev 0 fatload mmc 0:1 ${kernel_addr_r} kernel7.img fatload mmc 0:1 0x19000000 bcm2710-rpi-3-b.dtb setenv bootargs earlyprintk console=tty0 console=ttyAMA0 root=/dev/mmcblk0p2 rootfstype=ext4 rootwait noinitrd mem=512M maxcpus=3 bootz ${kernel_addr_r} - 0x19000000
2行目でロードする.imgファイル(Raspberry Pi OSカーネル)を指定しています。
.imgファイルはRaspberry Pi OSをインストールしたときSDカードの/bootに作られます。
カーネルには以下の種類があります。今回は32bit RPi3用のkernel7.imgを指定します。
- kernel8.img :64bit RPi3、RPi4用
- kernel7l.img :32bit RPi4用(LPAE付)
- kernel7.img :32bit RPi3、RPi4用(LPAEなし)
- kernel.img :上記以外のRPi
3行目はデバイスツリーを配置するアドレスを指定しています。
デバイスツリーもRaspberry Pi OSをインストールしたときSDカードに作られるものを流用します。
今回は1GBあるメモリの内、lowerの512MBをRaspberry Pi OS用、upperの512MBをベアメタルアプリケーション用にしたいと考えています。
そこでデバイスツリーの配置アドレスは、あえて0x19000000(400MB付近)にしました。
4行目では、Raspberry Pi OSの起動オプションを指定しています。
mem=512M maxcpus=3を指定することで、メモリサイズを512MB、使用するCPUのコア数を3個にしてRaspberry Pi OSを起動できるようにはずです。
5行目では、bootzコマンドでカーネルとデバイスツリーを配置させます。
${kernel_addr_r}はたしか0x80000からだったと思います。
したがってここから4行目で指定したmem(=512MB)がカーネルが使用できるメモリ範囲になると思います。
(めんどくさいのでデバイスツリーの開始アドレスを指定していますが(;^_^A
上記スクリプトが書けたらboot.txtとして保存し、以下のコマンドを使用してboot.scrを作成します。
mkimage -A arm -O linux -T script -C none -n boot.script -d boot.txt boot.scr
最後に作成したboot.binとboot.scrをSDカードの/boot直下にコピーします。
そして/boot/config.txtに以下の行を追加して準備完了です。
kernel=u-boot.bin
再びRaspberry Pi OSを起動してみる
これでSDカードをRPiに入れて電源ONすると同じようにRaspberry Pi OSが立ち上がるはずです。
ただし起動プロセスとしてはu-bootが先に起動してRaspberry Pi OSをキックするような流れになっていると思います。
(本当にu-bootが起動しているかはUARTを有効にするなどして確かめるとよいかと思います。)
起動後は再びSSHで接続し、メモリとCPUコア使用数を確認しておきます。
$ free total used free shared buffer/cache available Mem: 506152 40940 397744 3500 67468 411052 Swap: 102396 0 102396
$ cat /proc/cpuinfo processor : 0 model name : ARMv7 Processor rev 4 (v71) BogoMIPS : 38.40 Features : half thumb fastmult vfp edsp neon vfpv3 tls vfpv4 idiva idivt vfpd32 lpae evtstrm crc32 CPU implementer : 0x41 CPU architecture: 7 CPU variant : 0x0 CPU part : 0xd03 CPU revision : 4 processor : 1 model name : ARMv7 Processor rev 4 (v71) BogoMIPS : 38.40 Features : half thumb fastmult vfp edsp neon vfpv3 tls vfpv4 idiva idivt vfpd32 lpae evtstrm crc32 CPU implementer : 0x41 CPU architecture: 7 CPU variant : 0x0 CPU part : 0xd03 CPU revision : 4 processor : 2 model name : ARMv7 Processor rev 4 (v71) BogoMIPS : 38.40 Features : half thumb fastmult vfp edsp neon vfpv3 tls vfpv4 idiva idivt vfpd32 lpae evtstrm crc32 CPU implementer : 0x41 CPU architecture: 7 CPU variant : 0x0 CPU part : 0xd03 CPU revision : 4
これによるとメモリ使用量が512MB(起動時のメッセージを見ると0x80000から配置されていると思います)で
使用CPUコア数が3個になっていることがわかります。
これでRaspberry Pi OSの準備は完了です。
ベアメタルアプリケーションを作成する
ここまででRaspberry Pi OSが3コアで動作するようになったので、残りの1コアでベアメタルアプリケーションを動作させるようにしていきます。
が、まずはベアメタルアプリケーションがちゃんと動くか確認します。
Lチカコードを作成する
コンパイラは同じくCrosstool-NGで作ったRPiのベアメタル開発環境を使います。
ベアメタルアプリケーションの動作確認は単純なLEDチカチカプログラムを使って行います。
以下、ソースコードです。
#define GPFSEL1 0x3F200004 /* GPIO のピン設定をするためのレジスタ */ #define GPSET0 0x3F20001C /* GPIO を HIGH にするためのレジスタ */ #define GPCLR0 0x3F200028 /* GPIO を LOW にするためのレジスタ */ typedef unsigned char bool; #define TRUE 1 #define FALSE 0 #define WAIT_COUNT 3000000 /* * @fn wait_count だけビジーウェイトする */ void busy_wait(int wait_count); /* * @fn Lチカする */ int main(void) { // GPIO 出力に設定。 *(volatile unsigned int*)GPFSEL1 = (1 << (18)); // セットして待つ、クリアして待つ、を繰り返す。 while (1) *(volatile unsigned int*)GPSET0 = (1 << 16); busy_wait(WAIT_COUNT); *(volatile unsigned int*)GPCLR0 = (1 << 16); busy_wait(WAIT_COUNT); } return 0; } /* * @fn delay関数 */ void busy_wait(int wait_count) { volatile unsigned int i; for (i = 0; i < wait_count; i++); }
このプログラムではGPIO16(pin36)を制御してH/Lを繰り返します。
したがってpin36に抵抗を挟んでLEDを接続しておきます。
スタートアップコードを作成する
続いてスタートアップコードをアセンブラで書きます。
スタックポインタは、ARM Coreの実行開始アドレスが0x0008 0000で、spはそこより若いアドレスの領域を指定します。
mov sp, #0x80000 bl main
ビルドして動かしてみる
コードができたら以下のコマンドでビルドします。
今回はリンカスクリプトを作成しないのでldの-Tオプションを使用してtextアドレスを決めまています。
$ aarch64-rpi3-elf-as -o start.o start.S $ aarch64-rpi3-elf-gcc -c -o main.o main.c $ aarch64-rpi3-elf-ld -Ttext 0x80000 -o metal.elf start.o main.o $ aarch64-rpi3-elf-objcopy -O binary metal.elf metal.img
ビルドが成功したらmetal.imgをSDカードの/bootにコピーし、config.txtのkernelパラメータを以下のように変更します。
kernel=metal.img
あとは再びSDカードをRPiに入れて電源をONします。
LEDがチカチカすれば成功です。
ベアメタルアプリケーションをAMPで動かす
ここまでで、SMP動作するRaspberry Pi OSとベアメタルアプリケーションができました。 次はベアメタルアプリケーションをAMPで動作させるようにしていきます。
ベアメタル アプリケーションを改造する
まずはベアメタルアプリケーションを改造していきます。
start.Sを以下のようにしてスタックポインタの初期位置を変更します。
1GBあるメモリの内、upper512MBをベアメタルアプリケーションの領域としたので適当に0x30000000(768MB付近)としました。
mov sp, #0x30000000 bl main
変更できたらビルドします。
前回同様、リンカスクリプトは作っていないので-Ttextを使用してupper512MBの先頭アドレス0x20000000にプログラムを配置するようにしています。
$ aarch64-rpi3-elf-as -o start.o start.S $ aarch64-rpi3-elf-gcc -c -o main.o main.c $ aarch64-rpi3-elf-ld -Ttext 0x20000000 -o metal.elf start.o main.o $ aarch64-rpi3-elf-objcopy -O binary metal.elf metal.img
metal.imgができたらSDカードの/bootにコピーしておきます。
なおconfig.txtのkernelパラメータはu-bootが起動するようにしておきます。
これでベアメタルアプリーケーションの準備完了です。
ベアメタルアプリケーションをメモリに展開するプログラムを作成する
最初はRaspberry Pi OSからベアメタルアプリケーションをメモリに展開するプログラムを作成します。
このプログラムは0x20000000番地にベアメタルアプリケーションの実行ファイルを展開します。
ただ0x20000000番地はRaspberry Pi OSの管理対象外です。
なのでmmapを使用してファイルを展開するようにしています。
#include <stdio.h> #include <stdlib.h> #include <fcntl.h> #include <sys/mman.h> int main (int argc, char * argv []) { int fd_mem; void *load_address; unsigned long fileLen; FILE *file; printf ("Opening %s\n",argv[1]); file=fopen(argv[1],"rb"); fseek(file, 0, SEEK_END); fileLen=ftell(file); fseek(file, 0, SEEK_SET); printf ("File lenght %d\n",fileLen); printf ("Opening Mem %x\n",0x20000000); fd_mem = open("/dev/mem", O_RDWR); load_address = mmap(NULL, fileLen,PROT_READ|PROT_WRITE, MAP_SHARED, fd_mem, 0x20000000); fread(load_address, fileLen, 1, file); fclose(file); }
ソースが書けたらビルドして実行してみます。 以下のようになれば成功です。
$ gcc loadmetal.c -o loadmetal $ sudo ./loadmetal metal.img Opening metal.img File lenght 160 Opening Mem 20000000
特定のメモリアドレスを読み書きするプログラムを作成する
お次は特定のメモリアドレスを読み書きするためにdevmemを用意します。
ソースコードは以下を流用しました。
https://bootlin.com/pub/mirror/devmem2.c
以下のコマンドでソースをダウンロードしてビルドします。
$ wegt https://bootlin.com/pub/mirror/devmem2.c
$ gcc devmem2.c -o devmem
$ sudo ./loadmetal metal.img
Raspberry Pi OSからベアメタルアプリケーションをキックしてみる
ここまでできたら、ようやくRaspberry Pi OSからベアメタルアプリケーションをキックさせます。
Raspberry Pi OSからCore 3をkickするためにメールボックスを使用します。
Core3_MBOX3_SETレジスタ= 0x400000BCに0x20000000を書き込むと、
Core 3はその位置にロードした実行可能ファイルにジャンプするようです。
メールボックスの仕様は以下が参考になりました。
https://qiita.com/toshinaga/items/e65004cc207a46d17b7b
devmemを使用して0x400000BCに0x20000000を書き込みます。
これでRaspberry Pi OSからベアメタルアプリケーションがキックされ、ベアメタルアプリケーションがGPIOを制御し始めます。
以下のコマンドを実行した直後にLEDがチカチカすれば成功です!
$ sudo ./devmem 0x400000bc w 0x20000000 /dev/mem opened. Memory mapped at address 0x76f080000. Value at address 0x400000BC (0x76f080bc): 0xACA0000 Written 0x20000000; readback 0xACA0000
Raspberry Pi OSとベアメタルアプリケーションで共有メモリを試す
最後に共有メモリを使用して、Raspberry Pi OSとベアメタルアプリケーション間のIPCをテストしてみます。
再びベアメタル アプリケーションを改造する
まずはベアメタルアプリケーションを改造します。
#define GPFSEL1 0x3F200004 /* GPIO のピン設定をするためのレジスタ */ #define GPSET0 0x3F20001C /* GPIO を HIGH にするためのレジスタ */ #define GPCLR0 0x3F200028 /* GPIO を LOW にするためのレジスタ */ typedef unsigned char bool; #define TRUE 1 #define FALSE 0 #define WAIT_COUNT 3000000 volatile char delay = 0x54; volatile char count = 0x00; /* * @fn wait_count だけビジーウェイト */ void busy_wait(int wait_count); /* * @fn Lチカする */ int main(void) { // GPIO 出力に設定。 *(volatile unsigned int*)GPFSEL1 = (1 << (18)); // セットして待つ、クリアして待つ、を繰り返す。 while (1) { *(volatile unsigned int*)GPSET0 = (1 << 16); busy_wait(WAIT_COUNT); *(volatile unsigned int*)GPCLR0 = (1 << 16); busy_wait(WAIT_COUNT); if( count == 0xFF ) { count = 0x00; } else { count++; } } return 0; } /* * @fn delay関数 */ void busy_wait(int wait_count) { volatile unsigned int i; for (i = 0; i < delay * 10000; i++); }
このプログラムでは、delayとcountという変数を追加しました。
delay変数の値でLチカの速度が変化し、Lチカされる度にcount変数が加算されていきます。
ビルドが成功してバイナリができたら、以下のコマンドを実行して変数シンボルのアドレスを確認しておきます。
$nm metal.elf 20000000 t $a 20000008 t $a 2000009c t $a 20010119 b $d 20000088 t $d 20000114 t $d 20000118 t $d 2001011c B __bss_end__ 20010119 B __bss_start 20010119 B __bss_start__ 20010118 D __data_start 2001011c B __end__ 2001011c B _bss_end__ 20010119 D _edata__ 2001011c B _end 00080000 N _stack 2000009c T busy_wait 20010119 B count 20010118 D delay 20000008 T main
ここではdelay変数は0x20010118に配置されました。
共有メモリで通信できるか試してみる
バイナリができたらこれまでと同様にSDカードにコピーし、loadmetalを使用してメモリに展開しておきます。
そしてdevmemでdelay変数のアドレスの値を読み込んでみます。
プログラムで定義した値(delay = 0x54)になっていれば成功です。
$ sudo ./devmem 0x20010118 b /dev/mem opened. Memory mapped at address 0x76fb1000 Value at address 0x20010118 (0x76fb1118): 0x54
続いてdelay変数の値をdevmemを使用して書き換えてみます。
sudo ./devmem 0x20010118 b 0x30 sudo ./devmem 0x20010118 b 0x20 sudo ./devmem 0x20010118 b 0x10
0x30→0x20→0x10に下げていくと高速点滅し、
逆に0x10→0x20→0x30に上げていくと低速点滅すれば成功です。
これでRaspberry Pi OSからメモリを介してベアメタルアプリケーションが制御できたことがわかります。
まだ共有メモリのキャッシュやコヒーレントなどの問題が残っているかもですが、とりあえず思っていたAMPシステムが構築できました。
かなり長くなりましたが、ここまで読んでくださりありがとうございますm(_ _)m
なにかの参考になれば幸いです。