LeavaTailの日記

LeavaTailの日記

Linuxエンジニアを目指した技術者の備忘録

組込みLinuxディストリビューションを構築する(Yocto編)

関連記事

概要

Yocto (Zeus)でARM64用のLinuxディストリビューション一式 (Linuxカーネル、U-Boot、ルートファイルシステム、ツールチェイン) を生成する。
また、QEMU (virtボード)でU-Bootをロードし、そこからLinxuカーネルの起動とルートファイルシステムのマウントまでの手順を確認した。

はじめに

前回の記事で、QEMUx86_64アーキテクチャ上でARM用にビルドしたLinuxカーネルを起動させることに成功した。

leavatail.hatenablog.com

その時はLinuxシステムの構築にBuildRootを使用したが、今回はYoctoを使用してLinuxシステムを構築する。

Yoctoについて

Yocto Projectは、開発者がLinuxディストリビューションを構築する仕組みを提供するプロジェクトである。 Yoctoの特徴としては、以下のようなものがあげられる。

Yoctoは複数のコンポーネントから構成されている。下図はコンポーネントについて簡略に表したものである。

Yoctoの全体像イメージ

  • poky : リファレンス・ビルド・システム
    独自のLinuxディストリビューションを構築するためのシステム。 (厳密には違うが) pokyはYocto専用のLinuxディストリビューションであり、ここでカーネルイメージやrootfsを生成する。 記事によっては、yocto=pokyのことを指していることもある。

  • bitbake: ビルドツール
    Yocto(poky)上で利用される組込みLinuxシステム向けのビルドツール。 Makeのようなもので、依存関係を解決したうえでイメージ(カーネルイメージやrootfsなど)を生成する。
    具体的にbitbakeでは、「レシピの解析」「ソースコードのダウンロード」「パッチ適用」「ビルド」を一括で実施してくれる。

  • レシピ: ソフトウェアのビルド定義
    ソフトウェアのビルドやインストール方法が書かれたファイル。

  • レイヤー: メタ情報の集合
    レシピなどを含めた情報を集めた層。レイヤーは重ねることで機能を追加することができ、アーキテクチャによる依存を最小限にすることができる。
    レイヤーやレシピの詳細な説明については、参考資料を参照。

  • 構成情報: ターゲットマシンの構成定義
    CPUアーキテクチャなどのターゲットマシンごとの定義。

環境構成

前回の記事で作成した環境を利用する。

実行環境

仮想マシンの構築にはVagrantVirtualBox、BoxイメージにはUbuntu /bionic64を利用する。

今回使用するホスト環境は下記の通り。

テスト環境 詳細
CPU intel core i7-9700
メモリ DDR4-2666 32G
ストレージ M.2 SSD(500GB)
Virtualbox v6.0.18

実行環境の準備

Yoctoの要件として空きディスク容量が50Gであることが求められる。 しかし、Vagrantのデフォルト仮想マシンのディスク容量は10Gであるので、Vagrantの定義ファイルを修正する必要がある。

今回は、vagrant-disksizeを使用して、ディスク容量を拡張する。 またyoctoではイメージ生成時間が長くなる傾向があるので、CPU数とメモリ容量も拡張しておく。 Vagrantfileの修正箇所は下記の通りである。

$ git diff
diff --git a/Vagrantfile b/Vagrantfile
index 1c4ee1e..bb5337f 100644
--- a/Vagrantfile
+++ b/Vagrantfile
@@ -45,17 +45,20 @@ Vagrant.configure("2") do |config|
   # argument is a set of non-required options.
   # config.vm.synced_folder "../data", "/vagrant_data"
 
+  config.ssh.forward_x11 = true
+
   # Provider-specific configuration so you can fine-tune various
   # backing providers for Vagrant. These expose provider-specific options.
   # Example for VirtualBox:
   #
-  # config.vm.provider "virtualbox" do |vb|
-  #   # Display the VirtualBox GUI when booting the machine
+  config.vm.provider "virtualbox" do |vb|
+    # Display the VirtualBox GUI when booting the machine
   #   vb.gui = true
-  #
-  #   # Customize the amount of memory on the VM:
-  #   vb.memory = "1024"
-  # end
+
+    # Customize the amount of memory on the VM:
+    vb.cpus = 8
+    vb.memory = "16384"
+  end
+
+  config.disksize.size = '200GB'
   #
   # View the documentation for the provider you are using for more
   # information on available options.

Yoctoを使用するにあたってX11転送は必須ではないが、デスクトップ環境イメージを利用する際に不便なので設定している。

システム構築のためのセットアップ

  1. 必要なパッケージをインストールする。

     vagrant@ubuntu-bionic:~$ sudo apt-get install gawk wget git-core diffstat unzip texinfo gcc-multilib \
                              build-essential chrpath socat cpio python python3 python3-pip python3-pexpect \
                              xz-utils debianutils iputils-ping python3-git python3-jinja2 libegl1-mesa libsdl1.2-dev \
                              pylint3 xterm
    
  2. Pokyリポジトリをクローンする。(執筆時点の最新バージョンyocto-3.0にチェックアウトする)

     vagrant@ubuntu-bionic:~$ git clone git://git.yoctoproject.org/poky
     vagrant@ubuntu-bionic:~$ cd poky/
     vagrant@ubuntu-bionic:~/poky$ git fetch --tag
     vagrant@ubuntu-bionic:~/poky$ git tag
     vagrant@ubuntu-bionic:~/poky$ git checkout tags/yocto-3.0 -b yocto-3.0
    
  3. ビルド環境の初期化する。

     vagrant@ubuntu-bionic:~/poky$ source oe-init-build-env
    
     You had no conf/local.conf file. This configuration file has therefore been
     created for you with some default values. You may wish to edit it to, for
     example, select a different MACHINE (target hardware). See conf/local.conf
     for more information as common configuration options are commented.
    
     You had no conf/bblayers.conf file. This configuration file has therefore been
     created for you with some default values. To add additional metadata layers
     into your configuration please add entries to conf/bblayers.conf.
    
     The Yocto Project has extensive documentation about OE including a reference
     manual which can be found at:
         http://yoctoproject.org/documentation
    
     For more information about OpenEmbedded see their website:
         http://www.openembedded.org/
    
    
     ### Shell environment set up for builds. ###
    
     You can now run 'bitbake <target>'
    
     Common targets are:
         core-image-minimal
         core-image-sato
         meta-toolchain
         meta-ide-support
    
     You can also run generated qemu images with a command like 'runqemu qemux86'
    
     Other commonly useful commands are:
      - 'devtool' and 'recipetool' handle common recipe tasks
      - 'bitbake-layers' handles common layer tasks
      - 'oe-pkgdata-util' handles common target package tasks
    
  4. 構成ファイルlocal.confを修正する。

diff --git a/build/conf/local.conf b/build/conf/local.conf
index 2e0bb41e64..1941917154 100644
--- a/build/conf/local.conf
+++ b/build/conf/local.conf
@@ -18,7 +18,7 @@
 # of emulated machines available which can boot and run in the QEMU emulator:
 #
 #MACHINE ?= "qemuarm"
-#MACHINE ?= "qemuarm64"
+MACHINE ?= "qemuarm64"
 #MACHINE ?= "qemumips"
 #MACHINE ?= "qemumips64"
 #MACHINE ?= "qemuppc"
@@ -265,3 +265,6 @@ PACKAGECONFIG_append_pn-qemu-system-native = " sdl"
 # track the version of this file when it was generated. This can safely be ignored if
 # this doesn't mean anything to you.
 CONF_VERSION = "1"
+
+BB_NUMBER_THREADS = '8'
+PARALLEL_MAKE = '-j 8'

修正内容は次の通りとなっている。

  • MACHINE ?=でターゲット対象アーキテクチャをarm64に変更する。
  • BB_NUMBER_THREADS:で並列処理するレシピ数を8に変更する。
  • PARALLEL_MAKE:コンパイル時に使用するコア数を8に変更する。

Yoctoによるビルド実行

今回は、最小限のシステムデスクトップ環境の2種類のLinuxディストリビューションを構築している。

