みつきんのメモ

組み込みエンジニアです。Interface誌で「Yocto Projectではじめる 組み込みLinux開発入門」連載中

Docker入門

はじめに

Dockerに入門してみる。

作業環境はUbuntu 20.04

環境設定

aptでも提供されているが、Install Docker Engine on Ubuntuの手順でインストールしたほうがトラブルは少なそう。

apt版のアンインストール

$ sudo apt purge docker docker-engine docker.io containerd runc

インストール

必要なパッケージのインストール

$ sudo apt install \
     apt-transport-https \
     ca-certificates \
     curl \
     gnupg-agent \
     software-properties-common

キーの設定

$ curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -

リポジトリの設定

$ sudo add-apt-repository \
    "deb [arch=amd64] https://download.docker.com/linux/ubuntu \
    $(lsb_release -cs) \
    stable"

インストール

$ sudo apt update
$ sudo apt install docker-ce docker-ce-cli containerd.io

dockerグループへの追加

dockerコマンドをsudoなしで実行できるようにする

$ sudo gpasswd -a ${USER} docker

一度ログアウトして、再ログインすると有効化される。

最初のコンテナ

いろいろなチュートリアルで、最初にDockerを実行するのは下記のようなコマンド。

$ docker run hello-world

docker runがいろいろなことを自動で実行するため便利だが、筆者のような初心者にはなにが起こっているか分かりづらい。

何もない状態からdocker runを実行する場合下記のようなことが行われる。

  1. イメージの取得
  2. イメージからコンテナを作成
  3. コンテナの起動
  4. コンテナへのアタッチ

dockerのコマンドでいうと下記のような感じ

  1. docker pull / docker build
  2. docker create
  3. docker start
  4. docker attach

イメージとコンテナ

Dockerといえばコンテナ。実際コンテナを作ってその上で作業するためのツールだが、コンテナを実行するためにはイメージが必要となる。

f:id:mickey_happygolucky:20210109161749p:plain
イメージとコンテナ

イメージとコンテナにはそれぞれIDが割り振られる(イメージIDとコンテナID)。これらは同じような16進数のハッシュ値なので、最初は混乱するがきちんと別物だということを理解しておく。

イメージはコンテナの金型のようなもので、1つのイメージからいくつもコンテナを実行できる。

f:id:mickey_happygolucky:20210109161837p:plain
複数のコンテナ

イメージIDは1つだが、コンテナIDは2つ必要になる。

コンテナでいろいろ作業して、使い終わったコンテナを削除することもできる。

f:id:mickey_happygolucky:20210109161921p:plain
コンテナの削除

通常コンテナで作業した内容はイメージには反映されない。

ネット上ではコンテナを実行する際下記のようなコマンドを示されることが多い。

$ docker run -it --rm ubuntu

自動的にbashが実行されubuntuが実行されたコンテナにログインされた状態になるため、ここでいろいろと作業することになる。

しかし--rmによってコンテナからexitした時点でコンテナが削除されてしまう。コンテナでの作業内容は基本的にイメージに反映されないため、作業した内容が失われてしまうことになる。

Dockerでの作業状態

Dockerで作業する場合に把握しておくべき状態をまとめる。便宜上実行状態にA-Eで名前をつけることにする。

状態 イメージ コンテナ 遷移する方法(作る時/A->E) 遷移する方法(消す時/E->A) 確認方法
A なし なし docker rmi
B 作成済み なし docker pull / docker build --rmをつけて実行したコンテナからexit / docker rm docker images
C 作成済み 停止中 docker create --rmなしのコンテナからexit docker ps -a
D 作成済み 実行中 docker start Ctrl+P Ctrl+Q でコンテナを抜ける docker ps
E 作成済み アタッチ docker attach / docker exec

大体いまどこかがわかっていれば生きていける。

よく使われるrunはこれらの状態を複数スキップというか自動実行する。

docker run

書式は下記。詳細(run)

docker run [オプション] イメージ [コマンド] [引数...]

表のAの状態でdocker runを実行すると一気にEの状態まで遷移する。ここで指定しているubuntuイメージ名

$ docker run -it ubuntu
Unable to find image 'ubuntu:latest' locally
latest: Pulling from library/ubuntu
da7391352a9b: Pull complete 
14428a6d4bcd: Pull complete 
2c2d948710f2: Pull complete 
Digest: sha256:c95a8e48bf88e9849f3e0f723d9f49fa12c5a00cfc6e60d2bc99d87555295e4c
Status: Downloaded newer image for ubuntu:latest
root@9f1316bc678f:/# 

--rmをつけていないので、この状態からexitすると表のCの状態に遷移する。

$ docker ps
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES

実行中のコンテナはいない。

下記のコマンドで停止中のコンテナも表示する。

$ docker ps -a
CONTAINER ID        IMAGE               COMMAND             CREATED              STATUS                      PORTS               NAMES
9f1316bc678f        ubuntu              "/bin/bash"         About a minute ago   Exited (0) 41 seconds ago                       focused_pasteur

コンテナID9f1316bc678fのコンテナが表示される。

このコンテナの元になったイメージを確認する。

$ docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
ubuntu              latest              f643c72bc252        6 weeks ago         72.9MB

イメージIDがf643c72bc252のイメージが表示される。

イメージのリポジトリとタグ

先ほど便宜上イメージ名と言ったものは正確にはREPOSITORYという。

1つのリポジトリにはいくつものイメージが存在する。たとえばubuntuの過去のバージョンを選択したい場合はubuntu:18.04のように指定する。

コロン(:)のあとに続く18.04TAGといい、コマンドでREPOSITORYのみ指定した場合、最新を示すlatestというTAGが自動的に選択される。

これらのことからイメージを指定するために必要な識別子はリポジトリ名、タグ名ということになるが、イメージIDと表現を揃えるためイメージ名と呼ぶことにする。

コンテナがある状態でdocker run

停止中とはいえ既にコンテナがある状態で同じイメージに対してdocker runを実行するとどうなるか。

$ docker run -it ubuntu

exitして下記のコマンドを実行する。

$ docker ps -a
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS                      PORTS               NAMES
fd274bb747d1        ubuntu              "/bin/bash"         13 seconds ago      Exited (0) 9 seconds ago                        elegant_blackburn
9f1316bc678f        ubuntu              "/bin/bash"         18 minutes ago      Exited (0) 17 minutes ago                       focused_pasteur

停止中のコンテナが2つになっている。増えた方のコンテナIDはfd274bb747d1

作成済みのイメージからコンテナへアタッチされたことになる。つまり表のBからEに遷移される。

つまり一度作成したコンテナに対してはdocker runでは操作できないということ。docker runに指定するのがイメージ名なのでコンテナへの識別が必要となる操作はできない。

attach vs exec

実行中のコンテナへ接続する方法はattachexecの2つある。これらのコマンドはコンテナ識別子を指定する。(コンテナID/コンテナ名)

書式は下記。詳細(attach)詳細(exec)

docker attach [オプション] コンテナ
docker exec [オプション] コンテナ コマンド [引数...]

docker attach

docker attachはPID=1のプロセスに接続する。Ctrl+P、Ctrl+Qでデタッチした場合でも再接続可能。

$ docker start 9f1316bc678f
$ docker attach 9f1316bc678f
root@9f1316bc678f:/# echo $$
1

環境変数$$で実行中のPIDを取得できる。ここでは1になっている。

作業途中で抜けて再接続したい場合などはこちらで作業すると良さそう。

docker exec

コンテナ内の指定した任意のコマンドを実行できる。attachとは異なり新規にプロセスを作成する。

$ docker start 9f1316bc678f
$ docker exec -it 9f1316bc678f /bin/bash 
root@9f1316bc678f:/# echo $$
8

こちらに関してはCtrl+P、Ctrl+Qでデタッチした場合、このプロセスに再接続する方法がない。

実行中のコンテナで複数のターミナルを開いて作業したい場合などはこちらを使うと良さそう。

コンテナの削除

docker rmで削除する。

書式は下記。詳細(rm)

docker rm [オプション] コンテナ [コンテナ...]

削除するには下記のようにする。

$ docker ps -a
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS                     PORTS               NAMES
2b17b5b1a846        ubuntu              "/bin/bash"         8 seconds ago       Exited (0) 6 seconds ago                       ecstatic_jepsen
$ docker rm -f 2b17b5b1a846

-fを指定すると実行中のコンテナも削除できる。コンテナはIDでも名前でもよい。

イメージの削除

docker rmiで削除できる。

書式は下記。詳細(rmi)

docker rmi [オプション] イメージ [イメージ...]

削除するには下記のようにする。

$ docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
ubuntu              latest              f643c72bc252        6 weeks ago         72.9MB
hello-world         latest              bf756fb1ae65        12 months ago       13.3kB
$ docker rmi bf756fb1ae65

よく使うオプション(-it)

docker rundocker execで、-itというオプションをつけているのをよく見かける。

Docker-docs-jaによると下記の通り。

option long name 機能
-i --interructive コンテナの STDIN にアタッチ
-t --tty 疑似ターミナル (pseudo-TTY) を割り当て

標準入力を擬似ttyに接続することでインタラクティブなシェルを実現できるとのこと。

逆に言えば単発で実行されるコマンドを実行する場合は不要

$ docker start 9f1316bc678f
$ docker exec 9f1316bc678f cat /etc/os-release
NAME="Ubuntu"
VERSION="20.04.1 LTS (Focal Fossa)"
ID=ubuntu
ID_LIKE=debian
PRETTY_NAME="Ubuntu 20.04.1 LTS"
VERSION_ID="20.04"
HOME_URL="https://www.ubuntu.com/"
SUPPORT_URL="https://help.ubuntu.com/"
BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/"
PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy"
VERSION_CODENAME=focal
UBUNTU_CODENAME=focal

docker runの単発実行

任意のコマンドの単発実行について触れたのでdocker runの単発実行についても触れる。

docker runもコマンドを指定することで、起動したコンテナで任意のコマンドを実行することができる。

$ docker run --rm ubuntu cat /etc/os-release

