リアルタイム性能の測定に向けたRaspberryPi3のセットアップ

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を使用しました。

  1. Download Raspbean for Raspberry Pi より Raspbian Buster with desktop をダウンロード
  2. SDカードのフォーマット
    $ sudo mkfs.vfat -I [device path] 
    # ex. sudo mkfs.vfat /dev/sde
    
  3. イメージの書き込み
    $ sudo dd if=[img path] of=[device path] 
    # ex. sudo dd if=2020-02-05-raspbian-buster.img of=/dev/sde
    
  4. 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
※ 名前にdwc_otgと付いたプロセスが30%程度CPUを使うのはFIQを無効にしたのが原因。
詳細:RT kernel: Unusual dwc_otg high 8k/s interrupt rate using quite some CPU? – Navio / Edge / Flight stack – Community Forum

参考

ハードウェア的な設定

Raspberry Pi は電源が4.75Vを下回った場合や、CPU温度が60度を超えた場合にCPUクロックを落とします。
今回は上記現象の回避のため、発熱を抑えるためにCPUクロックを1000MHzに固定し、GPIOから直接電源供給しました。

CPUクロックの固定と動作の確認方法は以下の通りです。

  1. 以降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
    
  2. 動作確認
    $ 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で動作していた事を確認
      ~~~
    

参考

動作確認

セットアップした環境の性能を確認するために、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起床までにかけたレイテンシのヒストグラムの結果は次のようになりました。
上図は縦軸が線形、下図は縦軸が対数になっています。

pic1

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'"