最小システムのビルドと起動

  1. 最小限のシステムをビルドする。

     vagrant@ubuntu-bionic:~/poky/build$ bitbake core-image-minimal
    
  2. ビルドしたイメージは tmp/deploy/images に格納される。

     vagrant@ubuntu-bionic:~/poky/build$ ls -l tmp/deploy/images/qemuarm64/
     total 423572
     lrwxrwxrwx 2 vagrant vagrant        73 Jun 12 05:56 Image -> Image--5.15.32+git0+63e25b5717_387a676543-r0-qemuarm64-20220612044554.bin
     -rw-r--r-- 2 vagrant vagrant  20445696 Jun 12 05:56 Image--5.15.32+git0+63e25b5717_387a676543-r0-qemuarm64-20220612044554.bin
     lrwxrwxrwx 2 vagrant vagrant        73 Jun 12 05:56 Image-qemuarm64.bin -> Image--5.15.32+git0+63e25b5717_387a676543-r0-qemuarm64-20220612044554.bin
     -rw-r--r-- 2 vagrant vagrant      1779 Jun 12 06:06 core-image-minimal-qemuarm64-20220612044554.qemuboot.conf
     -rw-r--r-- 2 vagrant vagrant  11512832 Jun 12 06:57 core-image-minimal-qemuarm64-20220612044554.rootfs.ext4
     -rw-r--r-- 2 vagrant vagrant       721 Jun 12 06:06 core-image-minimal-qemuarm64-20220612044554.rootfs.manifest
     -rw-r--r-- 2 vagrant vagrant   2753520 Jun 12 06:06 core-image-minimal-qemuarm64-20220612044554.rootfs.tar.bz2
     -rw-r--r-- 2 vagrant vagrant    242743 Jun 12 06:06 core-image-minimal-qemuarm64-20220612044554.testdata.json
     lrwxrwxrwx 2 vagrant vagrant        55 Jun 12 06:06 core-image-minimal-qemuarm64.ext4 -> core-image-minimal-qemuarm64-20220612044554.rootfs.ext4
     lrwxrwxrwx 2 vagrant vagrant        59 Jun 12 06:06 core-image-minimal-qemuarm64.manifest -> core-image-minimal-qemuarm64-20220612044554.rootfs.manifest
     lrwxrwxrwx 2 vagrant vagrant        57 Jun 12 06:06 core-image-minimal-qemuarm64.qemuboot.conf -> core-image-minimal-qemuarm64-20220612044554.qemuboot.conf
     lrwxrwxrwx 2 vagrant vagrant        58 Jun 12 06:06 core-image-minimal-qemuarm64.tar.bz2 -> core-image-minimal-qemuarm64-20220612044554.rootfs.tar.bz2
     lrwxrwxrwx 2 vagrant vagrant        57 Jun 12 06:06 core-image-minimal-qemuarm64.testdata.json -> core-image-minimal-qemuarm64-20220612044554.testd
     -rw-r--r-- 2 vagrant vagrant   2004411 Jun 12 05:56 modules--5.15.32+git0+63e25b5717_387a676543-r0-qemuarm64-20220612044554.tgz
     lrwxrwxrwx 2 vagrant vagrant        75 Jun 12 05:56 modules-qemuarm64.tgz -> modules--5.15.32+git0+63e25b5717_387a676543-r0-qemuarm64-20220612044554.tgz
    
  3. QEMUで生成したイメージをエミュレートする。(nographicを指定することで、現在のターミナルからシリアル接続することができる)

     vagrant@ubuntu-bionic:~/poky/build$ runqemu qemuarm64 nographic
     runqemu - INFO - Running MACHINE=qemuarm64 bitbake -e ...
     runqemu - INFO - Continuing with the following parameters:
     KERNEL: [/home/vagrant/poky/build/tmp/deploy/images/qemuarm64/Image--5.15.32+git0+63e25b5717_387a676543-r0-qemuarm64-20220612044554.bin]
     MACHINE: [qemuarm64]
     FSTYPE: [ext4]
     ROOTFS: [/home/vagrant/poky/build/tmp/deploy/images/qemuarm64/core-image-sato-qemuarm64-20220612065747.rootfs.ext4]
     CONFFILE: [/home/vagrant/poky/build/tmp/deploy/images/qemuarm64/core-image-sato-qemuarm64-20220612065747.qemuboot.conf]
    
     runqemu - INFO - Setting up tap interface under sudo
     runqemu - INFO - Network configuration: ip=192.168.7.2::192.168.7.1:255.255.255.0::eth0:off:8.8.8.8
     runqemu - INFO - Running /home/vagrant/poky/build/tmp/work/x86_64-linux/qemu-helper-native/1.0-r1/recipe-sysroot-native/usr/bin/qemu-system-aarch64 -device virtio-net-device,netdev=net0,mac=52:54:00:12:34:02 -netdev         tap,id=net0,ifname=tap0,script=no,downscript=no -object rng-random,filename=/dev/urandom,id=rng0 -device virtio-rng-pci,rng=rng0 -drive id=disk0,file=/home/vagrant/poky/build/tmp/deploy/images/qemuarm64/        core-image-sato-qemuarm64-20220612065747.rootfs.ext4,if=none,format=raw -device virtio-blk-device,drive=disk0 -device qemu-xhci -device usb-tablet -device usb-kbd  -machine virt -cpu cortex-a57 -smp 4 -m 512 -serial mon:stdio     -serial     null -nographic -device virtio-gpu-pci -kernel /home/vagrant/poky/build/tmp/deploy/images/qemuarm64/Image--5.15.32+git0+63e25b5717_387a676543-r0-qemuarm64-20220612044554.bin -append 'root=/dev/vda rw  mem=512M     ip=192.168.7.2::192.168.7.1:255.255.255.0::eth0:off:8.8.8.8 console=ttyAMA0 console=hvc0  '
    

デスクトップ環境を構築する

  1. デスクトップ環境をビルドする

     vagrant@ubuntu-bionic:~/poky/build$ bitbake core-image-sato
    
  2. ビルドしたイメージは tmp/deploy/images に格納される。

     vagrant@ubuntu-bionic:~/poky/build$ ls -l tmp/deploy/images/qemuarm64/
     total 423580
     lrwxrwxrwx 2 vagrant vagrant        73 Jun 12 05:56 Image -> Image--5.15.32+git0+63e25b5717_387a676543-r0-qemuarm64-20220612044554.bin
     -rw-r--r-- 2 vagrant vagrant  20445696 Jun 12 05:56 Image--5.15.32+git0+63e25b5717_387a676543-r0-qemuarm64-20220612044554.bin
     lrwxrwxrwx 2 vagrant vagrant        73 Jun 12 05:56 Image-qemuarm64.bin -> Image--5.15.32+git0+63e25b5717_387a676543-r0-qemuarm64-20220612044554.bin
     -rw-r--r-- 2 vagrant vagrant      1767 Jun 12 08:46 core-image-sato-qemuarm64-20220612065747.qemuboot.conf
     -rw-r--r-- 2 vagrant vagrant 522982400 Jun 25 09:26 core-image-sato-qemuarm64-20220612065747.rootfs.ext4
     -rw-r--r-- 2 vagrant vagrant     28808 Jun 12 08:46 core-image-sato-qemuarm64-20220612065747.rootfs.manifest
     -rw-r--r-- 2 vagrant vagrant  96465741 Jun 12 08:46 core-image-sato-qemuarm64-20220612065747.rootfs.tar.bz2
     -rw-r--r-- 2 vagrant vagrant    243264 Jun 12 08:46 core-image-sato-qemuarm64-20220612065747.testdata.json
     lrwxrwxrwx 2 vagrant vagrant        52 Jun 12 08:46 core-image-sato-qemuarm64.ext4 -> core-image-sato-qemuarm64-20220612065747.rootfs.ext4
     lrwxrwxrwx 2 vagrant vagrant        56 Jun 12 08:46 core-image-sato-qemuarm64.manifest -> core-image-sato-qemuarm64-20220612065747.rootfs.manifest
     lrwxrwxrwx 2 vagrant vagrant        54 Jun 12 08:46 core-image-sato-qemuarm64.qemuboot.conf -> core-image-sato-qemuarm64-20220612065747.qemuboot.conf
     lrwxrwxrwx 2 vagrant vagrant        55 Jun 12 08:46 core-image-sato-qemuarm64.tar.bz2 -> core-image-sato-qemuarm64-20220612065747.rootfs.tar.bz2
     lrwxrwxrwx 2 vagrant vagrant        54 Jun 12 08:46 core-image-sato-qemuarm64.testdata.json -> core-image-sato-qemuarm64-20220612065747.testdata.json
     -rw-r--r-- 2 vagrant vagrant   2004411 Jun 12 05:56 modules--5.15.32+git0+63e25b5717_387a676543-r0-qemuarm64-20220612044554.tgz
     lrwxrwxrwx 2 vagrant vagrant        75 Jun 12 05:56 modules-qemuarm64.tgz -> modules--5.15.32+git0+63e25b5717_387a676543-r0-qemuarm64-20220612044554.tgz        
    
  3. QEMUで生成したイメージをエミュレートする。

     vagrant@ubuntu-bionic:~/poky/build$ runqemu qemuarm64
    

これにより、新規のウインドウが立ち上がる。

Yoctoの起動画面

デスクトップ環境ではのメインメニューから"Terminal"を選択することで、コンソールにログインできる。

アプリケーション選択画面

