ROS2のリアルタイム性を測定していくにあたって、ハードウェアのセットアップや測定時の注意点をまとめました。
この記事と同様のセットアップ手順を踏むことで、リアルタイム性の高い環境の構築ができます。
セットアップ対象
今回は、Raspberry Pi 3B+ にRaspbeanをインストールし、リアルタイム向けの設定を行います。
セットアップする項目については、以下の資料を参考にしました。
参考
ROS 2 Design | Introduction to Real-time Systems
ROSCon JP 2019 | REAL-TI ME CONTROL IN REDHAWK AND ROS 2.0
Raspbean のインストール
今回はRT-Preemptパッチ済みカーネルの使用に合わせ、OSはRaspbeanを使用しました。
- Download Raspbean for Raspberry Pi より Raspbian Buster with desktop をダウンロード
- SDカードのフォーマット
$ sudo mkfs.vfat -I [device path] # ex. sudo mkfs.vfat /dev/sde
- イメージの書き込み
$ sudo dd if=[img path] of=[device path] # ex. sudo dd if=2020-02-05-raspbian-buster.img of=/dev/sde
- Raspberry Piに差し込み、Raspbeanの起動とカーネルを確認
$ # NTPの同期設定 $ timedatectl Local time: 水 2020-02-12 10:29:15 JST Universal time: 水 2020-02-12 01:29:15 UTC RTC time: n/a Time zone: Asia/Tokyo (JST, +0900) System clock synchronized: yes # yesになっていることを確認 NTP service: active RTC in local TZ: no $ sudo raspi-config SSHを有効化 CLI起動に変更 $ uname -a Linux raspberrypi 4.19.97-v7+ #1294 SMP Thu Jan 30 13:15:58 GMT 2020 armv7l GNU/Linux $ # デフォルトで入っているカーネルは 4.19.97-v7+
参考
Linux(Ubuntu)で、RaspberryPiのSDカードをインストールする方法
Raspberry Pi(Raspbian)のNTPサーバ設定 | 株式会社CONFRAGE 組込制御システム事業部
RT-Preemptパッチ済みカーネルのビルド
作業を簡単にするために、RT_RT-Preemptパッチがあてられたカーネルを使用します。
# host PC側
$ mkdir ~/rpi-kernel && cd ~/rpi-kernel
$ git clone https://github.com/raspberrypi/linux.git -b rpi-4.19.y-rt # RT_PREEMPT済みブランチ
$ git clone https://github.com/raspberrypi/tools.git --depth 1
$ cd linux
$ export ARCH=arm
$ which arm-linux-gnueabihf-cpp
/home/username/tools/arm-bcm2708/gcc-linaro-arm-linux-gnueabihf-raspbian-x64/bin/arm-linux-gnueabihf-cpp
# arm-linux-gnueabihf-***ツールまでの絶対パスを確認
$ export CROSS_COMPILE=/home/username/tools/arm-bcm2708/gcc-linaro-arm-linux-gnueabihf-raspbian-x64/bin/arm-linux-gnueabihf-
$ export KERNEL=kernel7
$ mkdir ~/rpi-kernel/boot
$ export INSTALL_MOD_PATH=~/rpi-kernel/boot
$ export INSTALL_DTBS_PATH=~/rpi-kernel/boot
$ cd linux
$ make bcm2709_defconfig # bcm2835_defconfigでは起動しなかった
HOSTCC scripts/basic/fixdep
HOSTCC scripts/kconfig/conf.o
YACC scripts/kconfig/zconf.tab.c
LEX scripts/kconfig/zconf.lex.c
HOSTCC scripts/kconfig/zconf.tab.o
HOSTLD scripts/kconfig/conf
#
# configuration written to .config
#
$ make menuconfig
- Select: Preemptive Kernel(Low-Latency Desktop)
General Setup>
Preemption Model>
Preemptive Kernel(Low-Latency Desktop)
- Enable CONFIG_PREEMPT_RT_FULL
General setup>
Timers subsystem>
High Resolution Timer Support
- Enable tickless
General setup>
Timers subsystem>
Timer tick handling>
Full dyntick system (tickless)
- Set CONFIG_HZ to 1000Hz>
Kernel Features>
Timer frequency = 1000 Hz
- Save to .config
% vim .config # debug類を全てyからnに変える。
$ grep -i debug .config | grep -v ^#
CONFIG_GENERIC_IRQ_DEBUGFS=n
CONFIG_SLUB_DEBUG=n
CONFIG_DEBUG_ALIGN_RODATA=n
CONFIG_BLK_DEBUG_FS=n
CONFIG_BT_DEBUGFS=n
CONFIG_WIMAX_DEBUG_LEVEL=8
CONFIG_B43LEGACY_DEBUG=n
CONFIG_RTLWIFI_DEBUG=n
CONFIG_WIMAX_I2400M_DEBUG_LEVEL=8
CONFIG_USB_SERIAL_DEBUG=n
CONFIG_OCFS2_DEBUG_MASKLOG=n
CONFIG_JFFS2_FS_DEBUG=0
CONFIG_CIFS_DEBUG=n
CONFIG_DEBUG_FS=n
CONFIG_DEBUG_KERNEL=n
CONFIG_HAVE_DEBUG_KMEMLEAK=n
CONFIG_ARCH_HAS_DEBUG_VIRTUAL=n
CONFIG_DEBUG_MEMORY_INIT=n
CONFIG_SCHED_DEBUG=n
CONFIG_DEBUG_PREEMPT=n
CONFIG_LOCK_DEBUGGING_SUPPORT=n
CONFIG_DEBUG_BUGVERBOSE=n
CONFIG_DEBUG_LL_INCLUDE="mach/debug-macro.S"
CONFIG_UNCOMPRESS_INCLUDE="debug/uncompress.h"
$ make -j 10 zImage modules dtbs
~~~~
OBJCOPY arch/arm/boot/zImage
Kernel: arch/arm/boot/zImage is ready
$ mkdir -p ~/rpi-kernel/boot/overlays
$ make modules_install
$ make dtbs_install
$ cp arch/arm/boot/zImage ../boot/
$ rsync -avz ../boot pi@[ip_address_for_raspberry_pi]:~
## raspberry pi側
$ sudo cp ~/boot/*.dtb /boot/
$ sudo cp ~/boot/overlays/*.dtb* /boot/overlays/
$ sudo cp ~/boot/overlays/README /boot/overlays/
$ sudo cp ~/boot/zImage /boot/rt-kernel.img
$ sudo mv ~/boot/lib/modules/* /lib/modules
$ sudo vim /boot/config.txt
#/boot/config.txtに以下を追加
kernel=rt-kernel.img
$ sudo vim /boot/cmdline.txt
# 末尾に以下を追加
# dwc_otg.fiq_fsm_enable=0 dwc_otg.fiq_enable=0 dwc_otg.nak_holdoff=0 dwg_otg.speed=1 rcu_nocbs=0 nohz_full=1-3 isolcpus=1-3
# fiq割り込み無効化、カーネルスレッドをcpu0のみで動作、cpu1~3をticklessに変更
# 例:
# console=serial0,115200 console=tty1 root=PARTUUID=266982c5-02 rootfstype=ext4 elevator=deadline fsck.repair=yes rootwait quiet splash plymouth.ignore-serial-consoles dwc_otg.fiq_fsm_enable=0 dwc_otg.fiq_enable=0 dwc_otg.nak_holdoff=0 dwg_otg.speed=1 rcu_nocbs=0 nohz_full=1-3 isolcpus=1-3
$ sudo reboot
$ uname -a
Linux raspberrypi 4.19.71-rt24-v7+ #5 SMP PREEMPT RT Thu Feb 13 14:16:03 JST 2020 armv7l GNU/Linux
# -rtが付いていることを確認
$ sudo modprobe config
$ zcat /proc/config.gz > .config
$ grep -i preempt .config
CONFIG_PREEMPT=y # yを確認
CONFIG_PREEMPT_RT_BASE=y # yを確認
~~
CONFIG_PREEMPT_RT_FULL=y # yを確認
~~
CONFIG_NO_HZ_FULL=y # yを確認
$ dmesg | grep -i fiq
[ 0.000000] Kernel command line: coherent_pool=1M 8250.nr_uarts=0 bcm2708_fb.fbwidth=1824 bcm2708_fb.fbheight=984 bcm2708_fb.fbswap=1 smsc95xx.macaddr=B8:27:EB:0F:81:5B vc_mem.mem_base=0x3ec00000 vc_mem.mem_size=0x40000000 console=ttyS0,115200 console=tty1 root=PARTUUID=266982c5-02 rootfstype=ext4 elevator=deadline fsck.repair=yes rootwait quiet splash plymouth.ignore-serial-consoles dwc_otg.fiq_fsm_enable=0 dwc_otg.fiq_enable=0 dwc_otg.nak_holdoff=0 dwc_otg.speed=1
[ 0.957023] dwc_otg: FIQ disabled# disabledを確認
[ 0.957041] dwc_otg: FIQ split-transaction FSM disabled
$ mpstat -P ALL # cpu0のみが使用されていることを確認
Linux 4.19.55-rt24-v7+ (raspberrypi) 03/13/2020 _armv7l_ (4 CPU)
10:12:38 AM CPU %usr %nice %sys %iowait %irq %soft %steal %guest %gnice %idle
10:12:38 AM all 4.79 0.00 8.82 0.32 0.00 0.05 0.00 0.00 0.00 86.03
10:12:38 AM 0 21.28 0.01 39.21 1.40 0.00 0.21 0.00 0.00 0.00 37.88
10:12:38 AM 1 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 100.00
10:12:38 AM 2 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 100.00
10:12:38 AM 3 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 100.00
$ cat /proc/interrupts # 複数回行い、cpu0の割り込み処理回数のみが増加していることを確認。
CPU0 CPU1 CPU2 CPU3
17: 82 0 0 0 ARMCTRL-level 1 Edge 3f00b880.mailbox
18: 36 0 0 0 ARMCTRL-level 2 Edge VCHIQ doorbell
33: 856926 0 0 0 ARMCTRL-level 41 Edge dwc_otg, dwc_otg_pcd, dwc_otg_hcd:usb1
40: 0 0 0 0 ARMCTRL-level 48 Edge bcm2708_fb DMA
42: 341 0 0 0 ARMCTRL-level 50 Edge DMA IRQ
44: 4237 0 0 0 ARMCTRL-level 52 Edge DMA IRQ
80: 4197 0 0 0 ARMCTRL-level 88 Edge mmc0
81: 7425 0 0 0 ARMCTRL-level 89 Edge uart-pl011
86: 3703 0 0 0 ARMCTRL-level 94 Edge mmc1
161: 0 0 0 0 bcm2836-timer 0 Edge arch_timer
162: 110685 157 142 127 bcm2836-timer 1 Edge arch_timer
165: 0 0 0 0 bcm2836-pmu 9 Edge arm-pmu
166: 0 0 0 0 lan78xx-irqs 17 Edge usb-001:004:01
IPI0: 0 0 0 0 CPU wakeup interrupts
IPI1: 0 0 0 0 Timer broadcast interrupts
IPI2: 15 26 26 24 Rescheduling interrupts
IPI3: 0 5 5 5 Function call interrupts
IPI4: 0 0 0 0 CPU stop interrupts
IPI5: 40 5 5 5 IRQ work interrupts
IPI6: 0 0 0 0 completion interrupts
Err: 0
$ cat /proc/softirqs # 複数回行い、cpu0の割り込み処理回数のみが増加していることを確認。
CPU0 CPU1 CPU2 CPU3
HI: 3120 0 0 0
TIMER: 111577 156 141 126
NET_TX: 9 0 0 0
NET_RX: 462 0 0 0
BLOCK: 0 0 0 0
IRQ_POLL: 0 0 0 0
TASKLET: 16745 0 0 0
SCHED: 0 0 0 0
HRTIMER: 1771 0 0 0
RCU: 0 0 0 0
詳細:RT kernel: Unusual dwc_otg high 8k/s interrupt rate using quite some CPU? – Navio / Edge / Flight stack – Community Forum
参考
realtime:documentation:howto:applications:preemptrt_setup [Wiki]
Kernel building – Raspberry Pi Documentation
Raspberry Pi 3とリアルタイムカーネル(1)[RT Preempt導入編] – アールテクニカ地下ガレージ
New RT (Real Time) kernel branch – Raspberry Pi Forums
Raspberry Pi 4B: Real-Time System using Preempt-RT (kernel 4.19.y) – LeMaRiva|tech
where is mkknlimg? · Issue #3249 · raspberrypi/linux · GitHub
It’s been deprecated for a long time, and the current firmware makes no use of the trailer it used to add, so I removed it as part of the continuous drive to converge with upstream.
ハードウェア的な設定
Raspberry Pi は電源が4.75Vを下回った場合や、CPU温度が60度を超えた場合にCPUクロックを落とします。
今回は上記現象の回避のため、発熱を抑えるためにCPUクロックを1000MHzに固定し、GPIOから直接電源供給しました。
CPUクロックの固定と動作の確認方法は以下の通りです。
- 以降SSH経由で全ての作業を行う。
$ sudo apt install cpufrequtils $ sudo vim /etc/default/cpufrequtils # ファイルを作成 ENABLE="true" GOVERNOR="userspace" MAX_SPEED=1000000 MIN_SPEED=1000000 $ sudo vim /boot/config.txt arm_freq=1000 # 最大1000MHzに固定 temp_soft_limit=70 # soft limitを60度から70度に変更 $ sudo reboot
- 動作確認
$ echo '1' | sudo tee /sys/devices/system/cpu/cpu*/cpufreq/stats/reset # cpu-statsをリセット $ stress -c 4 & # CPUに100%負荷 pi@raspberrypi:~ $ vcgencmd measure_temp temp=53.7C # 70度以内を確認。 $ vcgencmd get_throttled throttled=0x0 # 0x0を確認。 過去に熱や低電圧によるクロックダウンが無かったことを示している。 $ cpufreq-info analyzing CPU 0: cpufreq stats: 600 MHz:0.00%, 1000 MHz:100.00% # 全てのコアが1000MHzで動作していた事を確認 ~~~
参考
ubuntu18.04 – cpufreqからCPU統計を削除する方法は? – 初心者向けチュートリアル
Overclocking options in config.txt – Raspberry Pi Documentation
Thermal testing Raspberry Pi 4 – Raspberry Pi
動作確認
セットアップした環境の性能を確認するために、linuxの計測用ツールであるcyclictestにて設定前後のリアルタイム性を比較します。
$ taskset -c 0 stress -c 1 & # 各コア固定でcpu負荷とio負荷をかけておく
$ taskset -c 1 stress -c 1 &
$ taskset -c 2 stress -c 1 &
$ taskset -c 3 stress -c 1 &
$ taskset -c 0 stress -i 1 &
$ taskset -c 1 stress -i 1 &
$ taskset -c 2 stress -i 1 &
$ taskset -c 3 stress -i 1 &
$ ps -eo pid,comm,psr | grep kworker | awk '{print "chrt -r -p 98 "$1}' | sh
# kworkerの優先度を98に上げる。
$ taskset -c 0 cyclictest -p 98 -m -t1 -n -D 3h -i 200 -a 1 --policy=rr -h500 -q > result.txt
# cpu1にて計測用のスレッドをラウンドロビンの優先度98にて3時間動作させる。
$ ps -em -o pid,tid,policy,pri,ni,rtprio,comm,psr | grep cyclic -A 2
# スレッドごとの優先度とスケジューリングポリシーを確認
$ grep -v -e "^#" -e "^$" result.txt | tr " " "," > histogram.csv # ヒストグラム部分のみ抽出
nanosleep起床までにかけたレイテンシのヒストグラムの結果は次のようになりました。
上図は縦軸が線形、下図は縦軸が対数になっています。
pureカーネルでは210us〜最悪300usのウェイトが観測された事を示しており、設定後では210us〜最悪240us程度のウェイトが観測されたことを示しています。
pureカーネルの方が最頻値で比較すると高速で優れていると言えます。
一方で、設定後の方が最悪値が小さく、リアルタイム性が向上していることが確認できます。
今回、なるべくリアルタイムな測定が出来るような環境を目指しましたが、
- • Raspberry Pi 3B+は Gigabit Ethernet over USB 2.0で、USB 2.0 がボトルネックになっている。
(参考:Getting Gigabit Networking on a Raspberry Pi 2, 3 and B+)) - MMU を利用しているため、 minor page fault が発生する。
- RT-Preempt では高いトラフィック時に無視できない遅延が発生する。
という問題があり、リアルタイム性を向上させたと言えど、いくらかの制限があります。
しばらくは今回セットアップしたRasperry Pi 3B+ で測定をしてきましたが、今後使われるデバイスとしては Raspberry Pi 4B の方が有望であるため、
また改めてRaspberry Pi 4Bでも同様のセットアップを行う予定です。
私達の検証対象はROS2ですので、ROS2のビルドも行っています。
ビルドは可視化関連のパッケージを除外し、atomic関連のエラー回避用にcmake-argsを設定してビルドできました。
$ touch src/ros-visualization/COLCON_IGNORE src/ros2/rviz/COLCON_IGNORE
# 不要なパッケージをビルド対象から外す
$ colcon build --symlink-install --packages-skip-build-finished --continue-on-error --cmake-args "-DCMAKE_SHARED_LINKER_FLAGS='-latomic'" "-DCMAKE_EXE_LINKER_FLAGS='-latomic'"