このコマンドを実行すると下記のことが実行される。

  1. イメージの取得
  2. コンテナの作成
  3. コンテナの起動
  4. コンテナ内でコマンドを実行
  5. コンテナ終了
  6. コンテナの削除

表の状態のA->B->C->D->E->D->Bという遷移が自動で行われる。

コンテナの作業内容の保存

コンテナ内でファイルを追加したり環境を構築したりてもコンテナを削除すると全て失われるが、 これらを保持したい場合は、docker commitでイメージにすることができる。

書式は下記。

docker commit [オプション] コンテナ [リポジトリ[:タグ]]

下記のように実行する。

$ docker commit 9f1316bc678f my_env:version1

まとめ

いままでDockerをなんとなく使っていたが今回まとめてみた。

runが高機能すぎて、わからなくてもなんとなく使えてしまうのが良し悪しあると思う。

attachとexecも割と混乱する。

これで君もDockerマスターだ!(嘘)

Giant BoardをYoctoに対応する

はじめに

Giant BoardをDebian GNU/Linux 9 (stretch)で動かす動かしたGiant BoardをYoctoProjectに対応する。

ここでは新規ボード向けにYoctoProjectのレイヤを作る最小限の手順を紹介する。

新規ボードのレイヤを作る

YoctoProjectが未対応のボードに対応するためにはBSPレイヤを作成する。BSPレイヤを作成する場合次のものが必要となる。

今回はmeta-giantboardというレイヤを作成する。

作成の方針

meta-atmel

SAMA5向けのブートストラップなどはmeta-atmelで定義されている。

https://github.com/linux4sam/meta-atmelをベースにGiant Board向けの対応を作成する。

今回はmeta-atmelに取り込んでもらう目的ではないため独自にmeta-giantboardを作成する。この時meta-atmelのレシピの作り方などは参考にさせてもらう。

Giant Board対応の変更

at91bootstrap、u-bootおよびLinuxカーネルへのGiand Board対応の変更についてはgiantboard-toolsから取り込む。

下記のコマンドで一通りのビルドを実行しているものとする。

$ cd
$ git clone https://github.com/Groboards/giantboard-tools.git
$ cd giantboard-tools
$ chmod +x build_menu.sh
$ ./build_menu.sh

作業環境の作成

ベース環境の構築

Dunfell(3.1)をベースに環境を作る。

$ mkdir -p ~/gb-dunfell/layers && cd ~/gb-dunfell/layers
$ git clone git://git.yoctoproject.org/poky.git -b dunfell
$ cd ../

環境変数設定

$ source layers/poky/oe-init-build-env build

自動的にビルドディレクトリに移動される。 これで、bitbake関連のツールが使用可能になる。

meta-giantboardの作成

$ bitbake-layers create-layer meta-giantboard -p 9
$ rm -rf ./meta-giantboard/recipes-example
$ mv ./meta-giantboard/ ../layers/

ビルド対象へ追加

$ bitbake-layers add-layer ../layers/meta-giantboard

Giant BoardのHW情報

CPU

# cat /proc/cpuinfo
processor       : 0
model name      : ARMv7 Processor rev 1 (v7l)
BogoMIPS        : 326.86
Features        : half thumb fastmult vfp edsp neon vfpv3 tls vfpv4 vfpd32
CPU implementer : 0x41
CPU architecture: 7
CPU variant     : 0x0
CPU part        : 0xc05
CPU revision    : 1

Hardware        : Atmel SAMA5
Revision        : 0000
Serial          : 0000000000000000

メモリ

# free -h
              total        used        free      shared  buff/cache   available
Mem:           117M         14M         70M        960K         33M         97M
Swap:            0B          0B          0B

マシン定義の作成

$ cd ../layers/meta-giantboard
$ mkdir -p conf/machine

conf/machine/giantboard.confを以下の内容で作成する。

#@TYPE: Machine
#@Name: Groboards Giant Board
#@DESCRIPTION: A single-board computer in the Adafruit Feather form factor.

# Atmel SAMA5 defaults
require conf/machine/include/soc-family.inc
require conf/machine/include/tune-cortexa5.inc

# Atmel SAMA5D2 defaults
SOC_FAMILY ?= "sama5:sama5d2"
DEFAULTTUNE ?= "cortexa5thf-neon-vfpv4"

# Machine Features
MACHINE_FEATURES ?= "apm vfat ext2 serial usbhost usbgadget"

# Virtual package definitions
PREFERRED_PROVIDER_virtual/kernel_sama5 ?= "linux-giantboard"
PREFERRED_PROVIDER_virtual/bootloader_sama5 ?= "u-boot-at91"
PREFERRED_PROVIDER_u-boot_sama5 ?= "u-boot-at91"

PREFERRED_PROVIDER_jpeg ?= "jpeg"
PREFERRED_PROVIDER_jpeg-native ?= "jpeg-native"

# Default settings for serial console
SERIAL_CONSOLES ?= "115200;ttyS0"

# Kernel informations
PREFERRED_VERSION_linux ?= "5.4%"
KERNEL_DEVICETREE ?= " \
                at91-sama5d27_giantboard.dtb \
        "

# Settings for wic image
do_image_wic[depends] += "at91bootstrap:do_deploy u-boot-at91:do_deploy"
IMAGE_FSTYPES += " tar.gz wic.bz2 wic.bmap"
WKS_FILE ?= "sdimage-bootpart.wks"
WIC_CREATE_EXTRA_ARGS ?= "--no-fstab-update"

# File definitions that BOOT partation contains.
IMAGE_BOOT_FILES ?= "BOOT.BIN \
           u-boot.bin \
           uEnv.txt \
           ${KERNEL_DEVICETREE} \
           ${KERNEL_IMAGETYPE} \
"

基本的にconfで定義する変数は再定義できる余地を残しておくために?=??=で定義しておく。この時点で=で強い束縛をしてしまうとlocal.confで再定義できないなど非常に不便になるので注意。

絶対に再定義させないという強い意志 ががない場合は=の使用はおすすめしない。 もしどうしても再定義させたくない場合はコメントなどで再定義できない理由を記載しておくと良い。

以下内容を解説する。

SoC定義

SoCの定義関連。これによってクロスコンパイル時のターゲットや最適化オプションなどが決定される。

# Atmel SAMA5 defaults
require conf/machine/include/soc-family.inc
require conf/machine/include/tune-cortexa5.inc

# Atmel SAMA5D2 defaults
SOC_FAMILY ?= "sama5:sama5d2"
DEFAULTTUNE ?= "cortexa5thf-neon-vfpv4"

Machine Features

ハードウェアが持っている機能について設定する。USBやWiFiなどの有無など。 ビルド全体に関わるコンパイルフラグに影響する。

# Machine Features
MACHINE_FEATURES ?= "apm vfat ext2 serial usbhost usbgadget"

バーチャル関連の定義

virtual/kenrelvirtual/bootloaderなどの仮想ターゲットと実体を紐付けるための定義。

# Virtual package definitions
PREFERRED_PROVIDER_virtual/kernel_sama5 ?= "linux-giantboard"
PREFERRED_PROVIDER_virtual/bootloader_sama5 ?= "u-boot-at91"
PREFERRED_PROVIDER_u-boot_sama5 ?= "u-boot-at91"

PREFERRED_PROVIDER_jpeg ?= "jpeg"
PREFERRED_PROVIDER_jpeg-native ?= "jpeg-native"

このおかげで下記のようにビルドを実行できる。

$ bitbake virtual/kernel

最後のjpeg関連についてはデフォルトでJPEG関連のライブラリをjpegパッケージにするかjpeg-turboパッケージにするかという目的で使用される。

シリアル定義

シリアルコンソールのポートや設定のデフォルトを定義する。

# Default settings for serial console
SERIAL_CONSOLES ?= "115200;ttyS0"

ARMではシリアルポートがttyAMA0だったりする場合があるのでそういう差異を吸収している。

カーネル設定

ターゲット向けにカーネルをビルドする際に必要な情報の定義。 デフォルトで使用するカーネルのバージョンや、defconfig、デバイスツリーを設定している。

# Kernel informations
PREFERRED_VERSION_linux ?= "5.4%"
KERNEL_DEVICETREE ?= " \
                at91-sama5d27_giantboard.dtb \
        "

wicイメージ関連の設定

YoctoではWicというイメージ生成機能を使用できる。

ここではWicイメージを生成するために必要な情報を定義している。

Wicはパーティション情報を含んでおり、SDカードなどのストレージにddやbmaptoolなどで直接OSイメージを流し込むことができる。

# Settings for wic image
do_image_wic[depends] += "at91bootstrap:do_deploy"
IMAGE_FSTYPES += " tar.gz wic.bz2 wic.bmap"
WKS_FILE ?= "sdimage-bootpart.wks"
WIC_CREATE_EXTRA_ARGS ?= "--no-fstab-update"

BOOTパーティションの設定

BOOTパーティションに含めるファイルの定義。

# File definitions that BOOT partation contains.
IMAGE_BOOT_FILES ?= "BOOT.BIN \
           u-boot.bin \
           uEnv.txt \
           ${KERNEL_DEVICETREE} \
           ${KERNEL_IMAGETYPE} \
"

ここでBOOTパーティションに含めるファイルを設定する。

ブートローダのレシピ

SAMA5D2でLinuxブートするためには、次のソフトウェアが必要になる。

ブートローダのレシピはrecipes-bspに配置する。

$ mkdir recipes-bsp
$ mkdir recipes-bsp/at91bootstrap recipes-bsp/u-boot

at91bootstrap

giantboard-toolsを確認すると、使用しているバージョンは下記の通り。

commit 5a89d0a2314761d51b2c750592273a0edd3addf8 (HEAD -> tmp, tag: v3.8.13)
Author: Eugen Hristev <eugen.hristev@microchip.com>
Date:   Wed Jul 10 13:42:17 2019 +0300

    Makefile: prepare 3.8.13
    
    Signed-off-by: Eugen Hristev <eugen.hristev@microchip.com>

これにdefconfigの定義を追加してビルドしている。

recipes-bsp/at91bootstrap/at91bootstrap_3.8.13.bbを下記の内容で作成する。