ツールチェインの生成

  1. ツールチェインをビルドする。

     vagrant@ubuntu-bionic:~/poky/build$ bitbake meta-toolchain
    
  2. ビルドしたツールチェインは tmp/deploy/sdk に格納される。

     vagrant@ubuntu-bionic:~/poky/build $ ls -l tmp/deploy/sdk/
     total 135232
     -rw-r--r-- 2 vagrant vagrant     11909 Jun 25 10:28 poky-glibc-x86_64-meta-toolchain-cortexa57-qemuarm64-toolchain-4.0.host.manifest
     -rwxr-xr-x 2 vagrant vagrant 138233871 Jun 25 10:30 poky-glibc-x86_64-meta-toolchain-cortexa57-qemuarm64-toolchain-4.0.sh
     -rw-r--r-- 2 vagrant vagrant      1730 Jun 25 10:28 poky-glibc-x86_64-meta-toolchain-cortexa57-qemuarm64-toolchain-4.0.target.manifest
     -rw-r--r-- 2 vagrant vagrant    225233 Jun 25 10:28 poky-glibc-x86_64-meta-toolchain-cortexa57-qemuarm64-toolchain-4.0.testdata.json
    
  3. ツールチェインをインストールする。

     vagrant@ubuntu-bionic:~/poky/build$ ./tmp/deploy/sdk/poky-glibc-x86_64-meta-toolchain-cortexa57-qemuarm64-toolchain-4.0.sh 
     Poky (Yocto Project Reference Distro) SDK installer version 4.0
     ===============================================================
     Enter target directory for SDK (default: /opt/poky/4.0): 
     You are about to install the SDK to "/opt/poky/4.0". Proceed [Y/n]? Y
     Extracting SDK...............................................done
     Setting it up...done
     SDK has been successfully set up and is ready to be used.
     Each time you wish to use the SDK in a new shell session, you need to source the environment setup script e.g.
      $ . /opt/poky/4.0/environment-setup-cortexa57-poky-linux        
    

ツールチェインの利用

Autotoolsを利用するプロジェクトを生成したツールチェインでビルドする。

github.com

  1. 環境をセットアップする (現在のプロセス shell 単位に反映される)

     vagrant@ubuntu-bionic:/vagrant/Autotools-tutorial$  . /opt/poky/4.0/environment-setup-cortexa57-poky-linux 
    
  2. 設定された環境変数configureを実施する

     vagrant@ubuntu-bionic:/vagrant/Autotools-tutorial$ ./configure ${CONFIGURE_FLAGS}
    
  3. 設定された環境設定でプロジェクトのビルドを実施する

     vagrant@ubuntu-bionic:/vagrant/Autotools-tutorial$ make
    

おわりに

Linuxシステムを構築するツールYoctoを使用して、組込みLinuxディストリビューションを構築した。 YoctoはBuildRootなどと比較して、設定が複雑な印象があるが、純正性の高いディストリビューションを作成することができそうだ。またレイヤーによる概念により、再利用性が高くなっている。

変更履歴

  • 2020/02/23: 記事公開
  • 2022/06/08: 章構成を変更
  • 2022/06/25: ビルドによる成果物一覧を追加

参考

Yocto Projectの公式ガイド

Yoctoについて解説している記事

Yoctoのレシピ/レイヤーについて解説

BitBakeの解説

組込みLinuxディストリビューションを構築する(BuildRoot編)

関連記事

概要

BuildRootでARM64用のLinuxディストリビューション一式 (Linuxカーネル、U-Boot、ルートファイルシステム、ツールチェイン) を生成する。
また、QEMU (virtボード)でU-Bootをロードし、そこからLinxuカーネルの起動とルートファイルシステムのマウントまでの手順を確認した。

はじめに

前回の記事で、QEMUx86_64アーキテクチャ上でARM64用にビルドしたLinuxカーネルを起動させることに成功した。

leavatail.hatenablog.com

しかし、前回作成した環境はLinuxカーネルを起動してシェルから最低限の操作ができるものだった。

ここから、Linuxシステムとして利用できるようにするためには、uClibcなどを利用してツールチェーンを生成したり、必要に応じてブートローダをビルドする必要がある。

上記の手順を一から手動で構築するには手間がかかり、依存関係の乱れによるケアレスミスなど発生する恐れがある。

BuildRootは、上記のような問題を解決することができるLinuxシステムを構築するツールである。   BuildRootを使用することで、下記のものを構築することができる。

今回はBuildRootを使用して、組込みLinuxディストリビューションを構築する。

環境構成

前回の記事で作成した環境を利用する。

実行環境

仮想マシンの構築にはVagrantVirtualBox、BoxイメージにはUbuntu /bionic64を利用する。

TargetBoardにはARM仮想ボードの「virt-2.11」を使用する。 このTargetBoardに、ルートファイルシステムLinuxカールの入ったディスクを接続し、フラッシュメモリブートローダをロードする。

今回使用するTargetBoardの抽象図を下記に示す。

QEMUでエミュレートするボードの抽象図

依存パッケージ

公式サイト(The Buildroot user manual)より、下記のコマンドを必要とする。

  • sed
  • make (version 3.81 or any later)
  • binutils
  • build-essential (only for Debian based systems)
  • gcc (version 4.8 or any later)
  • g++ (version 4.8 or any later)
  • bash
  • patch
  • gzip
  • bzip2
  • perl (version 5.8.7 or any later)
  • tar
  • cpio
  • unzip
  • rsync
  • file (must be in /usr/bin/file)
  • bc
  • wget

システム構築の手順

  1. BuildRootに必要としているパッケージをインストールする。(そのほかのコマンドは、Vagrantのイメージでもインストール済み)

     vagrant@ubuntu-bionic:~$ libncurses-dev unzip
    
  2. BuildRootを入手する。(執筆時点の安定版である2019.11.1を使用する)

     vagrant@ubuntu-bionic:~$ git clone git://git.buildroot.net/buildroot
     vagrant@ubuntu-bionic:~/buildroot$ cd buildroot
     vagrant@ubuntu-bionic:~/buildroot$ git checkout -b 2019.11.1 2019.11.1
    
  3. QEMU用のARM64アーキテクチャqemu_aarch64_virt_defconfigのconfigファイルを生成する。1

     vagrant@ubuntu-bionic:~/buildroot$ make qemu_aarch64_virt_defconfig
    
  4. ビルドの設定を変更する。

     vagrant@ubuntu-bionic:~/buildroot$ make menuconfig
    

このとき、ブートローダU-Bootのビルドを有効化するために次の設定をする。

  • Bootloadersを選択する。
  • U-Bootを選択、Board defconfigに(qemu_arm64)を入力する。

その後、生成したconfigをもとにビルドする。(一般権限で実行すること)

vagrant@ubuntu-bionic:~/buildroot$ make

Linuxの起動

  1. カーネルを格納するイメージ2を生成する。

     vagrant@ubuntu-bionic:~$ dd if=/dev/zero of=boot.img bs=512 count=65536
     vagrant@ubuntu-bionic:~$ mkfs.vfat boot.img 
     mkfs.fat 4.1 (2017-01-24)
    
  2. 作成したイメージにカーネルを格納する。

     vagrant@ubuntu-bionic:~$ sudo mount -o loop boot.img /mnt/
     vagrant@ubuntu-bionic:~$ sudo cp buildroot/output/images/Image /mnt/
     vagrant@ubuntu-bionic:~/buildroot$ sudo umount /mnt 
    
  3. 作成したカーネルを格納したイメージとルートファイルシステムイメージを接続、ファームウェアにU-Bootのイメージをロードし、QEMUを起動させる。

     vagrant@ubuntu-bionic:~$ qemu-system-aarch64 \
       -M virt \
       -cpu cortex-a53 \
       -bios ~/buildroot/output/images/u-boot.bin \
       -drive file=~/boot.img,if=none,format=raw,id=hd0 \
       -device virtio-blk-device,drive=hd0 \
       -drive file=~/buildroot/output/images/rootfs.ext4,if=none,format=raw,id=hd1 \
       -device virtio-blk-device,drive=hd1 \
       -nographic
    
  4. U-Bootのデフォルト設定では今回の環境を動作させることができないので、設定したうえでカーネルを起動させる。

     => load virtio 1 ${kernel_addr_r} /Image
     => setenv bootargs root=/dev/vda rdinit=/bin/sh
     => booti ${kernel_addr_r} - ${fdt_addr}
    

これらの設定は、それぞれ次のようなことをしている。

Linuxの起動画面

BuildRootではユーザ名root、パスワードなしでログインすることができる。

ツールチェインの取得

生成したツールチェインはoutput/host/以下に格納されている。

vagrant@ubuntu-bionic:~$ ls -l buildroot/output/host/
total 48
drwxr-xr-x  6 vagrant vagrant  4096 Feb  4 15:33 aarch64-buildroot-linux-uclibc
drwxr-xr-x  2 vagrant vagrant 12288 Feb  9 16:03 bin
drwxr-xr-x  4 vagrant vagrant  4096 Feb  9 15:52 etc
drwxr-xr-x 23 vagrant vagrant  4096 Feb  9 15:52 include
drwxr-xr-x  8 vagrant vagrant  4096 Feb  9 15:52 lib
lrwxrwxrwx  1 vagrant vagrant     3 Feb  4 15:10 lib64 -> lib
drwxr-xr-x  3 vagrant vagrant  4096 Feb  4 15:44 libexec
drwxr-xr-x  3 vagrant vagrant  4096 Feb  9 15:49 man
drwxr-xr-x  2 vagrant vagrant  4096 Feb  9 15:51 sbin
drwxr-xr-x 21 vagrant vagrant  4096 Feb  4 15:44 share
lrwxrwxrwx  1 vagrant vagrant     1 Feb  4 15:10 usr -> .
drwxr-xr-x  3 vagrant vagrant  4096 Feb  4 15:44 var

