x86_64のPCでJetson用のROS/CUDAビルド環境を構築する

だいぶ前回の投稿から時間が経ってしまいましたが、今回ROS Adovent Calenderに投稿するために主にロボット開発者の抱えている課題を解決できそうなネタを投稿したいと思います。

# Jetson（aarch64)で実行可能なバイナリをx86_64で作成する

ロボット開発者であれば、Jetsonをロボットに搭載し、省電力でかつ高性能な機械学習と、ロボットの制御等を実現しようとされている方も多いと思います。
とはいえ、開発において、プログラムを変更するたびに実機でビルドしてコンパイルエラーがないかを確認していると、JetsonとはいえCPUはそこまで高性能ではないので、時間がかかってストレスが溜まっておられる方も多いと思います。

そこで今回は、ロボットでそのまま使用できるバイナリをx86_64のPCで作成し、実機で実行する際は、作成したバイナリをDeployすることで対応するという方法について説明したいと思います。

## どうやってx86_64上でaarch64のバイナリを作成するか

x86_64上で他のアーキテクチャのバイナリを作成すると聞くと多くの人はクロスコンパイルを想像されることと思います。

クロスコンパイルの場合は、基本的にx86_64_aarch64-linux-gnuのようなクロスコンパイラを使用すればビルドできますが、機械学習系やロボット用のバイナリを作成しようとすると、リンクするライブラリがないため基本的にHOST環境だけでクロスコンパイルすることはできず、実機のrootfsが必要となります。NVIDIAはJetson向けにはCUDAのクロスコンパイルの具体的な手法は記載されていないようです。DRIVEシリーズにおいては、クロスコンパイル環境をきちんと用意しているようですが、License絡みで色々面倒ですし、企業向けのみとなっています。

今回は、QEMUを用いて、ビルドのパフォーマンスは低下するものの、簡単にx86_64のPCでJetsonで動作するバイナリの作成方法の説明を行いたいと思います。最近のIntel Core i9 12900KなどのCPUの力で殴ればきっと高速にビルド出来ることでしょう。

前回Apple M1 MacでJetson用のバイナリを超高速にビルドする方法を書きましたが、今回はそれのx86_64での方法となります。

ちなみに頑張ってクロスコンパイル環境を構築したい人は以下のサイトに色々情報があります。（今度まとめます）

https://docs.nvidia.com/jetson/l4t-multimedia/cross_platform_support.html
https://docs.nvidia.com/jetson/l4t/index.html#page/Tegra%20Linux%20Driver%20Package%20Development%20Guide/xavier_toolchain.html

Linux Kernelのビルドの場合は上記を参考に

```console
$ export CROSS_COMPILE=$HOME/l4t-gcc/gcc-linaro-7.3.1-2018.05-x86_64_aarch64-linux-gnu/bin/aarch64-linux-gnu-
```

こうすることで、クロスコンパイラを指定して進めることになります。（詳細は割愛します）

### Jetsonのimgファイルのマウントと中身のコピー
それでは、ここからSDカードとQEMUを用いた手法を説明していきたいと思います。
まず、NVIDIAのHPからJetson NX用のSDカードイメージをダウンロードしておきます。（Nanoでも出来ると思います）
手っ取り早く、ダブルクリックをすれば/media/<"user name">/xxxxにマウントされます。

続いて、JetPackのSDカードイメージの中身をコピーしてきますが、このあとファイルの所有者等で問題が発生するのでオプションに必ず-aを入れておかなければいけません。

```console
sudo cp -raf /media/user/xxx/* /home/user/任意のフォルダ
```

これでファイルの準備は完了です。

### コンテナ環境の準備

基本的にDockerなどのコンテナを用いて作業を行う必要はないのですが、さまざまな操作をする中で、間違って環境を破壊してしまう可能性もあるので、Dockerコンテナ内で作業することを強く推奨します。
今回は、専用のDockerFile等を準備しましたので、ぜひご利用ください。

https://github.com/tatsuyai713/jetson_cross_docker

コンテナのセットアップは以下のコマンドで実行できます。

```console
./launch_container.sh setup
```