SUMMARY = "Initial bootstrap for AT91 ARM MPUs"
DESCRIPTION = " \
        at91bootstrap is the second-level bootloader for Atmel AT91  \
        SoCs. It provides a set of algorithms to manage the hardware \
        initialization and to download the main application (or a    \
        third-level bootloader) from specified boot media to         \
        main memory and start it.                                    \
          "
AUTHOR = "Atmel Corporation"
HOMEPAGE = "http://www.at91.com/linux4sam/bin/view/Linux4SAM/AT91Bootstrap"
BUGTRACKER = "https://github.com/linux4sam/at91bootstrap/issues"
SECTION = "bootloaders"
LICENSE = "ATMEL"
LIC_FILES_CHKSUM = "file://main.c;endline=27;md5=a2a70db58191379e2550cbed95449fbd"

COMPATIBLE_MACHINE = '(giantboard)'

SRC_URI = "git://github.com/linux4sam/at91bootstrap.git;protocol=https \
    file://sama5d27_giantboard_sd1_uboot_defconfig \
"

PV = "3.8.13+git${SRCPV}"
SRCREV = "5a89d0a2314761d51b2c750592273a0edd3addf8"

S = "${WORKDIR}/git"

inherit deploy
DEPENDS += "bc-native"

EXTRA_OEMAKE = 'CROSS_COMPILE=${TARGET_PREFIX} CC="${TARGET_PREFIX}gcc ${TOOLCHAIN_OPTIONS}"'

do_unpack_append() {
    bb.build.exec_func('do_copy_defconfig', d)
}

do_copy_defconfig() {
    cp ${WORKDIR}/sama5d27_giantboard_sd1_uboot_defconfig ${S}/board/sama5d27_som1_ek
}

do_configure() {
    oe_runmake distclean
    oe_runmake sama5d27_giantboard_sd1_uboot_defconfig
}

do_compile() {
    oe_runmake
}

do_deploy () {
    install -d ${DEPLOYDIR}
    install ${S}/binaries/boot.bin ${DEPLOYDIR}/BOOT.BIN
}
addtask deploy before do_build after do_compile

結構細かい小技が効いているので解説していく。

ヘッダ部

レシピの概要やこのレシピでパッケージングするOSSの情報について記述する。

SUMMARY = "Initial bootstrap for AT91 ARM MPUs"
DESCRIPTION = " \
        at91bootstrap is the second-level bootloader for Atmel AT91  \
        SoCs. It provides a set of algorithms to manage the hardware \
        initialization and to download the main application (or a    \
        third-level bootloader) from specified boot media to         \
        main memory and start it.                                    \
          "
AUTHOR = "Atmel Corporation"
HOMEPAGE = "http://www.at91.com/linux4sam/bin/view/Linux4SAM/AT91Bootstrap"
BUGTRACKER = "https://github.com/linux4sam/at91bootstrap/issues"
SECTION = "bootloaders"
LICENSE = "ATMEL"
LIC_FILES_CHKSUM = "file://main.c;endline=27;md5=a2a70db58191379e2550cbed95449fbd"

COMPATIBLE_MACHINE = '(giantboard)'

COMPATIBLE_MACHINEを指定することで、MACHINEにgiantboard以外を設定されている時にat91bootstrapのレシピが選択された場合エラーにすることができる。 ブートローダのような明らかに他のボードでは動かないようなものなこのようにしておくとミスが発見しやすくなる。

ライセンスについて

ヘッダ部のうちライセンスの記述はLICENSELIC_FILES_CHKSUM

LICENSE = "ATMEL"
LIC_FILES_CHKSUM = "file://main.c;endline=27;md5=a2a70db58191379e2550cbed95449fbd"

at91bootstrapはAt91bootstrap-licenseというライセンスで配布されている。

LIC_FILES_CHKSUMはレシピ作成時とライセンスが変わっていないかをチェックできる。この例ではmain.cの先頭からendline=27で指定した行までがライセンス記述だという指定。この部分に対してmd5でハッシュを取っている。つまり、この部分の文言に変更があった場合md5の値が変わるためライセンス定義の変更を検出することができる。

ATMELの独自ライセンスであるため、レシピだけライセンス情報を記述するだけでは下記のような警告が表示される。

WARNING: at91bootstrap-3.8.13+gitAUTOINC+5a89d0a231-r0 do_populate_lic: at91bootstrap: No generic license file exists for: ATMEL in any provider

これはLICENSEにbitbakeが管理しているライセンスにないものを指定された場合に出される。この警告によってケアされていないライセンスが混入していないか確認を促すことができる。

この警告を黙らせるためには、layers.confで下記のように設定し、ここで指定したディレクトリにライセンスの内容を記述したファイルを配置しておく。つまりもともとYocto ProjectやOpenEmbeddedで管理していないようなライセンスを取り扱う場合、レイヤの提供者がきちんと管理している必要があるということになる。

# License
LICENSE_PATH += "${LAYERDIR}/licenses"

meta-giantboard直下にlicensesディレクトリを作成する。

$ mkdir licenses

ここではlicenses/ATMELとして下記の内容でファイルを作成している。

----------------------------------------------------------------------------
        ATMEL Microcontroller Software Support
----------------------------------------------------------------------------
Copyright (c) 2006, Atmel Corporation

All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

- Redistributions of source code must retain the above copyright notice,
this list of conditions and the disclaimer below.

Atmel's name may not be used to endorse or promote products derived from
this software without specific prior written permission.

DISCLAIMER: THIS SOFTWARE IS PROVIDED BY ATMEL "AS IS" AND ANY EXPRESS OR
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT ARE
DISCLAIMED. IN NO EVENT SHALL ATMEL BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

ソースファイル関連

パッケージングされるソフトウェアのソースコードの有りか及びパッチファイルや追加のコンフィグファイルをSRC_URLで指定する。PVはパッケージバージョンの指定、SRCREVでどのコミットを使用するかを指定する。

Sはbitbakeがビルド時に参照する変数でソースコード(Source)を配置したディレクトリを指定する。ちなみにビルドディレクトリを指定するB(Build)、生成物を配置するD(Destination)という変数もあり、これらはレシピのデバッグ時にはよく参照することになる。

SRC_URI = "git://github.com/linux4sam/at91bootstrap.git;protocol=https \
    file://sama5d27_giantboard_sd1_uboot_defconfig \
"

PV = "3.8.13+git${SRCPV}"
SRCREV = "5a89d0a2314761d51b2c750592273a0edd3addf8"

S = "${WORKDIR}/git"

依存関係関連

inheritはビルドシステムがbbclassとして提供する便利な関数をレシピ内で使用するために記述する。ここではブートパーティションにファイルを配置するために使用するdeployを使用する。

DEPENDSはこのレシピをビルドするときに依存するレシピを指定する。

inherit deploy
DEPENDS += "bc-native"

環境設定関連

EXTRA_OEMAKEmakeコマンド実行時に渡すパラメータを指定する。ここではクロスコンパイルに必要な設定を行っている。

EXTRA_OEMAKE = 'CROSS_COMPILE=${TARGET_PREFIX} CC="${TARGET_PREFIX}gcc ${TOOLCHAIN_OPTIONS}"'

タスク

実際のビルド処理を実行するにはタスクを定義していく。

基本的には下記のような流れで実行されていく。実際にはライセンスチェックやサニティチェックなど他にも細々とタスクがある。

  1. do_fetch(ソースのダウンロード)
  2. do_unpack(ダウンロードしたソースファイルの展開)
  3. do_configure(ビルド設定/configureスクリプトの実行など)
  4. do_compile(ビルド実行)
  5. do_install(ローカル環境へのインストール/make installなど)
  6. do_package(パッケージ作成/rpm,debなど)

各タスクにはその直前や直後に処理を追加する_prepend_appendをいう関数を定義することができる。 また、タスク自体も自由にに追加や削除ができる。

do_unpack

at91bootstrapをgiantboard向けにビルドするためにはdefconfigを追加する必要がある。このdefconfigはboard/sama5d27_som_ekに配置されている必要があるため、do_unpackタスクの処理を拡張して、ファイルをコピーする。

ただ、do_unpackpython関数として実装されているためshell関数としてdo_unpack_appendを追加すると構文エラーになってしまう。そのためpythonからbb.build.exec_funcでshell関数を呼び出すという方法を取っている。

do_unpack_append() {
    bb.build.exec_func('do_copy_defconfig', d)
}

do_copy_defconfig() {
    cp ${WORKDIR}/sama5d27_giantboard_sd1_uboot_defconfig ${S}/board/sama5d27_som1_ek
}

do_configure

makeコマンドを直接実行指定も良いが、oe_runmake関数を使用すると必要な変数やパラメータなどをあらかじめ設定してmakeコマンドを呼び出してくれるのでこちらを使用するほうが安全。

do_configure() {
    oe_runmake distclean
    oe_runmake sama5d27_giantboard_sd1_uboot_defconfig
}

do_compile

-j $(nproc)のような並列化の指定などもやってくれる。

do_compile() {
    oe_runmake
}

do_deploy

ブートローダはブートパーティションに配置する必要があるので、do_deployタスクを作成しaddtaskで追加している。 do_deployに必要なDEPLOYDIRなどの定義を使用するためにあらかじめinherit deployでbbclassを継承している。

do_deploy () {
    install -d ${DEPLOYDIR}
    install ${S}/binaries/boot.bin ${DEPLOYDIR}/BOOT.BIN
}
addtask deploy before do_build after do_compile

defconfig

SRC_URIで指定したsama5d27_giantboard_sd1_uboot_defconfigはgiantboard-toolsから取り込む。

$ mkdir -p recipes-bsp/at91bootstrap/files
$ cp ~/giantboard-tools/patches/at91bootstrap/sama5d27_giantboard_sd1_uboot_defconfig recipes-bsp/at91bootstrap/files

u-boot

u-bootは下記のコミットにGiant Board対応のための修正を加えている。

commit 003ba1b96b7ee1bffb81b31d62346d676b4afddc (HEAD -> master, origin/master, origin/HEAD)
Author: Christopher Alessandro <calessandro0827@gmail.com>
Date:   Tue Jul 14 14:23:19 2020 -0700

    Add command to force refresh ca-ceritificates to resolve SSL check errors.