おわりに

Linuxシステムを構築するツールBuildRootを使用して、組込みLinuxディストリビューションを構築した。 BuildRootでは、BusyBoxやuClibcなどを組み込んで構築してくれるので、依存関係の意識せずとも構築することができる。 しかし、BuildRootに頼りすぎてしまうと内部構造がブラックボックス化してしまうのであまりよくないと考えられる。 legal-infomakeターゲットを指定することでライセンスの一覧を取得することはできるが、これだけでは不十分だろう。

次回は、そのほかのLinux構築ツールyoctoなどを使用して同様の環境を構築してみたいと思う。

変更履歴

  • 2020/2/11: 記事公開
  • 2020/12/18: 投稿画像のアップデート
  • 2022/06/07: デザイン修正

参考


  1. 指定可能なターゲットはlist-defconfigsで確認することができる。

  2. 現状はカーネルのみ格納できれば良いので32Mで作成する。

QEMUでARM64用Linuxカーネルを起動する

関連記事

概要

x86_64の開発PCでARM64用にLinuxカーネルをビルド、busyboxで簡易initramfsを用意する。
また、QEMU (virtボード)でこれらのバイナリを動かした。

はじめに

Linuxカーネルは様々なアーキテクチャに対応している。
その中でもARMアーキテクチャでは、スマートフォンやゲーム機器などモバイル機器に広くから採用されている。

しかし、アーキテクチャ毎に命令セットが異なるため、ARM用にビルドされたバイナリを別のアーキテクチャで実行することはできない。

そこで、プロセッサエミュレータでもあるQEMUを用いてARM環境を構築し、ARM用にビルドされたLinuxカーネルを動かす方法を解説する。
また本記事では、以下の動作をする環境を目指す。

カーネル起動ワークフローとメモリマップイメージ図

環境構成

ホスト環境x86_64アーキテクチャに構築する。

実行環境

本記事は、下記の環境とソフトウェアバージョンに基づいて説明する。

環境 パラメータ
ホスト環境 x86_64
ホストOS Ubuntu 20.04
QEMU QEMU emulator version 4.2.1
ターゲットボード virt
linux 5.4.83
Busybox 1_32_stable
Docker version 19.03.13
Docker image ubuntu:20.04

ロスコンパイル環境の構築

  1. Docker imageからコンテナを作成する

     leava@ubuntu-bionic:~$ docker run --rm --name=kbuild -h "kbuild" -v /srv:/work -it ubuntu:20.04 /bin/bash 
    
  2. ARM64用のgccコンパイラをインストールする

     root@kbuild:/# apt update
     root@kbuild:/# apt install gcc-aarch64-linux-gnu 
    
  3. コンテナ内部でLinuxカーネルをビルドするために必要なパッケージをインストールする

    root@kbuild:/# apt install build-essential bc bison flex libncurses-dev libelf-dev libssl-dev git wget
    
  4. LinuxカーネルをARM64用にセットアップする

     root@kbuild:/# wget https://cdn.kernel.org/pub/linux/kernel/v5.x/linux-5.4.83.tar.xz 
     root@kbuild:/# tar xf linux-5.4.83.tar.xz -C /work/
     root@kbuild:/# cd work/linux-5.4.83
     root@kbuild:/work/linux-5.4.83# export ARCH="arm64"
     root@kbuild:/work/linux-5.4.83# export CROSS_COMPILE="aarch64-linux-gnu-"
    
  5. カーネルをビルドする。

     root@kbuild:/work/linux-5.4.83# make defconfig
     root@kbuild:/work/linux-5.4.83# make -j `getconf _NPROCESSORS_ONLN` Image dtbs modules
    

上記のコマンドによって、/srvディレクトリに Kernel ImageとDevice Tree Bolb(DTB)、Loadable Moduleが生成される。

QEMUカーネルを起動させる

  1. ARM64用QEMUをインストールする

     leava@ubuntu-bionic:~$ sudo apt install qemu-system-aarch64
    
  2. 作成したカーネルQEMUで実行する

     leava@ubuntu-bionic:~$ qemu-system-aarch64 \
         -M virt \
         -cpu cortex-a53 \
         -kernel /srv/linux-5.4.83/arch/arm64/boot/Image \
         -nographic \
         -append "console=ttyAMA0"
    

利用したオプションは下記の通り。
また、デバイスツリーを指定していないにも関わらず起動できているのは、QEMUのvirtがデバイスツリーを自動生成しているからである。

オプション 概要
-M 利用するターゲットボードを指定する。今回は汎用ボードのvirtを使用する。
-cpu CPUプロセッサを指定する。今回はARM64系のcortex-a53を使用する
-kernel カーネルイメージを指定する。先ほどビルドしたイメージを使用する。
-nographic GUIを立ち上げない
-append カーネルパラメータ。 ARMアーキテクチャのシリアルポートデバイスttyAMA0を使用する。

上記のコマンドを実行すると、kernel Panicしてしまう。

    [    0.440663] VFS: Cannot open root device "(null)" or unknown-block(0,0): error -6
    [    0.440874] Please append a correct "root=" boot option; here are the available partitions:
    [    0.441240] Kernel panic - not syncing: VFS: Unable to mount root fs on unknown-block(0,0)
    [    0.441534] CPU: 0 PID: 1 Comm: swapper/0 Not tainted 5.4.83 #1
    [    0.441648] Hardware name: linux,dummy-virt (DT)
    [    0.441849] Call trace:
    [    0.441927]  dump_backtrace+0x0/0x140
    [    0.442063]  show_stack+0x14/0x20
    [    0.442143]  dump_stack+0xb4/0x114
    [    0.442212]  panic+0x158/0x324
    [    0.442275]  mount_block_root+0x1d0/0x284
    [    0.442349]  mount_root+0x124/0x158
    [    0.442421]  prepare_namespace+0x12c/0x18c
    [    0.442489]  kernel_init_freeable+0x210/0x23c
    [    0.442553]  kernel_init+0x10/0x100
    [    0.442619]  ret_from_fork+0x10/0x1c
    [    0.442977] Kernel Offset: disabled
    [    0.443166] CPU features: 0x0002,24002004
    [    0.443266] Memory Limit: none
    [    0.443628] ---[ end Kernel panic - not syncing: VFS: Unable to mount root fs on unknown-block(0,0) ]---

initramfsを用意する

上記のメッセージを確認すると、「rootファイルシステムがない」からPanicしたことがわかるので、C-a xQEMUを終了する。

そこで、BusyBoxを利用して最低限起動できるrootファイルシステムを作成する。
BusyBoxは、UNIX系ユーティリティツール単一の実行ファイルにまとめたパッケージで、組込みLinuxでも利用されている。

  1. BusyBoxを取得する。

     root@kbuild:/# cd work
     root@kbuild:/work#  git clone git://git.busybox.net/busybox
     root@kbuild:/work# cd busybox
     root@kbuild:/work/busybox# git checkout remotes/origin/1_32_stable
    
  2. ビルド用の設定を修正する。最低限の動作を目指しているので、デフォルトからCONFIG_STATICを有効にするのみでよい。(CONFIG_STATICSetting->Build static binary (no shared libs)を有効にすることで設定される)

     root@kbuild:/work/busybox# make defconfig
     root@kbuild:/work/busybox# make menuconfig
    
  3. BusyBoxをビルドし、イメージを_installに生成する。

     root@kbuild:/work/busybox# make
     root@kbuild:/work/busybox# make install
    
  4. initramfsとして最低限必要なファイルやディレクトリを作成する。

     root@kbuild:/work/busybox# cd _install
     root@kbuild:/work/busybox/_install# mkdir proc
     root@kbuild:/work/busybox/_install# mkdir sys
     root@kbuild:/work/busybox/_install# mkdir dev
     root@kbuild:/work/busybox/_install# sudo mknod dev/null c 1 3
     root@kbuild:/work/busybox/_install# cat <<EOF > init
     #!/bin/sh
     mount -t proc none /proc
     mount -t sysfs none /sys
     /sbin/mdev -s
     exec  /bin/sh
     EOF
     root@kbuild:/work/busybox/_install# chmod +x init
    
  5. initramfsを生成する。

     root@kbuild:/work/busybox/_install# find . | cpio -o --format=newc > ../rootfs.img
    
  6. 作成したイメージを利用して、再度QEMUを実行する。

     leava@ubuntu-bionic:~$ qemu-system-aarch64 \
         -M virt \
         -cpu cortex-a53 \
         -kernel /srv/linux-5.4.83/arch/arm64/boot/Image \
         -initrd /srv/busybox/rootfs.img \
         -nographic \
         -append "console=ttyAMA0"
    
オプション 概要
-initrd 初期 RAM ディスク。BusyBoxで作成したイメージを使用する。