このコンテナでは、自動でHOST側のUSER IDを使用して、あたかもHOST側と変わらないように操作することができます。また、HOSTのHomeフォルダも~/host_homeでマウントされているため、基本的にコンテナ内にファイルを移動させて実行する必要はありません。

ただし、HOSTで操作しているかContainer内で操作しているか分からなくなるかもしれないので、その際は、BashのTerminalに出てくるHOST名を確認してください。Container内では、HOST名-Dockerという名前になっています。

### コンテナ内での準備

コンテナのセットアップが完了したあと、作成したコンテナに入ります。

```console
./launch_countainer.sh
```
コンテナから出るときは、exitで抜けることができます。またコンテナをcommitする際は、

```console
./launch_countainer.sh commit
```

でコミットすることができます。


ここからは、以下のGithubのリポジトリにある手順をベースにしています。こちらはNVIDIA DRIVE用のROSをビルドするための手法が書かれていますが、基本的にはJetsonでも同じようなことができます。

https://github.com/NVIDIA/dw-ros



まずは、QEMU関連のパッケージをインストールしておきます。

```console
sudo apt update
sudo apt install qemu-user-static
```

続いて、SDカードの中身をコピーしたフォルダに入り以下の作業を継続していきます。
SDカードはHOSTでコピー作業をしていると思いますので、フォルダは/home/USER名/host_home/以下になっていることに注意してください。

```console
cd /home/USER/host_home/SDカードの中身
sudo cp /usr/bin/qemu-aarch64-static usr/bin
sudo cp -b /etc/resolv.conf etc
sudo mount -o bind /dev dev
sudo mount -o bind /proc proc
sudo mount -o bind /sys sys
```

これで基本的な準備は完了です。


### JetPack内でのコンパイル

続いてchrootを実行し、Jetpack内のrootになります。

```console
sudo LC_ALL=C chroot .
```

これでJetPack内のrootになりました。chrootでは基本的にUSER権限での操作はできませんので、このままrootで作業していく必要があります。

まずは、JetPackの中でaptが正しく動くように以下を変更します。

```console
vi /etc/apt/sources.list.d/nvidia-l4t-apt-source.list 
```

ここで＜SOC＞となっている箇所を

```console
deb https://repo.download.nvidia.com/jetson/t194 r32.6 main
```
と言うような感じでSOCに合わせて変更します。


https://qiita.com/tatsuyai713/items/aca7b67ac0092a58ccd3

ここからは以前に紹介したM1 MacでJetPack用のコンパイルを行うのと全く同じように動作させることができます。

resolv.confをHOST側から持ってきているのでaptのコマンドも実行できます。
ですので、以下のサイトの通り、ROSのインストールも無事に実行可能です。

http://wiki.ros.org/melodic/Installation/Ubuntu

dw_rosのGithubには、この後このSDカードの中身を参照しながらクロスコンパイルを行う方法が書かれています。その方法を用いればQEMUを用いるよりも高速にコンパイル作業を行うことができますが、見ていただいてわかるようにCMakeFile.txtにクロスコンパイルの設定を書かないといけないといった、かなりめんどくさいことになります。ですので、ROSのパッケージ作成に今後クロスコンパイルを多用していく方はCMakeにクロスコンパイルの設定を書いておくと良いでしょう。

私はクロスコンパイルをそこまで多用しないということもあるので、このままQEMU上でビルド作業を行なっていますが、私が試したところでは、OpenCVをCUDAを有効にしてビルドすることもできましたので、速度は犠牲になるもののそれなりに便利に使うことができると思います。

この環境で、catkin_makeなどでバイナリファイルを作成して、それをTarballなどにかためて、Jetsonの実機にDeployするようにすれば、自動で実機にDeployすることもできるでしょう。

### 環境の後片付け

最後にchroot環境からexitコマンドで出た後に以下の作業をしておきましょう。

```console
sudo umount sys proc dev 
sudo rm usr/bin/qemu-aarch64-static
sudo mv etc/resolv.conf~ etc/resolv.conf
```

### まとめ

ということで、今回はROSとあまり関係ないですが、Jetson向けのビルドをx86_64上でQEMUを用いて実行する方法を説明しました。次回は、QEMUではなく、クロスコンパイルの方法について説明したいと思います。