レシピをrecipes-bsp/u-boot/u-boot-at91_2019.07.bbとして下記の内容で作成する。

DESCRIPTION = "U-Boot - the Universal Boot Loader"
HOMEPAGE = "http://www.denx.de/wiki/U-Boot/WebHome"
SECTION = "bootloaders"
PROVIDES = "u-boot virtual/bootloader"

inherit deploy

LICENSE = "GPLv2+"
LIC_FILES_CHKSUM = "file://README;beginline=1;endline=22;md5=9915e8cb100eb5dbb366010a0f10296c"

SRCREV = "e5aee22e4be75e75a854ab64503fc80598bc2004"

PV = "v2019.07-at91+git${SRCPV}"

DEPENDS += "bison-native flex-native"

COMPATIBLE_MACHINE = '(giantboard)'

UBRANCH = "master"

SRC_URI = "git://github.com/u-boot/u-boot;protocol=https;branch=${UBRANCH} \
       file://giantboard-fixes.patch \
       file://sama5d27_giantboard_mmc_defconfig \
       file://at91-sama5d27_giantboard.dts \
"

S = "${WORKDIR}/git"

PACKAGE_ARCH = "${MACHINE_ARCH}"

EXTRA_OEMAKE = 'CROSS_COMPILE=${TARGET_PREFIX} CC="${TARGET_PREFIX}gcc ${TOOLCHAIN_OPTIONS}" HOSTCC="${BUILD_CC} ${BUILD_CFLAGS} ${BUILD_LDFLAGS}"'

do_unpack_append() {
    bb.build.exec_func('do_copy_defconfig', d)
}

do_copy_defconfig() {
    cp ${WORKDIR}/sama5d27_giantboard_mmc_defconfig ${S}/configs
    cp ${WORKDIR}/at91-sama5d27_giantboard.dts ${S}/arch/arm/dts/
}

do_configure() {
    oe_runmake distclean
    oe_runmake sama5d27_giantboard_mmc_defconfig
}

do_compile() {
    oe_runmake
}

do_deploy () {
    install -d ${DEPLOYDIR}
    install ${S}/u-boot.bin ${DEPLOYDIR}/u-boot.bin
    cat << 'EOF' > ${DEPLOYDIR}/uEnv.txt
# Main Device Tree
fdtfile=/at91-sama5d27_giantboard.dtb
optargs=atmel.pm_modes=standby,ulp1 giantboard.disable_charging=1   
EOF
}
addtask deploy before do_build after do_compile

レシピの構造はat91bootstrapと大きな違いはないので、個別の解説は省略する。

1つだけ特筆するとすれば、do_deployではuEnv.txtを生成している。

変更点

u-bootのGiant Board向けのファイルは下記の通り。

  • giantboard-fixes.patch
  • sama5d27_giantboard_mmc_defconfig
  • at91-sama5d27_giantboard.dts

これらのファイルもgiantboard-toolsから取り込む。

$ mkdir -p recipes-bsp/u-boot/files
$ cp ~/giantboard-tools/patches/u-boot/giantboard-fixes.patch recipes-bsp/u-boot/files
$ cp ~/giantboard-tools/patches/u-boot/sama5d27_giantboard_mmc_defconfig recipes-bsp/u-boot/files
$ cp ~/giantboard-tools/patches/u-boot/at91-sama5d27_giantboard.dts recipes-bsp/u-boot/files

カーネルのレシピ

recipes-kernel/linuxlinux-giantboard_5.4.bbを作成する。

$ mkdir -p recipes-kernel/linux

カーネルは下記のコミットにGiant Boardサポートの修正を追加している。

commit 219d54332a09e8d8741c1e1982f5eae56099de85 (tag: v5.4)
Author: Linus Torvalds <torvalds@linux-foundation.org>
Date:   Sun Nov 24 16:32:01 2019 -0800

    Linux 5.4

recipes-kernel/linux/linux-giantboard_5.4.bbを下記の内容で作成する。

SECTION = "kernel"
DESCRIPTION = "Linux kernel for Microchip ARM SoCs (aka AT91)"
SUMMARY = "Linux kernel for Microchip ARM SoCs (aka AT91)"
LICENSE = "GPLv2"
LIC_FILES_CHKSUM = "file://COPYING;md5=bbea815ee2795b2f4230826c0c6b8814"

inherit kernel

FILESEXTRAPATHS_prepend := "${THISDIR}/${P}:"

SRCREV = "219d54332a09e8d8741c1e1982f5eae56099de85"

PV = "5.4+git${SRCPV}"

S = "${WORKDIR}/git"

KBRANCH = "master"
SRC_URI = "git://github.com/torvalds/linux.git;protocol=git;branch=${KBRANCH} \
       file://giantboard.patch \
       file://defconfig \
"

KERNEL_MODULE_AUTOLOAD += "atmel_usba_udc g_serial"
COMPATIBLE_MACHINE = "(giantboard)"

カーネルのレシピは少々特殊だが、基本的に必要な機能は実装済みであるのでinherit kernelカーネルのbbclassを継承すればよい。

変更点

カーネルの変更点は大まかに下記のようになっている。

  • at91-sama5d27_giantboard.dts
  • giantboard_defconfig
  • wilc1000ドライバの追加

今回はkernel.bbclassしか継承していないため、git diffで作成したパッチを適用できる。

$ cd ~/giantboard-tools/output/build/linux
$ git add .
$ git diff --cached > ~/giantboard.patch
$ cd ~/gb-dunfell/layers/meta-giantboard/recipes-kernel/linux/
$ mkdir files
$ mv ~/giantboard.patch ./files
$ cp ~/giantboard-tools/patches/kernel/giantboard_defconfig files/defconfig

.cfgフラグメントなどyoctoのカーネルレシピの機能を使用したい場合はkernel-yocto.bbclassを継承する必要がある。この場合パッチのチェックなども強化されるため、kernel-yoctoの作法に則ってレシピやパッチを作成する必要がある。 今回の様にカジュアルに変更を取り込むような場合は、kernel-yocto.bbclassを継承しないという方法も取ることができる。

ビルド

buildディレクトリに移動して、conf/local.confに下記の内容を追加する。

MACHINE = "giantboard"

bitbakeを実行する。

$ bitbake core-image-minimal

シリアルケーブルでボードとPCを接続しminicomを開いた状態で起動する。

Poky (Yocto Project Reference Distro) 3.1.4 giantboard /dev/ttyS0

giantboard login: root
root@giantboard:~# uname -a
Linux giantboard 5.4.0 #1 Mon Nov 25 00:32:01 UTC 2019 armv7l GNU/Linux

Linuxのログインまで行けた。。

まとめ

Yocto環境で新しいボードに対応する例を示した。

これができるようになると、Yoctoでの遊び方の幅が広がる。

kdb入門

はじめに

カーネルデバッガが便利だという噂は聞いたことがあったが、実際使ったことがなかった。 kdbをすこしだけ試してみたのでメモ。

kdb vs kgdb

Linuxカーネルデバッガを調べるとかならずkdbkgdbが引っかかる。

これらは別物で、どちらが良い悪いはなくてケースによって両方使うことになる。

kgdbのほうは名前にgdbが含まれていることからわかるようにGDBのサーバとなって、 デバッギーで動作させておいて、デバッガとなるホストのGDBからリモート接続してデバッグする。 こちらはカーネルがシンボル情報持っていて、アクセス可能なソースツリーが設定されていればソースレベルのデバッグが可能。

kdbの方はソースレベルでのデバッグはできないが、あらかじめ設定しておくとカーネルで何か発生した時に起動して、その時のメモリの状態とかいろいろ有用な情報を見ることができる。

今回はkdbの方を試す。

環境

Yocto Projectのmeta-raspberrypiで作成したraspberrypi4(32bit)環境で試す。

kdbを使用するにはカーネルを作成する際に下記のようなコンフィグが有効になっている必要がある。

CONFIG_KGDB=y
CONFIG_KGDB_KDB=y

ただ、meta-raspberrypiのカーネルではデフォルトで有効になっているので省略する。

デバッグ対象

今回も毎度お馴染みのhello-modをベースに作業する。

ビルド方法などはこちらを参照。

hello.cを下記の内容で作成する。今回はあらかじめバグを仕込んでおく。

#include <linux/module.h>

int init_module(void)
{
        int *p = NULL;

        printk("hello: init_module is called\n");
        *p = 10; // dereference the NULL pointer.
        return 0;
}

void cleanup_module(void)
{
}

MODULE_LICENSE("GPL");

これをビルドする。

$ source /opt/poky/3.1.4/environment-setup-aarch64-poky-linux
$ export KERNEL_SRC=${OECORE_TARGET_SYSROOT}/usr/src/kernel
$ make

デバッグする時に使うためにディスアセンブリも作っておく。

$ arm-poky-linux-gnueabi-objdump -S ./hello.ko

./hello.ko:     ファイル形式 elf32-littlearm


セクション .text.unlikely の逆アセンブル:

00000000 <init_module>:
   0:   e1a0c00d    mov ip, sp
   4:   e92dd800    push    {fp, ip, lr, pc}
   8:   e24cb004    sub fp, ip, #4
   c:   e52de004    push    {lr}        ; (str lr, [sp, #-4]!)
  10:   ebfffffe    bl  0 <__gnu_mcount_nc>
  14:   e3000000    movw    r0, #0
  18:   e3400000    movt    r0, #0
  1c:   ebfffffe    bl  0 <printk>
  20:   e3a00000    mov r0, #0
  24:   e3a0300a    mov r3, #10
  28:   e5803000    str r3, [r0]
  2c:   e89da800    ldm sp, {fp, sp, pc}

00000030 <cleanup_module>:
  30:   e1a0c00d    mov ip, sp
  34:   e92dd800    push    {fp, ip, lr, pc}
  38:   e24cb004    sub fp, ip, #4
  3c:   e52de004    push    {lr}        ; (str lr, [sp, #-4]!)
  40:   ebfffffe    bl  0 <__gnu_mcount_nc>
  44:   e3000000    movw    r0, #0
  48:   e3400000    movt    r0, #0
  4c:   ebfffffe    bl  0 <printk>
  50:   e89da800    ldm sp, {fp, sp, pc}

セクション .plt の逆アセンブル:

000000c0 <.plt>:
    ...

これでデバッグ対象は準備完了

実機側の準備

今回はUARTコンソールを使用する必要があるのであらかじめブートパーティションにあるconfig.txtに下記の設定をしておく。

enable_uart=1

bitbakeする時にlocal.confに下記を設定している場合は不要(というか設定済み)

ENABLE_UART = "1"

この状態でラズパイを起動して、PCからminicomなどのシリアルコンソールで接続する。

raspberrypi4 login: root
root@raspberrypi4:~#

下記のコマンドでカーネルデバッガが使用するシリアルポートを設定しておく。

$ echo ttyS0 > /sys/module/kgdboc/parameters/kgdboc

ラズパイを使用している場合、少しノウハウがありBTの有効/無効によって設定するポートが異なる。

BT有無 シリルポート
無効 ttyAMA0
有効 ttyS0

これはBTを使用している場合HCIにUARTを取られてしまうためで、デバイスツリーの設定で「dtoverlay=miniuart-bt」とかする場合はBT有効でもttyAMA0を指定することになる。

デバッグ

先ほど作成したバグ有りのhello.koをラズパイに書き込んでinsmodする。

# insmod ./hello.ko
[  452.512402] hello: loading out-of-tree module taints kernel.
[  452.518998] hello: init_module is called
[  452.523001] 8<--- cut here ---
[  452.526152] Unable to handle kernel NULL pointer dereference at virtual address 00000000
[  452.534374] pgd = 5a6455cf
[  452.537155] [00000000] *pgd=1710d003, *pmd=00000000
[  452.542127] Internal error: Oops: a06 [#1] SMP ARM

Entering kdb (current=0xd6d19e80, pid 425) on processor 1 Oops: (null)
due to oops @ 0xbf07a028
CPU: 1 PID: 425 Comm: insmod Tainted: G         C O      5.4.79-v7l #1
Hardware name: BCM2711
PC is at init_module+0x28/0x30 [hello]
LR is at irq_work_queue+0x14/0x2c
pc : [<bf07a028>]    lr : [<c035287c>]    psr: 60030013
sp : ef959d88  ip : 00000007  fp : ef959d94
r10: 00000124  r9 : ef959f30  r8 : 00000028
r7 : d71c0580  r6 : 00000000  r5 : d71c0640  r4 : bf07a000
r3 : 0000000a  r2 : 00000000  r1 : 00000000  r0 : 00000000
Flags: nZCv  IRQs on  FIQs on  Mode SVC_32  ISA ARM  Segment user
Control: 30c5383d  Table: 173a0fc0  DAC: 55555555
CPU: 1 PID: 425 Comm: insmod Tainted: G         C O      5.4.79-v7l #1
Hardware name: BCM2711
Backtrace:
[<c020e054>] (dump_backtrace) from [<c020e3b8>] (show_stack+0x20/0x24)
 r7:ffffffff r6:00000000 r5:60030193 r4:c12a5cb0
[<c020e398>] (show_stack) from [<c0b80d0c>] (dump_stack+0xd0/0x114)
[<c0b80c3c>] (dump_stack) from [<c0209c80>] (show_regs+0x1c/0x20)
 r9:c1205ff0 r8:00000005 r7:ffffe000 r6:0000000f r5:c1212728 r4:c12ef2cc
[<c0209c64>] (show_regs) from [<c0308d94>] (kdb_main_loop+0x470/0x984)
more>

プロンプトがmore>の時は出力が途中なので任意のキーを押すと次の行が表示される。qを押すと中断できる。

下記の状態になるとコマンド入力できる。

[1]kdb>

まず最初にバックトレースを確認する。

[1]gdb> bt
...(snip)...
pc[<c0217310>] (do_DataAbort) from [<c02019b4>] (__dabt_svc+0x54/0x80)
Exception stack(0xef959d38 to 0xef959d80)
9d20:                                                       00000000 00000000
9d40: 00000000 0000000a bf07a000 d71c0640 00000000 d71c0580 00000028 ef959f30
9d60: 00000124 ef959d94 00000007 ef959d88 c035287c bf07a028 60030013 ffffffff
 r8:00000028 r7:ef959d6c r6:ffffffff r5:60030013 r4:bf07a028
[<bf07a000>] (init_module [hello]) from [<c0203110>] (do_one_initcall+0x54/0x268)
[<c02030bc>] (do_one_initcall) from [<c02d2a6c>] (do_init_module+0x70/0x2a4)
 r7:d71c0580 r6:00000002 r5:d71c0640 r4:bf07c000
[<c02d29fc>] (do_init_module) from [<c02d53c0>] (load_module+0x2680/0x29fc)
 r6:00000002 r5:00000002 r4:d71c0540
[<c02d2d40>] (load_module) from [<c02d59b4>] (sys_finit_module+0xe0/0xf8)
 r10:0000017b r9:ef958000 r8:c02011c4 r7:0000017b r6:004cd9e8 r5:00000003
 r4:00000000
[<c02d58d4>] (sys_finit_module) from [<c0201000>] (ret_fast_syscall+0x0/0x28)
Exception stack(0xef959fa8 to 0xef959ff0)
9fa0:                   00000000 00000000 00000003 004cd9e8 00000000 becaec44
9fc0: 00000000 00000000 01335190 0000017b becaec88 004cb9d0 00000002 004df768
9fe0: becaec50 becaec40 004c6e0d b6eb1f12
 r6:01335190 r5:00000000 r4:00000000

do_DataAbortが発生しているいることがわかる。

次にレジスタを確認する。

[1]kdb> rd
r0: 00000000  r1: 00000000  r2: 00000000  r3: 0000000a  r4: bf07a000
r5: d71c0640  r6: 00000000  r7: d71c0580  r8: 00000028  r9: ef959f30
r10: 00000124  fp: ef959d94  ip: 00000007  sp: ef959d88  lr: c035287c
pc: bf07a028  f0: ??  f1: ??  f2: ??  f3: ??  f4: ??  f5: ??  f6: ??  f7: ??
fps: 00000000  cpsr: 60030013

pcbf07a028であることに注目。つまりこのアドレスでプログラムがアボードしている。

次にロードされているモジュールの一覧を確認する。

[1]kdb> lsmod
Module                  Size  modstruct     Used by
hello                  16384  0xbf07c000    1  (Loading) 0xbf07a000 [ ]
nfc                    81920  0xbf1ac280    0  (Live) 0xbf19e000 [ ]
bnep                   20480  0xbf071180    2  (Live) 0xbf06e000 [ ]
bluetooth             376832  0xbf337400    9  (Live) 0xbf2ec000 [ bluetooth ]
ecdh_generic           16384  0xbf04c1c0    1  (Live) 0xbf04a000 [ ecdh_generic ]
ecc                    36864  0xbf1051c0    1  (Live) 0xbf0ff000 [ ecc ]
ipv6                  466944  0xbf2d39c0   28  (Live) 0xbf279000 [ ]
hid_logitech_hidpp     40960  0xbf09d100    0  (Live) 0xbf096000 [ ]
rpivid_mem             16384  0xbf06b080    0  (Live) 0xbf069000 [ ]
joydev                 20480  0xbf057080    0  (Live) 0xbf054000 [ ]
brcmfmac              323584  0xbf15af80    0  (Live) 0xbf121000 [ ]
brcmutil               24576  0xbf03c040    1  (Live) 0xbf039000 [ brcmutil ]
sha256_generic         16384  0xbf051400    0  (Live) 0xbf04f000 [ ]
libsha256              20480  0xbf003000    1  (Live) 0xbf000000 [ libsha256 ]
cfg80211              688128  0xbf249300    1  (Live) 0xbf1d0000 [ cfg80211 ]
bcm2835_codec          36864  0xbf046080    0  (Live) 0xbf040000 [ ]
bcm2835_isp            32768  0xbf02e080    0  (Live) 0xbf029000 [ ]
bcm2835_v4l2           49152  0xbf024300    0  (Live) 0xbf01c000 [ ]
rfkill                 28672  0xbf11d200    4  (Live) 0xbf119000 [ rfkill rfkill rfkill ]
v4l2_mem2mem           32768  0xbf10f000    1  (Live) 0xbf10b000 [ v4l2_mem2mem ]
bcm2835_mmal_vchiq     28672  0xbf092000    3  (Live) 0xbf08e000 [ bcm2835_mmal_vchiq bcm2835_mmal_vchiq bcm2835_mmal_vchiq ]

プログラムがアボードしたアドレスに一番近いモジュールは、当然ながらhelloとなっている。

これらから読み取れること

hello.koは0xbf07a000にロードされている。

hello                  16384  0xbf07c000    1  (Loading) 0xbf07a000 [ ]

レジスタの状態からプログラムがアボートしたと思われるアドレス(pcのアドレス)はbf07a028

[1]kdb> rd
r0: 00000000  r1: 00000000  r2: 00000000  r3: 0000000a  r4: bf07a000
r5: d71c0640  r6: 00000000  r7: d71c0580  r8: 00000028  r9: ef959f30
r10: 00000124  fp: ef959d94  ip: 00000007  sp: ef959d88  lr: c035287c
pc: bf07a028  f0: ??  f1: ??  f2: ??  f3: ??  f4: ??  f5: ??  f6: ??  f7: ??
fps: 00000000  cpsr: 60030013

つまりhello.koの先頭から0x28進んだ場所だと推測される。

hello.koのアセンブリは次のようになっている。

00000000 <init_module>:
   0:   e1a0c00d    mov ip, sp
   4:   e92dd800    push    {fp, ip, lr, pc}
   8:   e24cb004    sub fp, ip, #4
   c:   e52de004    push    {lr}        ; (str lr, [sp, #-4]!)
  10:   ebfffffe    bl  0 <__gnu_mcount_nc>
  14:   e3000000    movw    r0, #0
  18:   e3400000    movt    r0, #0
  1c:   ebfffffe    bl  0 <printk>
  20:   e3a00000    mov r0, #0
  24:   e3a0300a    mov r3, #10
  28:   e5803000    str r3, [r0]    <-------------ここ
  2c:   e89da800    ldm sp, {fp, sp, pc}

つまり28: e5803000 str r3, [r0]の行で問題が発生していると推測することができる。

アセンブリと直近レジスタの値を当てはめていくと下記のように読める。

r0: 00000000  r1: 00000000  r2: 00000000  r3: 0000000a  r4: bf07a000
r5: d71c0640  r6: 00000000  r7: d71c0580  r8: 00000028  r9: ef959f30
r10: 00000124  fp: ef959d94  ip: 00000007  sp: ef959d88  lr: c035287c

  20:   e3a00000    mov r0, #0   // r0に0を代入
  24:   e3a0300a    mov r3, #10  // r3に10を代入
  28:   e5803000    str r3, [r0] // r0が指し示すアドレスにr3を代入

つまり0番地に10を書き込もうとしている。

hello.cに仕込んだバグが下記なので、

int init_module(void)
{
        int *p = NULL;

        printk("hello: init_module is called\n");
        *p = 10; // <-- 28:    e5803000    str r3, [r0] と一致する
        return 0;
}

*p = 10でプログラムがアボートしていることがわかる。

まとめ

実際にカーネルで問題が発生した場合はこんなに簡単に解析できることはないと思うが、 問題が発生した直後のスタックの状態やレジスタの状態が覗けるので、かなり便利だと思う。

また、今回は言及しなかったが、dumpallコマンドを実行すると、この時点で実行中の全てのプロセスの一覧およびすべてのCPUのスタックダンプ、dmesgの内容などが取れるため、この出力結果を保存しておいて机上でデバッグをする。なんてこともできる。

今度何かあったらkdbを使ってみようと思う。

参考

Using Serial kdb / kgdb to Debug the Linux Kernel

pandocのpdf出力にwkhtmltopdfを使ってみる

はじめに

pandocでマークダウンをPDF化する(LaTexなし)でwkhtmltopdfの存在を知った。

この記事ではWindows環境での話だったので、Ubuntu20.04で同様のことができないか確認した。

wkhtmltopdfのインストール

Stableなリリースはここで確認できる。

執筆時点では0.12.6なのでUbuntu20.04にインストールしてみる。

$ wget https://github.com/wkhtmltopdf/packaging/releases/download/0.12.6-1/wkhtmltox_0.12.6-1.focal_amd64.deb
$ sudo gdebi wkhtmltox_0.12.6-1.focal_amd64.deb

pandocでpdfを出力

pandocでpdfを出力してみる。

コマンドはPandocの比較的簡単なインストール方法を参考に下記のようにする。

どちらも同じ結果になるらしい。

$ pandoc hoge.md -s -o hoge.pdf -t html5 
$ pandoc hoge.md -s -o hoge.pdf --pdf-engine=wkhtmltopdf

pdfが出力できた。

cssの設定

pdfの見た目がそっけないので、cssを設定する。

$ pandoc hoge.md -s -o hoge.pdf --pdf-engine=wkhtmltopdf -c $HOME/.pandoc/github.css

ちょっと素敵になった。

まとめ

wkhtmltopdfはUbuntuでも使える。

YoctoのSDKでOut-of-treeのカーネルモジュールをビルドする

はじめに

bitbake meta-toolchain-c populate_sdkで作成したSDKを使用してOut-of-treeのカーネルモジュール(ドライバ)をビルドする時に少しハマったのでメモしておく。

SDKカーネルソースを含める

作成されたSDKに確実にカーネルソースコードを含めるためにlocal.confに下記を追加する。

TOOLCHAIN_TARGET_TASK_append = " kernel-devsrc"

SDKを作成する

カーネルカーネルモジュールをビルドしたいだけであればmeta-toolchainでも問題ない。

$ bitbake meta-toolchain

もちろんイメージをpopulate_sdkしても構わない。

$ bitbake core-image-base -c populate_sdk

環境設定

SDKを使用できるようにするために環境変数を設定する。

また、カーネルソースの場所を簡単にアクセスできるようにするために、KERNEL_SRCという環境変数を定義しておく。

$ source /opt/poky/3.1.4/environment-setup-aarch64-poky-linux
$ export KERNEL_SRC=${OECORE_TARGET_SYSROOT}/usr/src/kernel

カーネルモジュールをビルドするための準備

Out-of-treeのカーネルモジュールをビルドするためには下記のようにあらかじめカーネルのソースツリーでmake scriptsなどを実行しておく必要がある。

SDKにインストールされたカーネルのソースツリーの所有者がrootになっているため下記のようにする。

$ cd ${KERNEL_SRC}
$ sudo chown -R ${USER} .
$ make scripts
$ make prepare
$ sudo chown -R root .

この時make scriptsmake prepareを直接sudoで実行すると、実行時の環境変数が切り替わってしまうためうまくいかない。

作成するモジュール

Yoctoターゲットのセルフコンパイル環境でカーネルモジュールをビルドの回でもお世話になったhello-modをここでも試してみる。

~/helloにソースとMakefileを作成する。

# mkdir ~/hello
# cd hello

~/hello/hello.cを次の内容で作成する。

#include <linux/module.h>

int init_module(void)
{
    printk("Hello World!\n");
    return 0;
}

void cleanup_module(void)
{
    printk("Goodbye Cruel World!\n");
}

MODULE_LICENSE("GPL");

~/hello/Makefileを次の内容で作成する。

obj-m := hello.o

SRC := $(shell pwd)

all:
    $(MAKE) -C $(KERNEL_SRC) M=$(SRC)

modules_install:
    $(MAKE) -C $(KERNEL_SRC) M=$(SRC) modules_install

clean:
    rm -f *.o *~ core .depend .*.cmd *.ko *.mod.c
    rm -f Module.markers Module.symvers modules.order
    rm -rf .tmp_versions Modules.symvers

ビルド

$ make

まとめ

YoctoのSDKでOut-of-treeをビルドする時は注意が必要。

参考

meta-clangの使い方

はじめに

meta-clangの使い方を調べた。

meta-clangはbitbakeでのパッケージのビルドやSDKgccの代わりにclangを使えるようにするためのレイヤ。

meta-clangを使う

既存のビルド環境にmeta-clangを組み込むには下記のようにする。

$ git clone https://github.com/kraj/meta-clang -b dunfell

ビルド対象に組み込むにはbitbake-layersを実行する。

$ bitbake-layers add-layers ../layers/meta-clang

デフォルトのツールチェインを切り替える

bitbakeで使用するデフォルトのツールチェインをgccからclangに変更するにはlocal.confで下記を設定する。

TOOLCHAIN = "clang"

一部、gccじゃないとうまくビルドできないようなパッケージは、meta-clang/conf/nonclangable.confで次のように設定されている(抜粋)。

# __ai uint8x16_t vabdq_u8(uint8x16_t __p0, uint8x16_t __p1) {
TOOLCHAIN_pn-firefox = "gcc"
TOOLCHAIN_pn-gcc = "gcc"
TOOLCHAIN_pn-gcc-runtime = "gcc"
TOOLCHAIN_pn-gcc-sanitizers = "gcc"
TOOLCHAIN_pn-glibc = "gcc"
TOOLCHAIN_pn-glibc-initial = "gcc"
TOOLCHAIN_pn-glibc-locale = "gcc"
TOOLCHAIN_pn-glibc-mtrace = "gcc"
TOOLCHAIN_pn-glibc-scripts = "gcc"
TOOLCHAIN_pn-glibc-testsuite = "gcc"
TOOLCHAIN_pn-grub = "gcc"
TOOLCHAIN_pn-grub-efi = "gcc"

追加で設定したい場合はこの書式に則ってlocal.confに設定を追加する。

レシピ毎のツールチェインを切り替える

デフォルトのツールチェインは変更せずにレシピ毎に使用するツールチェインをclangに設定したい場合は、それぞれのレシピで下記の内容を設定する。

TOOLCHAIN = "clang"

SDKにclangを含める

SDKにclangを含めるにはlocal.confに下記を設定する。

CLANGSDK = "1"
# Adding clang in generated SDK toolchain

clang based cross compiler is not included into the generated SDK using `bitbake meta-toolchain` or
`bitbake -cpopulate_sdk <image>` if clang is expected to be part of SDK, add `CLANGSDK = "1"`
in `local.conf`

CLANGSDKに関するREADME.mdの説明は下記のようになっている。

# Removing clang from generated SDK toolchain

clang based cross compiler is automatically included into the generated SDK using `bitbake meta-toolchain` or
`bitbake -cpopulate_sdk <image>` in circumstanced where clang is not expected to be part of SDK, then reset `CLANGSDK`
variable in `local.conf`

```shell
CLANGSDK = ""
```

clangをSDKに含めないようにするにはCLANGSDK = ""として値をリセットするようにという説明しか無いため、 最初読んだ時今いち理解ができなかった。

これはもともとCLANGSDKのデフォルトが1になっていて、SDKに含まれることが前提だったためこのような書き方になっている。

デフォルトは826feacfeb64e2b4fe3ff50153a8ba4fa4bfff76のコミットで0に修正されている。

commit 826feacfeb64e2b4fe3ff50153a8ba4fa4bfff76
Author: Khem Raj <raj.khem@gmail.com>
Date:   Thu Apr 2 14:21:57 2020 -0700

    layer.conf: Mark CLANGSDK 0 by default
    
    This ensures that clangsdk does not inserts itself unless user asks for
    
    Fixes #234
    
    Signed-off-by: Khem Raj <raj.khem@gmail.com>

diff --git a/.drone.yml b/.drone.yml
index 548873f..7f14e38 100644
--- a/.drone.yml
+++ b/.drone.yml
@@ -40,6 +40,7 @@ steps:
       - echo IMAGE_CLASSES += \"testimage testsdk\" >> conf/local.conf
       - echo INHERIT += \"report-error rm_work blacklist\" >> conf/local.conf
       - echo TOOLCHAIN = \"clang\" >> conf/local.conf
+      - echo CLANGSDK = \"1\" >> conf/local.conf
       - echo IMAGE_INSTALL_append = \" clang \" >> conf/local.conf
       - echo XZ_DEFAULTS = \"--threads=8\" >> conf/local.conf
       - echo LICENSE_FLAGS_WHITELIST_append = \" commercial non-commercial\" >> conf/local.conf
diff --git a/conf/layer.conf b/conf/layer.conf
index f3e3fcc..792ba40 100644
--- a/conf/layer.conf
+++ b/conf/layer.conf
@@ -26,8 +26,8 @@ PREFERRED_PROVIDER_llvm-native = "clang-native"
 PREFERRED_PROVIDER_nativesdk-llvm = "nativesdk-clang"
 INHERIT += "clang"
 
-# include clang in SDK
-CLANGSDK ??= "1"
+# Do not include clang in SDK unless user wants to
+CLANGSDK ??= "0"
 
 LLVMVERSION = "10.0.0"

この時、ドキュメントは更新されなかったためこのような状態になっている。

masterを見ると修正されていたのでblameしてみると、下記のコミットで修正されたらしい。

commit 2730fe62d5d0af59d49bc2d8fe703753331aacf2
Author: Khem Raj <raj.khem@gmail.com>
Commit: Khem Raj <raj.khem@gmail.com>

    README: Update documentation
    
    Few knobs have changed and has not been yet reflected in README
    
    Signed-off-by: Khem Raj <raj.khem@gmail.com>

SDKにクロスコンパイラが含まれない場合

既にSDKをビルドしてしまった状態で、local.confにCLANGSDK = "1"を設定した場合、SDKにclangのクロスコンパイラが含まれない場合がある。

実際にSDKにclangのクロスコンパイラを入れる処理はpackagegroup-cross-canadianというレシピが行っているが、 CLANGSDKの値の変更はbitbake時に検出しないため、このパッケージをcleanする必要があるようだ。

確実にCLANGSDKの変更を反映するには下記のようにコマンドを実行する。

$ bitbake packagegroup-cross-canadian -c cleanall
$ bitbake core-image-base -c populate_sdk -f

SDKをインストールする際の注意点

build/tmp/deploy/sdk以下に生成されるインストーラではインストール先のディレクトリ変更することができるが、2020/12/13時点でWhen setting up sdk relocating error #119の問題が解決されてないため、デフォルトのままインストールするほうが無難。

インストールしたSDKのclangを使う

インストールしたSDKを使用するには最初に環境変数を設定する必要がある。

$ source /opt/poky/3.1.4/environment-setup-aarch64-poky-linux

Cソースのビルド

今回は適当にHello worldを用意する。下記の内容でhello.cを作成する。

#include <stdio.h>

int main(void)
{
    printf("hello world from C\n");
    return 0;
}

ターゲット向けにCのソースファイルをビルドするには下記のようにする。

$ ${CLANGCC} hello.c -ohello -O2

ターゲット用のビルドを行うためのコマンドラインCLANGCC環境変数に設定されている。

C++ソースのビルド

こちらもC++Hello world。下記の内容でhello.cppを作成する。

#include <iostream>

int main(void)
{
    std::cout << "hello world" << std::endl;
    return 0;
}

ターゲット向けにC++のソースファイルをビルドするには下記のようにする。

$ ${CLANGCXX} hello.cpp -ohello -O2

autotoolsベースのビルド

Makefile.amを以下の内容で作成する。

bin_PROGRAMS=hello
hello_SOURCES=hello.cpp

configure.acを以下の内容で作成する。

AC_INIT(hello.cpp)
AM_INIT_AUTOMAKE(hello,0.1)
AC_PROG_CC
AC_PROG_CXX
AC_PROG_INSTALL
AC_OUTPUT(Makefile)

次のような配置にする。

.
├── Makefile.am
├── configure.ac
└── hello.cpp

下記のように環境変数を設定する。

$ export CC=${CLANGCC}
$ export CXX=${CLANGCXX}
$ export CPP=${CLANGCPP}

次のコマンドを実行してビルドする。

$ touch NEWS README AUTHORS ChangeLog
$ autoreconf -i
$ ./configure --host=${CROSS_COMPILE}
$ make

Makefileを確認するとclangでビルドされていることが分かる。

   ...(snip)...
CC = aarch64-poky-linux-clang  -march=armv8-a+crc -fstack-protector-strong  -D_FORTIFY_SOURCE=2 -Wformat -Wformat-security -Werror=format-security -mlittle-endian --sysroot=/opt/poky/3.1.4/sysroots/aarch64-poky-linux
CCDEPMODE = depmode=gcc3
CFLAGS =  -O2 -pipe -g -feliminate-unused-debug-types 
CPPFLAGS = 
CXX = aarch64-poky-linux-clang++  -march=armv8-a+crc -fstack-protector-strong  -D_FORTIFY_SOURCE=2 -Wformat -Wformat-security -Werror=format-security -mlittle-endian --sysroot=/opt/poky/3.1.4/sysroots/aarch64-poky-linux
   ...(snip)...

CMakeベースのビルド

CMakeLists.txtを下記の内容で作成する。

cmake_minimum_required(VERSION 3.10)
project("hello")

add_executable(${PROJECT_NAME}
    ${PROJECT_SOURCE_DIR}/hello.cpp
)

次のような配置にする。

.
├── CMakeLists.txt
└── hello.cpp

下記のように環境変数を設定する。

$ export CC=${CLANGCC}
$ export CXX=${CLANGCXX}
$ export CPP=${CLANGCPP}

次のコマンドを実行してビルドする。

$ mkdir build && cd build
$ cmake ..
$ make

ccmakeなどで確認するとclangでビルドされていることが分かる。

CMAKE_CXX_COMPILER               /opt/poky/3.1.4/sysroots/x86_64-pokysdk-linux/usr/bin/aarch64-poky-linux/aarch64-poky-linux-clang++
CMAKE_CXX_COMPILER_AR            /opt/poky/3.1.4/sysroots/x86_64-pokysdk-linux/usr/bin/aarch64-poky-linux/aarch64-poky-linux-llvm-ar
CMAKE_CXX_COMPILER_ARG1            -march=armv8-a+crc -fstack-protector-strong  -D_FORTIFY_SOURCE=2 -Wformat -Wformat-security -Werror=format-security -mlittle-endian --sysroot=/opt/poky/3.1.4/sysroots/aarch64-poky-linux

まとめ

meta-clangの使い方をまとめた。

もともと存在は知っていたが、実際に動作を確認したのは初めてだった。

Yoctoでのアプリケーション開発でclangが使用できると新し目のC++の機能を使ったりもできるので便利かもしれない。

meta-qt6を試す

はじめに

Qt 6 in OpenEmbedded and Yoctoが気になったので試してみる。

ここを見る限りまだdevっぽい。

meta-qt6を覗いてみる

layer.conf

...(snip)...

LAYERDEPENDS_qt6-layer = "core"

LAYERSERIES_COMPAT_qt6-layer = "zeus dunfell gatesgarth"
...(snip)...

依存はcoreのみ。

互換性はzeusdunfellgatesgarth。まぁ妥当なところか。

recipes-qt

treeコマンドの結果(patchは除外)

.
├── meta
│   └── meta-toolchain-qt6.bb
├── packagegroups
│   ├── nativesdk-packagegroup-qt6-toolchain-host.bb
│   ├── packagegroup-qt6-modules.bb
│   └── packagegroup-qt6-toolchain-target.bb
└── qt6
    ├── qt3d_git.bb
    ├── qt5compat_git.bb
    ├── qt6-git.inc
    ├── qt6.inc
    ├── qtbase
    ├── qtbase_git.bb
    ├── qtcoap_git.bb
    ├── qtdeclarative_git.bb
    ├── qtgraphicaleffects_git.bb
    ├── qtimageformats_git.bb
    ├── qtmqtt_git.bb
    ├── qtnetworkauth_git.bb
    ├── qtopcua_git.bb
    ├── qtquick3d
    ├── qtquick3d_git.bb
    ├── qtquickcontrols2_git.bb
    ├── qtquicktimeline_git.bb
    ├── qtserialbus_git.bb
    ├── qtserialport_git.bb
    ├── qtshadertools_git.bb
    ├── qtspeech_git.bb
    ├── qtsvg_git.bb
    ├── qttools_git.bb
    ├── qttranslations_git.bb
    ├── qtvirtualkeyboard_git.bb
    ├── qtwayland
    ├── qtwayland_git.bb
    └── qtwebsockets_git.bb

meta-qt5にはあったqtsmarthomeなどのexamplesは今のところ無い。これから生えてくるのだろうか。

QPAプラットフォームの設定

QtをYoctoで使用するといつもついてくるQPAプラットフォームの指定。結構分かりづらいのでいつも何かしら手を入れることになる。

qtbase_git.bbを覗いてみる。

# Default platform plugin
QT_QPA_DEFAULT_PLATFORM ?= "${@bb.utils.contains('DISTRO_FEATURES', 'x11', 'xcb', \
    bb.utils.contains('PACKAGECONFIG', 'gles2', 'eglfs', 'linuxfb', d), d)}"

...(snip)...

EXTRA_OECMAKE_append_class-target = "\
    -DFEATURE_rpath=OFF \
    -DQT_QPA_DEFAULT_PLATFORM=${QT_QPA_DEFAULT_PLATFORM} \
    -DQT_AVOID_CMAKE_ARCHIVING_API=ON \
"

今回ここが秀逸になっていて、DISTRO_FEATURESにx11が含まれている場合がデフォルトがxcbになっていて、 含まれない場合はqtbaseのPACKAGECONFIGにgles2があればeglfs、なければlinuxfbが設定されるようになっている。

meta-qt5までの環境ではデフォルトがxcbから変更できなかった(はず)ので、実行環境で環境変数を下記のように設定するかアプリケーション実行時に-platform=eglfsとかする必要があった。

export QT_QPA_PLATFORM=eglfs
export QT_QPA_EGLFS_INTEGRATION=eglfs_kms_egldevice 

今回はレシピかlocal.confで適切に設定すれば、実行時にQPA_PLATFORMをいちいち気にしないで良いようになっている。

構築手順

ソース取得

下記のコマンドでソースを取得する。

$ mkdir -p rpi-gatesgarth/layers
$ cd rpi-gatesgarth/layers
$ git clone git://git.yoctoproject.org/poky.git -b gatesgarth
$ git clone git://git.yoctoproject.org/meta-raspberrypi -b gatesgarth

meta-qt6の取得

$ git clone git://code.qt.io/yocto/meta-qt6.git

今回構築した時のハッシュは下記の通り。

meta
meta-poky
meta-yocto-bsp       = "gatesgarth:943ef2fad8428f002850e3655a3312e13d0dcb2c"
meta-raspberrypi     = "gatesgarth:09a3c11696c75593f8e475da5dfb401016c6aaca"
meta-qt6             = "dev:aaa3b77573f448c14a9526e85813b2e8c8ca6956"

環境変数設定

$ source layers/poky/oe-init-build-env build

自動的にビルドディレクトリに移動される。 これで、bitbake関連のツールが使用可能になる。

レイヤ追加

下記のコマンドでビルド対象にmeta-raspberrypiを追加する。

$ bitbake-layers add-layer ../layers/meta-raspberrypi
$ bitbake-layers add-layer ../layers/meta-qt6

local.confの修正

MACHINEをraspberrypi4-64に設定する。

MACHINE = "raspberrypi4-64"
DL_DIR ?= "${TOPDIR}/../downloads"

# systemd
DISTRO_FEATURES_append = " systemd pam"
VIRTUAL-RUNTIME_init_manager = "systemd"
DISTRO_FEATURES_BACKFILL_CONSIDERED = "sysvinit"
VIRTUAL-RUNTIME_initscripts = ""

# qt6 modules
IMAGE_INSTALL_append = " packagegroup-qt6-modules"

# Default QPA platform is eglfs-kms-integration
PACKAGECONFIG_append_pn-qtbase = " kms"
DISTRO_FEATURES_remove = "x11"

IMAGE_INSTALL_append = " ttf-vlgothic"

今回はQt6を動かすためマウスやキーボード、ディスプレイは接続するので、 ネットワークやUARTなどヘッドレスに便利なものは特に追加していない。

ビルド

core-image-baseをビルドする。

$ bitbake core-image-base

ビルド結果

core-image-base-raspberrypi4-64.manifestを確認する。

...(snip)...
libqt6coap-plugins cortexa72 6.0.0
libqt6coap-qmlplugins cortexa72 6.0.0
libqt6coap6 cortexa72 6.0.0
libqt6core5compat-plugins cortexa72 6.0.0
libqt6core5compat-qmlplugins cortexa72 6.0.0
libqt6core5compat6 cortexa72 6.0.0
libqt6mqtt-plugins cortexa72 6.0.0
libqt6mqtt-qmlplugins cortexa72 6.0.0
libqt6mqtt6 cortexa72 6.0.0
libqt6networkauth-plugins cortexa72 6.0.0
libqt6networkauth-qmlplugins cortexa72 6.0.0
libqt6networkauth6 cortexa72 6.0.0
libqt6opcua-plugins cortexa72 6.0.0
libqt6opcua-qmlplugins cortexa72 6.0.0
libqt6opcua6 cortexa72 6.0.0
libqt6serialbus-plugins cortexa72 6.0.0
libqt6serialbus-qmlplugins cortexa72 6.0.0
libqt6serialbus6 cortexa72 6.0.0
libqt6serialport-plugins cortexa72 6.0.0
libqt6serialport-qmlplugins cortexa72 6.0.0
libqt6serialport6 cortexa72 6.0.0
libqt6shadertools-plugins cortexa72 6.0.0
libqt6shadertools-qmlplugins cortexa72 6.0.0
libqt6shadertools6 cortexa72 6.0.0
libqt6texttospeech-plugins cortexa72 6.0.0
libqt6texttospeech-qmlplugins cortexa72 6.0.0
libqt6texttospeech6 cortexa72 6.0.0
libqt6virtualkeyboard-plugins cortexa72 6.0.0
libqt6virtualkeyboard-qmlplugins cortexa72 6.0.0
libqt6virtualkeyboard6 cortexa72 6.0.0
libqt6websockets-plugins cortexa72 6.0.0
libqt6websockets-qmlplugins cortexa72 6.0.0
libqt6websockets6 cortexa72 6.0.0
...(snip)...
packagegroup-qt6-modules raspberrypi4_64 1.0
...(snip)...
qt3d cortexa72 6.0.0
qt3d-plugins cortexa72 6.0.0
qt3d-qmlplugins cortexa72 6.0.0
qtbase cortexa72 6.0.0
qtbase-plugins cortexa72 6.0.0
qtbase-qmlplugins cortexa72 6.0.0
qtdeclarative cortexa72 6.0.0
qtdeclarative-plugins cortexa72 6.0.0
qtdeclarative-qmlplugins cortexa72 6.0.0
qtgraphicaleffects cortexa72 6.0.0
qtgraphicaleffects-plugins cortexa72 6.0.0
qtgraphicaleffects-qmlplugins cortexa72 6.0.0
qtimageformats cortexa72 6.0.0
qtimageformats-plugins cortexa72 6.0.0
qtimageformats-qmlplugins cortexa72 6.0.0
qtquick3d cortexa72 6.0.0
qtquick3d-plugins cortexa72 6.0.0
qtquick3d-qmlplugins cortexa72 6.0.0
qtquickcontrols2 cortexa72 6.0.0
qtquickcontrols2-plugins cortexa72 6.0.0
qtquickcontrols2-qmlplugins cortexa72 6.0.0
qtquicktimeline cortexa72 6.0.0
qtquicktimeline-plugins cortexa72 6.0.0
qtquicktimeline-qmlplugins cortexa72 6.0.0
qtsvg cortexa72 6.0.0
qtsvg-plugins cortexa72 6.0.0
qtsvg-qmlplugins cortexa72 6.0.0
qttools cortexa72 6.0.0
qttools-plugins cortexa72 6.0.0
qttools-qmlplugins cortexa72 6.0.0
qttranslations cortexa72 6.0.0
qttranslations-plugins cortexa72 6.0.0
qttranslations-qmlplugins cortexa72 6.0.0
qtwayland cortexa72 6.0.0
qtwayland-plugins cortexa72 6.0.0
qtwayland-qmlplugins cortexa72 6.0.0

大体のところはイメージに追加されている。

書き込み

bmaptoolで書き込む。

$ sudo bmaptool copy core-image-base-raspberrypi4.wic.bz2 /dev/sdX

/dev/sdXは環境に応じて適宜読み替える。

Qtのアプリを作成してみる

今のところサンプルアプリケーションが見当たらないのでQtQuickの簡単なアプリを作成してみる。

SDKの作成

meta-toolchain-qt6SDKを作成する。

$ bitbake meta-toolchain-qt6

出来上がったSDKをインストールする。

$ cd cd tmp/deploy/sdk/
$ ./poky-glibc-x86_64-meta-toolchain-qt6-cortexa72-raspberrypi4-64-toolchain-3.2.1.sh

デフォルトの設定では/opt/poky/3.2.1にインストールされる

プログラムの作成

下記のような配置になるようにファイルを作成する。

$ tree .
.
├── CMakeLists.txt
├── main.cpp
├── main.qml
└── qthello.qrc

QMLファイル

下記の内容でmain.qmlを作成する。

import QtQuick 2.15
import QtQuick.Controls 2.15

ApplicationWindow {
    visible: true
    font.pointSize: 64
    
    Button {
        id: helloButton
        text: "Hello world"
        anchors.centerIn: parent

        onClicked: {
            Qt.quit()
        }
    }
}

QtQuickアプリケーションではQMLファイルによってGUIのレイアウトを定義する。

フォントサイズは少し大きめにしてみた。

C++ソースコード

下記の内容でmain.cppを作成する。

#include <QGuiApplication>
#include <QtQml/QQmlApplicationEngine>

int main(int argc, char *argv[]) {
    QGuiApplication app(argc, argv);
    QQmlApplicationEngine engine;
    engine.load("qrc:/main.qml");
    return app.exec();
}

QRCファイル

下記の内容でqthello.qrcを作成する。

<!DOCTYPE RCC>
<RCC version="1.0">
  <qresource prefix="/">
    <file>main.qml</file>
  </qresource>
</RCC>

リソース中のQMLを/main.qmlとして参照できるようにしている。

CMakeLists.txt

cmake_minimum_required(VERSION 3.10)
project(qthello)

find_package(Qt6 COMPONENTS Quick REQUIRED)
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTORCC ON)
QT6_ADD_RESOURCES(RESOURCES ${PROJECT_NAME}.qrc)

add_executable(${PROJECT_NAME} main.cpp ${RESOURCES})
target_link_libraries(${PROJECT_NAME} Qt6::Quick)

Qtプログラムのビルド

下記のコマンドでアプリケーションをビルドする。

$ source /opt/poky/3.2.1/environment-setup-cortexa72-poky-linux
$ mkdir build && cd build
$ cmake ..
$ make -j $(nproc)

qthelloが生成されるのでこれをLinuxイメージを書き込んだSDカードにコピーする。

SDカードがPCに認識されると/media/${USER}/の下にマウントされる。

$ sudo cp ./qthello /media/${USER}/root/home/root

プログラムの実行

アプリケーションを書き込んだSDでラズパイを起動する。

rootでログインし、下記のコマンドで実行する。

$ ./qthello

f:id:mickey_happygolucky:20201212004933j:plain
ラズパイ4での実行画面

無事、アプリケーションが実行される。ボタンをクリックすると終了する。

写真が汚いのはご愛嬌。

まとめ

まだ開発中のmeta-qt6を試してみた。

足りない部分はまだあるが、QPAプラットフォームの扱いに関しては明らかに向上していると思う。

meta-toolchain-qt6のSDKによるアプリケーション開発などについては、現時点でも十分試せるレベルなので、いろいろ遊べそうだ。

リリースが楽しみ。