上記のコマンドを実行すると、シェルが立ち上がる。

    [    0.425197] sdhci: Copyright(c) Pierre Ossman
    [    0.425800] Synopsys Designware Multimedia Card Interface Driver
    [    0.427183] sdhci-pltfm: SDHCI platform and OF driver helper
    [    0.429410] ledtrig-cpu: registered to indicate activity on CPUs
    [    0.431969] usbcore: registered new interface driver usbhid
    [    0.432146] usbhid: USB HID core driver
    [    0.440648] NET: Registered protocol family 17
    [    0.441712] 9pnet: Installing 9P2000 support
    [    0.442051] Key type dns_resolver registered
    [    0.443071] registered taskstats version 1
    [    0.443167] Loading compiled-in X.509 certificates
    [    0.449869] input: gpio-keys as /devices/platform/gpio-keys/input/input0
    [    0.451992] rtc-pl031 9010000.pl031: setting system clock to 2020-12-13T15:31:16 UTC (1607873476)
    [    0.455420] ALSA device list:
    [    0.455514]   No soundcards found.
    [    0.457913] uart-pl011 9000000.pl011: no DMA platform data
    [    0.648255] Freeing unused kernel memory: 4992K
    [    0.648750] Run /init as init process
    /bin/sh: can't access tty; job control turned off
    / # 

おわりに

実際にARMアーキテクチャを所持していなくても、QEMUでARM用Linuxカーネルを起動させることができた。今回の記事では紹介できなかったが、gdbデバッグが容易にできたりとデバッグ時にも大変有効であるので積極的に利用していきたい。

今回はBusyBoxのデフォルト設定でrootファイルシステムも生成したが、こちらは今後の課題となってくる。

変更履歴

  • 2020/01/26: 記事公開
  • 2020/12/11: ブログタイトルを "ARM64用" に訂正
  • 2020/12/14: 実行環境をUbuntu18.04からUbuntu20.04に更新
  • 2022/06/07: デザイン修正

参考

Raspberry Pi 3 Model Bとシリアル通信で接続する

概要

FTDI USBシリアル変換ケーブルとRaspberry Pi 3 Model Bを接続し、screenコマンドでコンソールが表示されることが確認できた。

はじめに

Raspberry Piを使って何か開発をしようとしたときに、ネットワーク経由でしかリモート接続ができない状態は危険である。 例えば、ネットワークの設定を誤ってRaspberry Piにリモート接続できなくなることが考えられる。

そこで、Raspberry Piとシリアル通信することでネットワークが使用できなくなった場合にも接続できるようにする。 Raspberry Pi 3 でシリアル通信するには、USB経由で通信する方法とGPIO経由で通信する方法がある。 ここでは、GPIO経由でシリアル通信する方法を記述する。

Raspberry Pi 3のシリアル通信の記事はこの記事は下記のリンクを参考にさせてもらった。

karaage.hatenadiary.jp

scrapbox.io

必要なもの

接続方法

1. Raspberry Pi側でシリアル通信の接続を有効にする

pi@raspberrypi:~ $ sudo raspi-config

"5 Interfacing Option"を選択する。

"P6 Serial"を選択する。

シリアルでシェルにログインするか聞かれるので"はい"を選択する。


2. FTDI USBシリアル変換ケーブルを接続する。

FTDI USBシリアル変換ケーブルの黒/橙/黄にブレッドボード・ジャンパーワイヤを接続する。

Raspberry Piとケーブルの配線

ブレッドボード・ジャンパーワイヤの黒をRaspberry PiのGround、橙をGPIO15 (RXD)、黄をGPIO14 (TXD) に接続する。

FTDI USBシリアル変換ケーブルの順番(黒/橙/黄)と、Raspberry Piへの接続先の順番 (黒/黄/橙)が異なるので注意。


3. 変換ケーブルのUSB側を操作端末 (今回はUbuntu 18.04を使用する) に接続する。

正しく認識されている場合、デバイスファイルが生成される。

$ ls -l /dev/ttyUSB*
crw-rw---- 1 root dialout 188, 0 12月 30 14:38 /dev/ttyUSB0

このデバイスに対して、シリアルコンソールに対応したターミナルエミュレータ (screen, minicom, etc.) を起動する。

 $ sudo screen /dev/ttyUSB0 115200

Raspbian GNU/Linux 9 raspberrypi ttyS0
raspberrypi login: 

おわりに

FTDI USBシリアル変換ケーブルを用いて、Raspberry Pi 3とGPIO経由でシリアル通信することができた。

ネットワークが切断されてもRaspberry Piと接続できるので、恐れずに開発できるようになった。

変更履歴

  • 2019/12/30: 記事公開

参考

*1:1袋に10本入っているので一つで十分だが、異なる3色のケーブルを買うことをお勧めする

Travis CIでdotfilesのテストを自動化する

概要

自作のdotfilesが一定の品質であることを保証するために、4つのテスト項目を定義し、テストの再設計・再実装した。

また、上記のテストとTravis CIを連携させることにより、テストの自動化を図った。

はじめに

皆様は設定ファイルをどのように管理しているだろうか。 外部記憶装置に保存する。ブログにアップする。いろいろな管理方法があるだろう。

私はdotfilesの一つのリポジトリで管理している。実際に私が使用しているdotfilesは下記の通りとなっている。

github.com

dotfilesで管理することで、新しい開発環境でもmake installのコマンド一つで自分の環境に設定ファイルを展開できる。

dotfilesについて興味のある方は有識者のサイトを参考にしてほしい。

一方で、dotfilesは常に使用しているものではなく、新しい開発環境を用意するとき (半年に一回程度くらい) などにしか使用されない。 そういったタイミングで用意していたdotfilesが動作しなかったら目も当てられない。

そこで、dotfilesが意図した通りに動作することをテストするためにCIを導入することにした。 今回は、CIツールとしてTravis CIを利用する。

目標とするシステム構成

dotfilesの構成

CIの導入に入る前に、私のdotfilesの現状構成について紹介する。

dotfilesの全体像

私はdotfilesで以下の6つの設定ファイルを管理している。

利用者がmake installコマンドを実行することで、これらの設定ファイルがローカルの開発環境に展開されるようになっている。 またmake install は、先駆者様に倣ってmake deploymake initを実行するコマンドとなっている。

make deployは、dotfiles内の設定ファイルをローカル開発環境に展開する(リンクを張る)作業をする。 make initは、開発環境の初期化処理をする。 自分のdotfilesでは初期化処理で、tmuxとvimプラグインをダウンロードする。

テスト環境

本来であれば複数の環境でテストすべきであるが、「dotfilesはあくまで自分用のリポジトリであること」「自分がUbuntuの最新LTSしか使用していないこと」から上記の一つのみ実施する。

テスト項目

これらの環境を考慮したうえで、dotfilesで満たすべき項目を考える。

  1. 設定ファイルのデプロイに成功している
  2. 設定ファイルが正しい場所に展開されている
  3. vimプラグインのインストールが成功している
  4. tmuxプラグインのインストールが成功している

設定ファイルのデプロイに成功している

単純かつ明解であるが、重要なテスト項目の一つである。 確認方法は、make installコマンドを実行したときのリターンコードが0であるかとした。

設定ファイルが正しい場所に展開されている

「正しい場所」とは、開発者の意図した場所のことを指す。

このテスト項目は、dotfilesの性質とし優先して満たすべきと考えられる。 確認方法は、展開した設定ファイルが存在しているか設定ファイルのリンク先が正しいか、この2つの手順で実施する。

設定ファイルの存在は、シェルスクリプトの「-e」演算子で簡単に確認することができる。 「-h」演算子シンボリックリンクであるか)の場合のis_link関数については後述。

# $1:  source file
# $2:  destination file
function is_exist() {
    DOTFILE=`basename $2`
    if [[ ! -e $2 ]]; then
        echo "  × \"$2\" is failed"
        echo "     Not found \"${DOTFILE}\"...NG"
        exit 1
    elif [[ -h $2 ]]; then
        is_link $1 $2
    elif [[ -e $2 ]]; then
        echo "\"$2\" is failed...OK"
        echo "     deploying \"${DOTFILE}\"\. but, file is unsupported...UNKHOWN"
        exit 2
    else
        echo "  ERROR: script was broken."
        exit -1
    fi
}

リンク先が正しい場所を指しているかについては、「readlink」コマンドで得られたパス名を比較することで確認できる。

# $1:  source file
# $2:  destination file
function is_link() {
    LINKPATH=`readlink -f $2`
    DOTFILE=`basename $1`
        if [ $1 = ${LINKPATH} ]; then
        echo "\"${DOTFILE}\" is succeed"
    else
        echo "  × \"${DOTFILE}\" is failed"
        echo "     deploying dot file...OK"
        echo "     invalid paths ($2 -> ${LINKPATH})...NG"
    fi
}

上記のスクリプトを実行する「make test」を用意したので、結果が0か非0かどうかをチェックする。

vimプラグインのインストールが成功している

このテスト項目は、展開した開発環境でVimが正しく使用できるか確認するものである。 そのために、インストール時のリターンコードと、プラグインが使用できること、確認するの二つを実施する。

私のdotfilesでは、vim起動時にプラグインをインストールしていなければプラグインをインストールするようにしている。 そこで、下記のスクリプト(一部) を実行して、vimの起動とリターンコードを確認する。

またプラグインのインストール時に、続けるにはENTERを押すかコマンドを入力してくださいが表示されるので、yesコマンドでごまかしている。

# install success?
yes | timeout -sKILL 60 vim +:q > /dev/null 2>&1
if [ $? != 0 ]; then
        echo "Failed install dein, Please install again."
        exit 1
fi

インストールしたプラグインが実行できるかの確認は、vimプラグインを実行するフェイズとエラーメッセージを確認するフェイズで実施する。

Vimプラグイン実行するフェイズでは、プラグインが実行できるかどうか判定する変数○○○_enabledを参照する。 ただし一部のプラグインは提供されていないので、適当のコマンドを実行することにした)

vim -V0easymotion.log +"echo g:EasyMotion_keys" +:q
vim -V0webdevicons.log +"echo g:webdevicons_enable" +:q
vim -V0ale.log +"echo ale_enabled" +:q
vim -V0gitgutter.log +"echo gitgutter_enabled" +:q
vim -V0lightline.log +"echo lightline" +:q

エラーメッセージを確認するフェイズでは、前工程で得られたログファイルからエラーメッセージが存在するか判定する。 VimのエラーメッセージはEと任意の数字で構成されるので、正規表現でパターンマッチさせる。

# $1: logfile name
# $2: return code
function check_plugin() {
    LOGFILE=$1
    ERRCODE=$2

    FAILED=0

    while read LINE
 do
        if [[ $LINE =~ :E[0-9]*: ]]; then
            echo $LINE
            FAILED=1
        fi
    done < $1

    if [ $FAILED -ne 0 ]; then
        ret=`expr $ret + $2`
    fi
}

tmuxプラグインのインストールが成功している

このテスト項目は、展開した開発環境でtmuxが正しく使用できるか確認するものである。 確認方法は、インストールされたtmuxプラグインが正しいかどうか確認することを実施する。

そのために、ローカル環境に展開されたプラグインを取得するフェイズと、設定ファイルに記述したプラグイン名と取得するフェイズで実施する。

ローカル環境に展開されたプラグインを取得するフェイズでは、プラグインマネージャがtmuxプラグインを展開する先の~/.tmux/pluginディレクトリエントリから取得する。

# installed tmux plugin
for LINE in `ls -l ~/.tmux/plugins/ | awk '$1 ~ /d/ {print $9 }'`
do
        acutual=(${actual[@]} ${LINE})
done

設定ファイルに記述したプラグイン名と取得するフェイズでは、設定ファイルからプラグイン記述部から取得する。 プラグイン記述部は、set -g @plugin 'tmux-plugins/でで始まるので、正規表現でパターンマッチさせる。

COMMAND="set -g @plugin 'tmux-plugins/"

# expect to install tmux plugin
while read LINE
do
        if [[ $LINE =~ ${COMMAND}(.*)\' ]]; then
                expect=(${BASH_REMATCH[1]} ${expect[@]})
        fi
done < ~/.tmux.conf

テスト項目と実施概要について下記にまとめる。

テスト項目 実施概要 参考
設定ファイルのデプロイに成功している make install の返り値が0である make install
設定ファイルが正しい場所に展開されている 「-e」演算子とreadlinkコマンドで場所を確認する make test
vimプラグインのインストールが成功している Vimプラグインのコマンドを実行してエラーメッセージがでないこと check_vim_plugin
tmuxプラグインのインストールが成功していること インストールされたTmuxプラグインが正しいかどうか check_tmux_plugin

CIサービスとの連携

Travis CIにアクセスして、リポジトリの連携を有効化する。 Travis CIの使い方については、他の方がわかりやすくまとめてあるのでそちらを参照。

qiita.com

作成した.travis.ymlは下記の通りである。

language: bash

os: linux
dist: bionic
sudo: required

before_install:
    - sudo apt-get update
    - sudo apt-get install git
    - sudo apt-get install make
    - sudo apt-get install vim
    - sudo apt-get install zsh

script:
    - "make clean"
    - "make install"
    - "make test"
    - "tests/check_tmux_plugin"
    - "tests/check_vim_plugin"

上記の設定ファイルをdotfilesのトップディレクトリに格納し、GitHubに何かしらをプッシュすると自動でテストが走る。

Travis CI の結果

おわりに

下記のテスト項目をTravis CIと連携させることで、作成したdotfilesが壊れていたということを減らせるだろう。

  1. 設定ファイルのデプロイに成功している
  2. 設定ファイルが正しい場所に展開されている
  3. vimプラグインのインストールが成功している
  4. tmuxプラグインのインストールが成功している

しかし、テスト項目もテスト環境もまだまだ甘く、見つけることのできないバグなどもある。 そのためにも、テスト項目自体も定期的にメンテナンスしていく必要があるだろう。

変更履歴

  • 2019/12/22: 記事公開
  • 2022/06/06: デザイン修正

参考

Vimの導入プラグインを見直す

概要

deinプラグインマネージャで21個Vimプラグインをインストールしている環境に対して、見直しを実施した。

Vimの基本機能で代用できるものを削除していった結果、12個までプラグインを削減することができた。

はじめに

著者は4年以上Vimを使っているが、設定ファイルがブラックボックス化してしまっている。
そこで、使っていないプラグインなど忘れられているプラグインなどあると思うので、2019年のうちに設定ファイルを整理してみたいと思う。 今回は、導入するプラグインを必要最低限に絞って素のVimを楽しむことにする。

本記事は、vimrcのプラグイン周りの整理したときの記録を残す。

見直し前の環境

1年前からVimを含めて設定ファイルはdotfilesにまとめている。導入自体は簡単で、ワンコマンドでデプロイできるようにしている。

github.com

整理前のvimは下記の状態になっていて、ステータスラインやアンドゥツリーなどIDEっぽくしている。

整理する前のvim

ここで、使用しているVimプラグインを抜粋してみる。 deinを利用してプラグインを管理している。

プラグイン 概要
Shougo/dein.vim Vimプラグインマネージャ
Shougo/vimproc.vim Vimを非同期処理
tomasr/molokai カラースキーム
Shougo/neosnippet-snippets スニペット定義ファイル
Shougo/context_filetype.vim 動的にfiletypeを判定する
nathanaelkane/vim-indent-guides インデントの可視化
ujihisa/neco-look 英単語を補完
sjl/gundo.vim アンドゥツリーの表示
Lokaltog/vim-easymotion キータッチで目的行に移動する
Shougo/vimshell Vimからシェルを起動する
scrooloose/nerdtree ディレクトリツリーを表示
ryanoasis/vim-devicons ファイルタイプのアイコンを表示する
roxma/nvim-yarp deopleteの依存パッケージ
roxma/vim-hug-neovim-rpc deopleteの依存パッケージ
airblade/vim-gitgutter Git差分を左端に表示する
mbbill/undotree アンドゥツリーを表示(gundoできない環境用)
itchyny/lightline.vim ステータスラインをカスタマイズ
Shougo/neocomplete.vim コード補完(deopleteできない環境用)
Shougo/deoplete.nvim コード補完
Shougo/neosnippet スニペット機能
cespare/vim-toml TOMLのシンタックス

プラグインマネージャ

現在、deinを使用しているが他のプラグインマネージャに移行するか検討する。

  • Vundle
  • vim-pathogen
  • vim-plug
  • NeoBundle

ただ、今更プラグインマネージャを入れ替えるのは手間なのでdeinのままで進める。

プラグイン 変更点 理由
Shougo/dein.vim なし 移行理由がなかったため

カラースキーム

これまでmolokaiを使用してきたが、私にはすこし黒が強すぎるので他のcolorを試してみる。 http://vimcolors.com/ から、しばらくはonedarkを使用することにした。

プラグイン 変更点 理由
tomasr/molokai ntk148v/vim-horizonへ移行 気分転換

dev.classmethod.jp

LSP

LSP (Language Server Protocol) は、Microsoftが設計したプロトコルであり、エディタに必要な機能を実現するためのやり取りを規定している。

qiita.com

VimでLSPを利用する場合には、プログインを使う方法が最も簡単だと思われる。

ここでは、拡張機能の豊富な点からcoc.nvimを利用する。

プラグイン 変更点 理由
neoclide/coc.nvim 追加 LSPプラグインの中で、拡張機能に優れているため

検索

fzf (fuzzy finder) がコマンドラインであいまい検索ができるツールとなっている。

wonderwall.hatenablog.com

プラグイン 変更点 理由
junegunn/fzf 追加 あいまい検索を使うため (fzf本体)
junegunn/fzf.vim 追加 検索時の入力ミスが目立ってきたため

表示

プラグイン 変更点 理由
kshenoy/vim-signature 追加 Vimシグネチャ機能を使えていないため、導入として
machakann/vim-highlightedyank 追加 ヤンクの範囲漏れが目立ってきたので

プラグイン削除

Vimの標準コマンドで代用できるプラグインの削除

NERDTree は、ディレクトリツリーを表示してくれるプラグインである。 現状は、起動時に自動でディレクトリを表示させるようにして、開発効率の貢献をしてくれたと思っている。 しかし、:e .ディレクトリ表示ができるらしいので、あえてこのプラグインを削除して運用してみることにした。

VimShellは、Vimからシェルを起動するプラグインである。 ちょっとしたプログラムやスクリプトを確認するときに約に立ってくれたプラグインでもあるが、:terminalで代用できることもありいったん削除することにした。

deopleteは補完ツールプラグインの一つで、入力した文字から動的に補完バーを表示してくれる。 こちらもかなりお世話にになっているプラグインで、neco-lookと組み合わせて開発効率にかなり貢献してくれた。 しかし、VimにはデフォルトでCtrl-X補完機能が備わっているため、しばらくはそちらで運用してみることにした。

プラグイン 変更点 理由
scrooloose/nerdtree 削除 :e .で代用できるため
Shougo/vimshell 削除 :terminalで代用できるため
Shougo/neocomplete.vim 削除 Ctrl-X補完モードで代用
Shougo/deoplete.nvim 削除 Ctrl-X補完モードで代用
Shougo/neosnippet-snippets 削除 deopleteを削除するため
ujihisa/neco-look 削除 deopleteを削除するため
Shougo/neosnippet 削除 deopleteを削除するため
roxma/nvim-yarp 削除 deopleteを削除するため
roxma/vim-hug-neovim-rpc 削除 deopleteを削除するため

最近あまり使用していなかったプラグインの削除

context_filetypeは動的にfiletypeを判定してくれるツールで、ファイル内に複数のfiletypeがあるような言語での開発などに大いに活躍してくれる。 しかし、現在ファイルシステムの開発やCプログラムを触っている私には、そのような機会がなくあまり恩恵が受けられなかったので削除して運用する。

vim-indent-guidesは、インデントを可視化するプラグインで、プログラムの深さを意識させてくれる。 しかし、タブ文字派に転職したことでタブ文字からインデントを意識できるようになったので削除して運用する。

gundoは、アンドゥツリーを表示するプラグインであり、戻したりやり直したりを容易にできるプラグインである。 しかし、自分はこの機能を忘れて普通にアンドゥやリドゥを使っていることが多かったので削除して運用する。

vim-tomlはTOML用のシンタックスを提供してくれるプラグインであるが、あまりTOMLファイルを書かないので必要になったときにインストールすることにして、今回は削除して運用する。

プラグイン 変更点 理由
Shougo/context_filetype.vim 削除 あまり使わないため
nathanaelkane/vim-indent-guides 削除 あまり使わないため
sjl/gundo.vim 削除 あまり使わないため
mbbill/undotree 削除 あまり使わないため
cespare/vim-toml 削除 あまり使わないため

見直し後の環境

最終的に導入しているプラグインは以下のようになった。

プラグイン 概要
Shougo/dein.vim Vimプラグインマネージャ
Shougo/vimproc.vim Vimを非同期処理
ntk148v/vim-horizon カラースキーム
Lokaltog/vim-easymotion キータッチで目的行に移動する
neoclide/coc.nvim LSPプラグイン
airblade/vim-gitgutter Git差分を左端に表示する
itchyny/lightline.vim ステータスラインをカスタマイズ
ryanoasis/vim-devicons ファイルタイプのアイコンを表示する
junegunn/fzf あいまい検索を使うため (fzf本体)
junegunn/fzf.vim fuzzy finder
kshenoy/vim-signature シグネチャの位置をマークする
machakann/vim-highlightedyank ヤンク範囲を一時的にハイライトする

整理前と比べて表示の部分もすっきりとした感じになった。

2022年現在のNeoVim

おわりに

今回はVimの使ってなかったプラグインの削除をして、vimrcの整理をした。 しばらくは、必要最低限度のプラグインで運用していき必要になったら追加していくようにする。

変更履歴

  • 2019/12/02: 記事公開
  • 2022/02/27: 2022年用に更新
  • 2022/06/05: デザイン修正

参考

trace-cmdでカーネルの関数のコールフローを取得する

概要

trace-cmdは、Linuxカーネルの機能ftraceを利用したツールであり、実行中のLinuxカーネルから関数コールスタックを取できる。

本記事では、別のLinuxマシンにトレースデータをTCPで送ることで、lsコマンドを実行したときのカーネル関数のコールフローを取得する。

はじめに

Linuxカーネルには、デフォルトでカーネルをトレースする機能がいくつか提供されている。 ftraceはトレース機構の一つで、カーネルの実行中に実行されたさまざまな関数呼び出しに関連する情報を取得できる。

Ftrace is an internal tracer designed to help out developers and designers of systems to find what is going on inside the kernel. It can be used for debugging or analyzing latencies and performance issues that take place outside of user-space. https://www.kernel.org/doc/Documentation/trace/ftrace.txt

今回は、ftraceをユーザランドから使いやすくしたツールtrace-cmdを用いて、関数コールフローを追ってみる。

利用イメージは下記の通りとなっている。 ゲスト2で関数コールフローを採取し、同じネットワークにつながれているゲスト1へTCPでトレースデータtrace.datを取得する。

関数コールフロー取得における利用イメージ

関数コールフローを採取するマシンと、トレースデータを取得するマシンを分けることで以下の利点がある。 *1

  • トレースデータを一つに集約することができる

ftraceとtrace-cmdの使い方など先駆者の方のリンクにまとめられているので、そちらを参照することをオススメする。

qiita.com

tasukuchan.hatenablog.com

qiita.com

yohgami.hateblo.jp

準備

内部ネットワークの構築

仮想マシン同士を同一ネットワークでつなげる (ゲスト1: 192.168.7.11、ゲスト2: 192.168.7.12)

# -*- mode: ruby -*-
# vi: set ft=ruby :

Vagrant.configure("2") do |config|
  config.vm.box = "ubuntu/bionic64"
  
  config.vm.define "debugger" do |c|
    c.vm.provider "virtualbox" do |vb|
      vb.memory = "4096"
      vb.cpus = 2
    end
    c.vm.network "private_network", ip: "192.168.7.11", virtualbox__intnet: "devnet"
  end

  config.vm.define "debuggee" do |c|
    c.vm.provider "virtualbox" do |vb|
      vb.memory = "4096"
      vb.cpus = 1
    end
    c.vm.network "private_network", ip: "192.168.7.12", virtualbox__intnet: "devnet"
  end

前回からの記事と同様に、ゲスト1をdebugger、ゲスト2をdebuggeeと呼ぶことにする。

ftraceの設定

trace-cmdを使用するためには、ftraceが使用できることが条件となっている。 ftraceが使える条件は以下の二つが設定されていることが重要である。

  • カーネルコンフィグレーションでftraceに対応している
  • debugfsをマウントしている

カーネルコンフィグレーションは以下の設定が必要である。(前回の仮想マシンではデフォルトで有効化になっているので変更不要)

CONFIG_FTRACE = y
CONFIG_FUNCTION_TRACER = y
CONFIG_FUNCTION_GRAPH_TRAC = y

debugfsのマウントも確認が必要である。

debuggee:~$ mount | grep debugfs
debugfs on /sys/kernel/debug type debugfs (rw,relatime)

trace-cmdのインストール

両方の仮想マシンにtrace-cmdをインストールする。

debuggee:~$ sudo apt install trace-cmd
debugger:~$ sudo apt install trace-cmd

トレースの取得

コマンドのトレースを取得する

  1. ゲスト1(debugger)の11111ポートでトレースを待ち受ける (必要に応じてポート開放すること)

     debugger:~$ sudo trace-cmd listen -p 11111
    
  2. ゲスト2(debuggee)からlsコマンドを実行し、トレース結果をゲスト1(192.168.7.11)の11111ポートへ転送する

     debuggee:~$ sudo trace-cmd record -N 192.168.7.11:11111 -p function_graph -F ls plugin 'function_graph'
    
  3. トレースを出力したら、ゲスト1(debugger)のトレース待ち受け状態を^Cで解除する

     Connected with 192.168.7.11:40234
     cpus=1
     pagesize=4096
     CPU0 data recorded at offset=0x4a8000
         2039808 bytes in size
     ^C
    
      debugger:~$
    
  4. 出力されたトレースデータtrace.192.168.7.11:40234.datをプレーンテキスト形式trace.listに出力する

     debugger:~$ sudo trace-cmd report trace.192.168.7.12\:48144.dat > trace.list
    
  5. 出力結果の確認

     debugger:~$ head -n 20 trace.list 
     cpus=1
                <...>-2278  [000]  4757.532540: funcgraph_entry:        1.148 us   |  mutex_unlock();
                <...>-2278  [000]  4757.532541: funcgraph_entry:        0.274 us   |  __fsnotify_parent();
                <...>-2278  [000]  4757.532541: funcgraph_entry:        0.164 us   |  fsnotify();
                <...>-2278  [000]  4757.532542: funcgraph_entry:        0.114 us   |  __sb_end_write();
                <...>-2278  [000]  4757.532542: funcgraph_entry:                   |  __f_unlock_pos() {
                <...>-2278  [000]  4757.532542: funcgraph_entry:        0.122 us   |    mutex_unlock();
                <...>-2278  [000]  4757.532542: funcgraph_exit:         0.341 us   |  }
                <...>-2278  [000]  4757.532544: funcgraph_entry:                   |  __do_page_fault() {
                <...>-2278  [000]  4757.532544: funcgraph_entry:        0.118 us   |    down_read_trylock();
                <...>-2278  [000]  4757.532544: funcgraph_entry:                   |    _cond_resched() {
                <...>-2278  [000]  4757.532544: funcgraph_entry:        0.117 us   |      rcu_all_qs();
                <...>-2278  [000]  4757.532545: funcgraph_exit:         0.347 us   |    }
                <...>-2278  [000]  4757.532545: funcgraph_entry:                   |    find_vma() {
                <...>-2278  [000]  4757.532545: funcgraph_entry:        0.157 us   |      vmacache_find();
                <...>-2278  [000]  4757.532545: funcgraph_exit:         0.398 us   |    }
                <...>-2278  [000]  4757.532545: funcgraph_entry:                   |    handle_mm_fault() {
                <...>-2278  [000]  4757.532545: funcgraph_entry:        0.111 us   |      mem_cgroup_from_task();
                <...>-2278  [000]  4757.532546: funcgraph_entry:                   |      __handle_mm_fault() {
                <...>-2278  [000]  4757.532546: funcgraph_entry:        0.131 us   |        pmd_devmap_trans_unstable();
     <snip>
    

プロセスのトレースを取得する

  1. ゲスト1(debugger)の11111ポートでトレースを待ち受ける (必要に応じてポート開放すること)

     debugger:~$ sudo trace-cmd listen -p 11111
    
  2. ゲスト2(debuggee)において、トレース対象のプロセスのpidを確認する。(今回はjbd2を対象とする)

     debuggee:~$ ps -e | grep jbd2
       330 ?        00:00:00 jbd2/sda1-8
    
  3. ゲスト2(debuggee)からpidを指定し、トレース結果をゲスト1(192.168.7.11)の11111ポートへ転送する

     debuggee:~$ sudo trace-cmd record -N 192.168.7.11:11111 -p function_graph -P 330 plugin 'function_graph'
    
  4. プロセスが動作したことを確認したら、ゲスト2(debuggee)のトレース出力状態を^Cで解除する

     ^C
    
     debuggee:~$
    
  5. 出力されたトレースデータtrace.192.168.7.11:48148.datをプレーンテキスト形式trace.listに出力する

     Connected with 192.168.7.12:48148
     cpus=1
     pagesize=4096
     CPU0 data recorded at offset=0x4ba000
    
     debugger:~$ sudo trace-cmd report trace.192.168.7.12\:48148.dat > trace.list
    
  6. 出力結果の確認

     debugger:~$ head -n 20 trace.list 
     cpus=1
               <idle>-0     [000] 10069.427204: funcgraph_entry:      + 12.184 us  |  enter_lazy_tlb();
          jbd2/sda1-8-330   [000] 10069.427218: funcgraph_entry:                   |  finish_task_switch() {
          jbd2/sda1-8-330   [000] 10069.427225: funcgraph_entry:                   |    smp_irq_work_interrupt() {
          jbd2/sda1-8-330   [000] 10069.427226: funcgraph_entry:                   |      irq_enter() {
          jbd2/sda1-8-330   [000] 10069.427226: funcgraph_entry:                   |        rcu_irq_enter() {
          jbd2/sda1-8-330   [000] 10069.427227: funcgraph_entry:        0.638 us   |          rcu_nmi_enter();
          jbd2/sda1-8-330   [000] 10069.427228: funcgraph_exit:         1.779 us   |        }
          jbd2/sda1-8-330   [000] 10069.427229: funcgraph_exit:         2.952 us   |      }
          jbd2/sda1-8-330   [000] 10069.427235: funcgraph_entry:                   |      __wake_up() {
          jbd2/sda1-8-330   [000] 10069.427236: funcgraph_entry:                   |        __wake_up_common_lock() {
          jbd2/sda1-8-330   [000] 10069.427236: funcgraph_entry:        0.568 us   |          _raw_spin_lock_irqsave();
          jbd2/sda1-8-330   [000] 10069.427237: funcgraph_entry:        0.597 us   |          __wake_up_common();
          jbd2/sda1-8-330   [000] 10069.427239: funcgraph_entry:        0.568 us   |          __lock_text_start();
          jbd2/sda1-8-330   [000] 10069.427240: funcgraph_exit:         4.013 us   |        }
          jbd2/sda1-8-330   [000] 10069.427240: funcgraph_exit:         5.284 us   |      }
          jbd2/sda1-8-330   [000] 10069.427241: funcgraph_entry:                   |      __wake_up() {
          jbd2/sda1-8-330   [000] 10069.427241: funcgraph_entry:                   |        __wake_up_common_lock() {
          jbd2/sda1-8-330   [000] 10069.427242: funcgraph_entry:        0.550 us   |          _raw_spin_lock_irqsave();
          jbd2/sda1-8-330   [000] 10069.427243: funcgraph_entry:                   |          __wake_up_common() {
    

イベントのトレースを取得する

  1. ゲスト1(debugger)の11111ポートでトレースを待ち受ける (必要に応じてポート開放すること)

     debugger:~$ sudo trace-cmd listen -p 11111
    
  2. ゲスト2(debuggee)からイベントnetを指定し、トレース結果をゲスト1(192.168.7.11)の11111ポートへ転送する (指定できるイベントはtrace-cmd list -eで確認できる)

     debugger:~$ sudo trace-cmd record -N 192.168.7.11:11111 -p function_graph -e net ping -c 1 192.168.7.11
       plugin 'function_graph'
     PING 192.168.7.11 (192.168.7.11) 56(84) bytes of data.
     64 bytes from 192.168.7.11: icmp_seq=1 ttl=64 time=0.176 ms
    
     --- 192.168.7.11 ping statistics ---
     1 packets transmitted, 1 received, 0% packet loss, time 0ms
     rtt min/avg/max/mdev = 0.176/0.176/0.176/0.000 ms
    
  3. トレースを出力したら、ゲスト1(debugger)のトレース待ち受け状態を^Cで解除する

     Connected with 192.168.7.12:48150
     cpus=1
     pagesize=4096
     CPU0 data recorded at offset=0x4be000
         1609728 bytes in size
     ^C
    
     debugger:~$
    
  4. 出力されたトレースデータtrace.192.168.7.11:48150.datをプレーンテキスト形式trace.listに出力する

     debugger:~$ sudo trace-cmd report trace.192.168.7.12\:48150.dat > trace.list
    
  5. 出力結果の確認

     debugger:~$ head -n 20 trace.list 
     cpus=1
                <...>-2746  [000] 10875.245946: funcgraph_entry:                   |  mutex_unlock() {
                <...>-2746  [000] 10875.245951: funcgraph_entry:                   |    smp_irq_work_interrupt() {
                <...>-2746  [000] 10875.245951: funcgraph_entry:                   |      irq_enter() {
                <...>-2746  [000] 10875.245952: funcgraph_entry:                   |        rcu_irq_enter() {
                <...>-2746  [000] 10875.245952: funcgraph_entry:        0.154 us   |          rcu_nmi_enter();
                <...>-2746  [000] 10875.245952: funcgraph_exit:         0.464 us   |        }
                <...>-2746  [000] 10875.245952: funcgraph_exit:         0.725 us   |      }
                <...>-2746  [000] 10875.245954: funcgraph_entry:                   |      __wake_up() {
                <...>-2746  [000] 10875.245954: funcgraph_entry:                   |        __wake_up_common_lock() {
                <...>-2746  [000] 10875.245954: funcgraph_entry:        0.121 us   |          _raw_spin_lock_irqsave();
                <...>-2746  [000] 10875.245954: funcgraph_entry:        0.116 us   |          __wake_up_common();
                <...>-2746  [000] 10875.245954: funcgraph_entry:        0.120 us   |          __lock_text_start();
                <...>-2746  [000] 10875.245954: funcgraph_exit:         0.835 us   |        }
                <...>-2746  [000] 10875.245955: funcgraph_exit:         1.076 us   |      }
                <...>-2746  [000] 10875.245955: funcgraph_entry:                   |      __wake_up() {
                <...>-2746  [000] 10875.245955: funcgraph_entry:                   |        __wake_up_common_lock() {
                <...>-2746  [000] 10875.245955: funcgraph_entry:        0.114 us   |          _raw_spin_lock_irqsave();
                <...>-2746  [000] 10875.245955: funcgraph_entry:        0.115 us   |          __wake_up_common();
                <...>-2746  [000] 10875.245955: funcgraph_entry:        0.122 us   |          __lock_text_start();
     <snip>
    

おわりに

今回は、カーネルのコードリーディングに役立つftrace/trace-cmdの導入方法についてまとめた。 trace-cmdでカーネルの関数コールフローを取得することで、kgdbでブレークするポイントを特定したり、Linuxカーネルのコードを効率的に読むことができる。

次回は、コードリーディングに役立つツールkprobeの紹介をしたいと思う。

変更履歴

  • 2019/11/18: 記事公開
  • 2022/06/05: デザイン修正

参考

*1:ただし、trace-cmdは採取するマシンと取得するマシンを分ける必要はない。