LeavaTailの日記

LeavaTailの日記

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

help2man でmanページを自動生成する

概要

automakeから、help2manによるmanページを生成する手順を確認した。

はじめに

Linuxではmanコマンドによる利用者にオンラインマニュアルを提供する機構が設けられている。

ソフトウェアパッケージの開発者は、そのソフトウェアの使い方を記したmanページを含めて配布することが望ましい。
しかし、manページはの作成は更新する頻度も多く手間がかかる

help2manは、ソフトウェアの実行結果から標準的manページを自動生成するツールである。 www.gnu.org

また、help2manautomakeを併用することで、configureファイルの生成からmanページの生成までの手順を自動化することができる。

そこで、本記事ではautomakeを使ってhelp2manでmanページを生成する手順を紹介する。

help2manの出力結果

準備

下記のリポジトリを例にhelp2manを使ってmanページを生成する手順と、automakeによりmanページを自動生成する手順をまとめる。

github.com

dumpexfatは、FAT/exFATファイルシステムイメージから情報を取得することができるプログラムである。
このプログラムは複数のオプションの指定を許しており、以下のオプションをサポートしている。

オプション (long option) 概要
-c (--cluster=index) indexで指定したクラスタ番号のデータを出力する
-f (--force) 未確保のクラスタでも出力を許す
-o (--output) 結果を指定したファイルに出力する
-s (--sector=index) indexで指定したセクタ番号のデータを出力する
-v (--verbose) メッセージを冗長に出力する

help2man とは

公式サイトに詳細な説明があるので、詳しくはそちらを参照。

www.gnu.org

help2manはプログラムの--helpオプションと--versionオプションから簡単なmanページを作成するツールである。

help2manの概略図

このとき、--help--versionは、仕様に基づいた形式*1で記述することで精度の高いmanページを生成することができる。

一般的なmanページを作るときに必要となる--helpを下記に示す。

    Usage: <コマンド名> <コマンド書式>              ★ SYNOPSIS に出力される
    <コマンド概要>                                 ★ NAMEに出力される

    <コマンド説明>                                 ★ DESCRIPTIONに出力される

    Options:                                      ★ OPTIONSに出力される
      <オプション>     <オプション概要>

    Examples:                                     ★ EXAMPLEに出力される
      <例>    <実行例の説明>

    Report bugs to: <mailing-address>             ★ REPORTING BUGSに出力される

<>で記述したものに関しては、開発者がそのプログラムにあったものに修正する必要となる。
また、その他にmanページのセクションを追加したい場合は、<セクション名>: で追加することができる。

一般的なmanページを作るときに必要となる--versionを下記に示す。

    <command> <version>

    <著作権表示>                                   ★ COPYRIGHTに出力される

    Written by <作者>                             ★ AUTHORに出力される

manページを生成する

help2manの書式に則って、--help--versionを作成する。

    $ dumpexfat --help
    Usage: dumpexfat [OPTION]... FILE
    dump FAT/exFAT filesystem information.

      -c, --cluster=index   dump the cluster index after dump filesystem information.
      -f, --force   dump the cluster forcibly in spite of the non-allocated.
      -o, --output=file     send output to file rather than stdout.
      -s, --sector=index    dump the sector index after dump filesystem information.
      -v, --verbose Version mode.
      --help        display this help and exit.
      --version     output version information and exit.

    Examples:
      dumpexfat /dev/sda    dump FAT/exFAT filesystem information.
      dumpexfat -c 2 /dev/sda       dump FAT/exFAT filesystem information and cluster #2.

    $ dumpexfat --version
    dumpexfat 0.1

    Written by LeavaTail.

manページの作成に必要なものはこれで完了したので、help2manを実行する。

    $ help2man --no-discard-stderr -N -o dumpexfat.1 dumpexfat    

今回は下記のオプションに指定した。

  • --no-discard-stderr: stderrに出力された内容も使用する
  • -N: manページの末尾にTexinfoの情報を載せないようにする
  • -o: 出力先のファイルを指定する

automakeにhelp2manを組み込む

help2manの更新でも記述されているが、automakeと連携させる場合には、manページの依存関係にソースコードを指定することが望ましい。
そこで、このプロジェクトではMakefile.amに下記のターゲットを追加した。

    dumpexfat.1: dumpexfat$(EXEEXT)
        help2man --no-discard-stderr -N -o dumpexfat.1 ./dumpexfat

これで、automakeを実行することでhelp2manコマンドからmanページを生成できるようなMakefileが生成できるようになった。

おわりに

本記事は、automakeを使ってhelp2manでmanページを生成する手順を書き留めた。

help2manによって生成されたmanページは必要最低限度の情報が記述されているはずなので、とりあえずプロジェクトに組み込むとよいだろう。

変更履歴

  • 2020/08/10: 記事公開

*1:cpコマンドを参考にするとよい

GitHub Actionsで既存リポジトリのテストを自動化する

概要

既存のソフトウェアに対して、GitHub Actionによるテストの自動化を導入する。

はじめに

GitHubではパブリックリポジトリであれば GitHub Actions という機能が無料に使用することができる

GitHub Actions では、世の中のCI/CDサービスと同様にソフトウェアワークフロー (例えば、Masterブランチにマージする前に用意しておいたテストを流し、テストが通ったらマージを許可するなど) を簡単に自動化することができる。

GitHub Actionsは利用料金がお手軽(プライベートリポジトリでも利用時間が2000分/月)といった点や、GitHubのサービスの一つでもある点から高い統合性がみられる。
また、ソフトウェアワークフローのテンプレートも幅広いパターンが用意されており、導入の敷居が低いといった点もみられる。

そこで、本記事はソフトウェアワークフローが自動化されていないリポジトリに、GitHub Actionsを導入するまでの手順を書き留めた。

準備

下記のリポジトリを例にGitHub Actionsと連携させる手順をまとめる。

github.com

このプロジェクトは、様々なアーキテクチャ(x86/arm/arm64)用でLinuxカーネルをビルドするためのDockerイメージを管理している。
Dockerイメージの利用方法は、ホスト環境にあるlinuxトップディレクトリ(${PWD}/linux)をコンテナ内のワーキングディレクトリ(/work)と共有化して実行する。

Dockerfileからイメージを生成する。

$ docker build -t kbuild .

Dockerコンテナでカーネルをビルドする。

$ docker run --rm --name=kbuild -v ${PWD}/linux:/work -it kbuild make

これまで、このプロジェクトはテストを用意しておらず、実行のたびに問題が見つかっていた。

テストスクリプトの作成

GitHub Actionsと連携するにあたって、手動で実施するテストを設計・実装するところから始める。

このプロジェクトでは、Linuxカーネルのビルド環境を構築するDockerイメージであり、考えられるテストパターンは莫大に存在する。
今回は、LTSのカーネル *1 のみを対象として、Linuxカーネルがビルドできるかどうかを確認する。

テストの全体像は次に示す。

プロジェクトのソフトウェアワークフロー

  1. テストに必要なtarballをkernel.orgからダウンロードする。
  2. プロジェクトが提供しているDockerfileからDockerイメージを作成し、コンテナを起動させる。
  3. コンテナ内でカーネルがビルドできるかどうかテストする。

GitHub Actionsとの連携

プロジェクトのソフトウェアフローが決まったので、GitHub Actionsと連携させていく。

  1. プロジェクトトップページのActionsを選択する。
    GitHub Actionsボタン
  2. 今回はDocker imageのリポジトリなのでDocker imageの「Set up this workflow」を選択する。(自分のリポジトリに似たテンプレートがなければ、「set up a workflow yourself」で一から作成する。)
    workflowsのテンプレートを選択する
  3. templeteからworkflowsのひな形が生成されるので、必要に応じて修正する。
    workflowsのEdit画面

今回のプロジェクトでは、下記のようなworkflowを作成した。

name: Docker Image CI                          # ワークフローの名前。ページに表示される
        
on:                                            # masterブランチへのpush/PRを契機とする
  push:        
    branches: [ master ]        
  pull_request:        
    branches: [ master ]        
        
jobs:        
        
  build:                                       # ジョブのID
        
    runs-on: ubuntu-latest                     # ジョブを実行するマシンの環境
        
    steps:                                     # 一連のタスクとして下記を実行する
    - uses: actions/checkout@v2                # リポジトリからチェックアウトする 
    - name: Obtain LTS kernel                  
      run: ./tests/00_init.sh                  # tarballを取得・展開する
    - name: Execute the Docker Container
      run: ./tests/01_build.sh                 # Dockerコンテナの起動
    - name: Execute the Docker Container
      run: ./tests/10_allnobuild.sh            # カーネルのビルド            

上記のファイルを作成後、Masterブランチにコミットすると、作成したworkflowsが有効になる。
また、今回のworkflowはmasterへのpushを契機としているので、すぐに作成したテストが走る。

workflowsの結果確認

ジョブ(build)の結果を確認する。

ジョブの実行結果を確認する

今回は設定しなかったが、actions/upload-artifact@v1と指定すると成果物を残すことができる。(artifactsで確認可能? )

下記にGitHub Actionsと連携させた後の概要図は下記のようなものになった。

GitHub Actionsと連携後のソフトウェアワークフロー

  1. ユーザが対象のリポジトリに対してPushかPull Requestを投げたとき(on:以下の指定より)、GitHubがあらかじめ用意してあるjobを実行する。
  2. GithubUbuntu仮想マシンを立ち上げ(runs-on: ubuntu-latestより)、下記のタスクを実行していく。
  3. Ubuntu仮想マシン上にリポジトリをチェックアウトする。(actions/checkout@v2)
  4. 指定されたスクリプトを実行し、Docker Imageが適切かどうか検証する。

おわりに

本記事はソフトウェアワークフローが自動化されていないリポジトリGitHub Actionsを導入するところまでを書き留めた。

この記事はあくまで導入のためのフローを書き記したものであり、GitHub Actionsはまだ多彩の機能を兼ね備えている。
ドキュメントを充実しており、導入の敷居も低いのでGitHub Actionsを導入して、プロジェクトの品質を向上させていきたい。

変更履歴

  • 2020/07/13: 記事公開

参考

*1:2020年7月現在で 5.4.51, 4.19.132, 4.14.188, 4.9.230, 4.4.230

AutotoolsでMakefileを自動生成する

概要

独自のMakefileを用意している独自のソフトウェアに対して、AutotoolsによるMakefileの生成手順を確認した。

はじめに

Autotoolsはソフトウェアパッケージ開発ツールの一つである。

www.gnu.org

開発者は、configure.acMakefile.amに必要な情報を記載することで、configureと呼ばれるビルドに必要なライブラリのチェックやビルドに必要なファイルを自動生成するスクリプトファイルを生成することができる。

しかし、記述方式や生成方法が少々トリッキーなところもあり、一からautotoolsに必要なファイルを用意するとなると手間がかかる。 *1 幸いにも、Autotoolsを採用しているパッケージは多く存在するので、それらを参考にすることでとりあえず動作させることはできる。

本記事では、備忘録としてAutotoolsを使用するにあたって参考となるサイトの紹介と必要最低限度の手順を紹介する。

準備

今回は小規模ソフトウェアパッケージを想定として、Autotools の使い方をおさらいする。 説明を簡単にするために、対象を下記のようなディレクトリ構成から成るソフトウェアを対象とする。

Autotools導入予定のプロジェクトのディレクトリツリー

上記のソフトウェアはGitHub上に公開してあるのでそちらを参照すると理解しやすいと思う。 (Autotoolsを導入するまでの手順とコミットを対応させて説明しているため)

github.com

このソフトウェアは、プログラムソースコードマニュアルページ多言語翻訳リソースの三つから構成される。

  • プログラムソースコード: includesrcの配下にCプログラムファイルとヘッダファイルを管理する。 main.cがプログラムのメイン部分となっており、ここからsub.cにある関数を呼び出す。
  • マニュアルページ: manの配下にマニュアルページを管理する。
  • 多言語翻訳リソース: poの配下に翻訳可能な文字列と日本語の対応関係を管理する。 gettextを利用してローカライズする。

本記事では、このソフトウェアが下記の3つの要件を達成することを目標とする。

  1. ソースコードがビルドするためのMakefileを生成できること
  2. マニュアルページをインストールできるMakefileを生成できること
  3. gettextでローカライズが可能なMakefileを生成できること

また、今回は下記ツールのバージョンで確認している。

  • GNU automake 1.16.1
  • GNU gettext-tools 0.19.8.1

手順

CプログラムのソースコードをビルドできるMakefileの生成

github.com

  1. パッケージのトップディレクトリにMakefile.amを作成する。

     bin_PROGRAMS = test
     test_SOURCES = src/main.c src/sub.c
     AM_CPPFLAGS = -I$(top_srcdir)/include -DLOCALEDIR='"$(localedir)"' -DPACKAGE='"test"'
    

    bin_PROGRAMS = ターゲット: コンパイルによって生成されるバイナリファイルを指定する。
    ターゲット_SOURCE = ソースファイル: ターゲットを生成するために必要なファイルを指定する。 AM_CPPFLAGS = オプション: コンパイラに渡すオプションを指定する。

  2. autoscanコマンドを実行して、configure.scanファイルを生成する。

     $ autoscan
    
  3. configure.scanファイルをconfigure.acにリネームする。

     $  mv configure.scan configure.ac
    
  4. configure.acファイルを必要最低限の修正する。(末尾に「★」を付けた行が対象)

     #                                               -*- Autoconf -*-
     # Process this file with autoconf to produce a configure script.
    
     AC_PREREQ([2.69])
     AC_INIT([test], [1.0], [starbow.duster@gmail.com])    ★ パッケージ名, バージョン, バグレポート用アドレスを記入
     AM_INIT_AUTOMAKE([foreign subdir-objects])            ★ 不要ファイルの自動生成を防ぐforeignとサブディレクトリに対応するためのsubdir-objectsを追加
     AC_CONFIG_SRCDIR([config.h.in])                       ★ ソースコードのディレクトリをconfig.h.inに変更しておくとrenameされた場合にも対応できる
     AC_CONFIG_HEADERS([config.h])
    
     # Checks for programs.
     AC_PROG_CC
    
     # Checks for libraries.
    
     # Checks for header files.
     AC_CHECK_HEADERS([libintl.h locale.h])
    
     # Checks for typedefs, structures, and compiler characteristics.
    
     # Checks for library functions.
     AC_CHECK_FUNCS([setlocale])
    
     AC_CONFIG_FILES([Makefile])
     AC_OUTPUT    
    
  5. プリプロセッサ用のヘッダファイルの生成する。

     $ autoheader
    
  6. automake用のm4ファイルを生成する。

     $ aclocal
    
  7. Makefile.inを生成する。

     $ automake --add-missing
    
  8. configureスクリプトを生成する。

     $ autoconf
    
  9. Makefileの生成・ソフトウェアのビルド・パッケージのインストールできることを確認する。

     $ ./configure
     $ make
     $ make install
    

マニュアルページをインストールできるMakefileを生成

github.com

「CプログラムのソースコードをビルドできるMakefileの生成」との差分のみ説明する。

  1. Makefile.amファイルに以下を追記する。

     man_MANS = man/test.1
    

    man_MANS = ターゲット: マニュアルページのパスを指定する。

gettextでローカライズが可能なMakefileを生成

github.com

「CプログラムのソースコードをビルドできるMakefileの生成」との差分のみ説明する。

  1. configure.acファイルを修正する。(末尾に「★」を付けた行が対象)

    #                                               -*- Autoconf -*-
    # Process this file with autoconf to produce a configure script.
    
    AC_PREREQ([2.69])
    AC_INIT([test], [1.0], [starbow.duster@gmail.com])    
    AM_INIT_AUTOMAKE([foreign subdir-objects])            
    AC_CONFIG_SRCDIR([config.h.in])
    AC_CONFIG_HEADERS([config.h])
    AM_GNU_GETTEXT([external])                                ★ 出力ファイルを自動生成する
    AM_GNU_GETTEXT_VERSION(0.19)                              ★ gettextのバージョンを指定する
    
    # Checks for programs.
    AC_PROG_CC
    
    # Checks for libraries.
    
    # Checks for header files.
    AC_CHECK_HEADERS([libintl.h locale.h])
    
    # Checks for typedefs, structures, and compiler characteristics.
    
    # Checks for library functions.
    AC_CHECK_FUNCS([setlocale])
    
    AC_CONFIG_FILES([po/Makefile.in Makefile])               ★ po/Makefile.inも生成する
    AC_OUTPUT    
    
  2. gettextの基礎となるファイルを生成する。

     $ autopoint
    
  3. Makevars.templateをコピーしてMakevarsを作成する。

     $ cp po/Makevars.template po/Makevars
    
  4. 利用可能な言語を記述するファイルpo/LINGUASを作成する。

     # Set of available languages
     ja
    
  5. 翻訳が必要なファイルを記述するファイルpo/POTFILES.inを作成する。

     # List of source files which contain translatable strings.
     src/main.c
    
  6. 翻訳が必要なファイルを記述するファイルMakefile.amを作成する。

     SUBDIRS = po             ★ 対象としてpo以下も適応する
     bin_PROGRAMS = test
     man_MANS = man/test.1
     test_SOURCES = src/main.c src/sub.c include/func.h
     AM_CPPFLAGS = -I$(top_srcdir)/include
    
  7. 下記のコマンドを実行して、Makefileファイルを生成する。

     $ autoheader
     $ aclocal
     $ automake --add-missing
     $ autoconf
     $ ./configure
    
  8. 多言語翻訳用テンプレートのpo/test.potを生成する。(testの部分はバイナリ名と一致する)

     $ make
    
  9. 多言語翻訳用テンプレートのpo/test.potから日本語翻訳ファイルja.poを新規作成する。

     $ cp po/test.pot po/ja.po
    
  10. 日本語翻訳ファイルja.poを修正する。(末尾に「★」を付けた行が対象)

    # SOME DESCRIPTIVE TITLE.
    # Copyright (C) YEAR Free Software Foundation, Inc.
    # This file is distributed under the same license as the test package.
    # FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
    #
    #, fuzzy
    msgid ""
    msgstr ""
    "Project-Id-Version: test 1.0\n"
    "Report-Msgid-Bugs-To: starbow.duster@gmail.com\n"
    "POT-Creation-Date: 2020-06-27 01:09+0900\n"
    "PO-Revision-Date: 2020-06-27 01:10+0900\n"                      ★ このファイルの更新日時を記入する
    "Last-Translator: LeavaTail <starbow.duster@gmail.com>\n"        ★ このファイルを編集した人を記入する
    "Language-Team: ja\n"                                            ★ 翻訳後の言語担当チームを指定する
    "Language: ja\n"                                                 ★ 翻訳後の言語を指定する
    "MIME-Version: 1.0\n" 
    "Content-Type: text/plain; charset=UTF-8\n"                      ★ 文字コード(UTF-8が妥当)を指定する
    "Content-Transfer-Encoding: 8bit\n"
    
    #: src/main.c:14
    msgid "Hello,World!\n"
    msgstr "こんにちは、世界!\n"                                      ★ 日本語翻訳を記述する
    
  11. 多言語翻訳リソースの更新とコンパイル

    $  make -C po/ update-po
    

注意: make installでパッケージのインストールまでしないと、ローカライズされない。

補足

手順の効率化

autoreconfを使う

ここまで手順を紹介してきたが、その中でたくさんのコマンドが登場してきた。 configure.acMakefile.amからconfigureを生成するのに下記の4つのコマンドが必要になる。

  1. autoheader: configure.acからプリプロセッサ用のヘッダファイルの生成する。
  2. aclocal: configure.acからautomake用のm4を生成する。
  3. automake: Makefile.amなどからMakefile.inを生成する。
  4. autoconf: configure.acなどからconfigureを生成する。

autoreconfはこれらの処理を自動的に実行してくれるコマンドである。

OSSライセンスについて

OSS利用者にとって一番気を付けなければいけないことがOSSライセンスである。

  • GNU automakeはGPLv2+
  • GNU AutoconfはGPLv3
  • GNU gettext-toolsはGPLv3+

調べてみると、このあたりを言及している記事はいくつか見つけることができた。
それらによると、GNU automakeとGNU Autoconfを利用することに関しては、強制的にGPLライセンスにされることはないらしい。

どういうことかというと、autotoolsによって生成されるのは全てテキスト(スクリプト)ファイルであり、そこにはライセンス条項がきちんと記載されている。そう、例のGPL文が載っている…のに続いて、As a special exception...というパラグラフがある。

As a special exception to the GNU General Public License, if you distribute this file as part of a program that contains a configuration script generated by Autoconf, you may include it under the same distribution terms that you use for the rest of that program.

趣旨としては、autoconfで自動生成された*1ものであり、かつ、配布物の一部として含まれる場合は、ライセンスはGPLにしなくてもいいよということみたいだ。これなら、会社とかプロプラな組織でも安心してautotoolsが使えるね! https://kuenishi.hatenadiary.jp/entry/20080303/1204474761

一方でgettext-toolsを利用することに関しては、強制的にGPLライセンスにされると思われる。
以下のようなGPL違反の問題があげられている。

各国語対応のため,gettextパッケージ(GPL-2ライセンス)のソースコードの一部(libintl)を利用したにもかかわらず,該当の配布物(ライセンスを明確にしていないソースコード・非GPLソースコード,およびバイナリのみ公開のライブラリを含む)がGPLでの配布ではなかった https://gihyo.jp/news/report/2019/03/0401

注意: あくまでどちらも私の理解を記述しているだけなので、利用時にはライセンス表記やライセンサーに問い合わせることを推奨する。

おわりに

本記事では、小規模ソフトウェアパッケージを想定として、Autotoolsの手順を紹介した。

今回は、「ソースコードがビルドできる」「マニュアルページをインストールできる」「gettextでローカライズが可能」を目的としたため、最小限のconfigure.acMakefile.amのパラメータを変更した。 しかし、これらのファイルには多数のパラメータが存在しており、さまざまな状況下でも対応することができる。

実際に中/大規模のプロジェクトでAutotoolsを利用するときには、automakeの公式ページや他プロジェクトを確認しておくとよいだろう。

変更履歴

  • 2020/06/27: 記事公開

参考

Automake関連

Gettext関連

OSSライセンス関連

*1:cmakeやMesonなど別のツールに乗り換えるのも手ではあるが

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

関連記事

概要

x86_64用にLinuxカーネルをビルド、BuildRootで構築したルートファイルシステムを用意する。
また、QEMUでこれらをバイナリを動かし、ルートファイルシステムがマウントできることを確認した。

はじめに

x86Intelが開発したマイクロプロセッサの命令セットアーキテクチャであり、x86_64x86を64ビットに拡張した命令セットアーキテクチャである。
x86はパーソナルコンピュータやサーバなど幅広く使われている。

一方、プロセッサエミュレータQEMUは、ブートローダの再設定などせずにカーネルramdiskを直接ロードすることができる。 そこで、プロセッサエミュレータでもあるQEMUを用いてx86_64用にビルドされたLinuxカーネルを動かす方法を解説する。

本記事では、以下の動作をする環境を目指す。

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

環境構成

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

実施環境

起動対象のLinuxカーネルは、ホストOS(Ubuntu 18.04)上にインストールしたQEMUから動作させる。 また、今回はクロスコンパイラ環境をDockerで構築した。

ルートファイルシステムとして、Buildrootで生成したルートファイルシステムの非圧縮のcpio形式のアーカイブを利用する。

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

環境 パラメータ
ホスト環境 x86_64
ホストOS Ubuntu 20.04
Buildroot buildroot-2020.02.8
QEMU QEMU emulator version 4.2.1
linux 5.7
Docker version 19.03.13
Docker image ubuntu:20.04

ロスコンパイラ環境の構築手順

  1. ホスト環境にDockerをインストールする。 docs.docker.com

  2. ロスコンパイラ環境を構築する。(作成が億劫な人は、著者自作Dockerfile(https://github.com/LeavaTail/kernel-build)を使用してほしい)

     leava@ubuntu-bionic:~$ docker run --rm --name=kbuild -h "kbuild" -v /srv:/work -it ubuntu:20.04 /bin/bash 
    
  3. コンテナ環境下にカーネルのビルドに必要なパッケージをインストールする。(今回はUbuntuコンテナを利用する)

     root@kbuild:/# apt install git bc bison flex libssl-dev make libncurses-dev libelf-dev file wget cpio unzip rsync build-essential
    
  4. カーネルソースの取得

     root@kbuild:/# cd /work
     root@kbuild:/work# git clone https://github.com/torvalds/linux.git
     root@kbuild:/work# cd linux
     root@kbuild:/work/linux# git checkout -b v5.7 refs/tags/v5.7
    
  5. ロスコンパイラ環境用の環境変数を設定する。

     root@kbuild:/work/linux# export ARCH="x86"
    
  6. x86Linuxカーネル用のコンフィグを生成する。

     root@kbuild:/work/linux# make x86_64_defconfig
    
  7. カーネルをビルドする。

     root@kbuild:/work/linux# make -j `getconf _NPROCESSORS_ONLN` bzImage
    

ルートファイルシステムの構築

Buildrootを用いてrootfsを構築する。

  1. Buildrootをインターネットからダウンロード、ファイルを解凍する。

     leava@ubuntu-bionic:~$ wget https://buildroot.org/downloads/buildroot-2020.02.8.tar.gz
     leava@ubuntu-bionic:~$ tar zxvf  buildroot-2020.02.8.tar.gz && cd buildroot-2020.02.8
    
  2. x86_64専用のデフォルトコンフィグqemu_x86_64_defconfigを利用する。

     leava@ubuntu-bionic:~/buildroot-2020.02.8$ make qemu_x86_64_defconfig
    
  3. Buildrootのビルド

     leava@ubuntu-bionic:~/buildroot-2020.02.8$ make
    
  4. ルートファイルシステムの確認

     leava@ubuntu-bionic:~/buildroot-2020.02.8$ ls -l output/images/rootfs.ext4
     lrwxrwxrwx 1 root root 11 Sep  8 15:45 output/images/rootfs.ext4 -> rootfs.ext2
    

カーネルの起動

  1. x86QEMUをインストールする。

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

     leava@ubuntu-bionic:~$ qemu-system-x86_64 \
         -kernel /srv/linux/arch/x86/boot/bzImage \
         -drive file=/srv/buildroot-2020.02.8/output/images/rootfs.ext2,if=ide,format=raw
         -nographic \
         -append "root=/dev/sda console=ttyS0"
    

おわりに

本記事では、QEMUx86_64用Linuxカーネルを起動させる手順を説明した。
Buildrootで構築した場合、自動でセットアップしてくれるため非常に使いやすく便利である。

変更履歴

  • 2020/05/26: 記事公開
  • 2020/12/11: ブログタイトルを "x86_64用" に訂正
  • 2020/12/14: GDBスタブの手順を削除

参考

QEMULinuxカーネルを起動する

linuxカーネルデバッグ

Raspberry Pi 3 Model B をネットワークブートで起動させる (U-Boot編)

関連記事

概要

Raspberry Pi 3 Model B からSDカードに保存したU-Boot (32bit) を起動し、メインラインカーネル (32bit) をネットワークブートするための環境を構築する方法を解説する。

Raspberry Pi 3Bのネットワークブート概要図 (U-boot)

Raspberry Pi 3B にはU-Bootのバイナリを格納したSDカードを差し込み、U-Bootを利用してネットワーク上にあるネットワークサーバからブートイメージとルートファイルシステムを取得する。
ネットワークブート用のサーバはUbuntu 18.04 LTS上にdnsmasqとnfs-kernel-serverをインストールし、メインラインカーネル (32bit) *1をビルドしておく。

はじめに

組込みシステムの開発段階やデバッグ段階では、ネットワーク経由でシステムを起動(ネットワークブート)できることが理想的である。
ネットワークブートができる環境が構築されていれば、組込みシステムのファームウェア再書き込みせずにデータを共有することができる。

Raspberry PiARMプロセッサを搭載したシングルボードコンピュータの一つで、手軽に入手できる点や世の中に情報が多い点から組込みシステムの入門として用いられることが多い。

しかし、Raspberry Pi 3B のROMには幾つかの不具合が報告されており、ROMによるネットワークブートが安定しない。

一方で、Raspberry Pi 3B のROMから別のブートローダをロードし、そのブートローダからネットワークブートする手法が存在する。 Das U-Boot(U-Boot)は組込み系で広く利用されているブートローダの一つであり、Raspberry Pi 3B でも利用することができる。

U-Bootには機能として、簡易のコマンドやドライバが組み込まれており、TFTPを利用してネットワークブートすることができる。

U-Bootでネットワークブートを用いたRaspberry Pi 3Bの起動シーケンスを下記に示す。

Raspberry Pi 3Bの起動シーケンス

  1. Raspberry Pi 3Bに電源投入すると、GPUが起動する。
  2. GPUは1段目のブートローダを起動。One-Time Programmable (OTP) メモリのブートモードに従ったメディア(今回はSD card)から、2段目のブートローダbootcode.binを取得する。
  3. bootcode.binを実行する。
  4. bootcode.binSDRAMを有効化する。
  5. bootcode.binファームウェアstart.elfを読み込む。
  6. start.elfを実行する。
  7. start.elfconfig.txtを指定されたメディア(今回はSD card)から取得する。
  8. start.elfcmdline.txtを指定されたメディア(今回はSD card)から取得する。
  9. start.elfconfig.txtのkernelパラメータで指定されたu-boot.binを指定されたメディア(今回はSD card)から取得する。
  10. u-bootを実行する。
  11. u-bootTFTP経由でzImageを取得する。
  12. 取得したzImageSDRAMの指定場所(${kernel_addr_r})に読み込む。
  13. u-bootTFTP経由でbcm2837-rpi-3-b.dtbを取得する。
  14. 取得したbcm2837-rpi-3-b.dtbSDRAMの指定場所(${fdt_addr_r})に読み込む。
  15. ARM coreにリセット指令を発行し、${kernel_addr_r}番地からプログラムを実行する。

下記は、Raspberry Pi 3Bが起動するまでに必要となるファイル群である。

ファイル名 格納先 概要
config.txt SD card システム構成パラメータ
cmdline.txt SD card カーネルコマンドライン(もしかしたら不要)
start.elf SD card 一般的なRaspberry Piファームウェア
fixup.dat SD card start.elfのリンカファイル
bootcode.bin SD card SoCに備わっているブートローダ
zImage Server カーネルイメージ
bcm2710-rpi-3-b.dtb Server バイスリーファイル (bcm2837-rpi-3-b.dtb でも代用可能)

そこで本記事では、Raspberry Pi 3B でメインラインカーネルをネットワークブートするための環境を構築する方法を紹介する。

各手法における特徴

前回の記事では、Raspberry Pi 3Bをネットワークブートする手法としてROM内のファームウェアを利用した。 ネットワークブートのための手法はいくつか存在しているが、それぞれ特徴が異なる。

ROM内のファームウェアを利用した場合の特徴としては以下の点があげられる。

  • SDカード周りのデバッグやドライバの開発などに向いている。
  • 運用中はSDカードが不要なので、SDカードを用意するコストを最小限に抑えることができる。

一方、U-Bootを利用した場合の特徴としては以下の点があげられる。

  • 起動対象の差を設定によって吸収できる。
  • (Raspberry Pi 3B の場合) 不具合の影響が小さい。
  • U-Bootに備わっている簡易コマンドを利用することで、別プログラムをロードしたり多段ブートすることを容易である。

実行環境

実験環境は前回構築した環境をそのまま使用する。

ネットワーク構成図

ネットワークブートサーバの詳細は下記のとおりである。

名前 詳細
OS Ubuntu 18.04 LTS
Kernel 5.3.0-40-generic
NIC (ホームセグメント側) eth0
NIC (開発側セグメント) eth1
IPアドレス (eth0) 192.168.1.11
IPアドレス (eth1) 172.16.1.1
ホスト名 server
Raspberry Pi 3Bのブートイメージ格納予定 /srv/boot
Raspberry Pi 3Bのルートファイルシステム格納予定 /srv/rootfs
シリアルデバイスファイル /dev/ttyUSB0
シリアルポートのボーレート 115200

また、使用するRaspberry Pi 3Bの詳細は下記のとおりである。

名前 詳細
ファームウェア 4.19.97-v7+
NIC eth0
IPアドレス 172.16.1.2 (DHCP)
ホスト名 raspberry
ストレージ HIDISC microSDHCカード 8GB CLASS10 UHS-1対応
バイスファイル /dev/sdb1

目標とする環境

本記事が目標とするネットワークブートの構成は下記のとおりである。

ネットワークブートの動作イメージ

Raspberry Pi 3Bに挿入してあるU-Boot(32bit)がネットワークブートを担うこと」と「起動するカーネルはメインラインカーネルのARM(32bit)」以外は前回の記事と同様のことを目指す。

今回使用したソフトウェアのバージョンは下記のとおりである。

名前 バージョン
U-Boot v2020.07-rc1
mainline kernel v5.7-rc3
dnsmasq v2.79-1
nfs-common 1:1.3.4-2.1ubuntu5.2

Raspberry Pi 3Bの設定

ネットワークブートサーバの設定は、システム構成パラメータの更新U-Bootの構築の2ステップ必要になる。

システム構成パラメータの更新

  1. config.txtから読み込むカーネルファイルをU-Bootに変更する。

     user@server:~$ mount /dev/sdb1 /mnt/tmp
     user@server:~$ echo 'kernel=u-boot.bin' > /mnt/tmp/config.txt
     user@server:~$ umount /mnt/tmp
    

U-Bootの構築

今回の手順では、別のマシン(ネットワークブートサーバ)でU-Bootをビルドし、バイナリをSDカードにコピーする。

  1. リポジトリをダウンロードする。

      user@server:~$ git clone git://git.denx.de/u-boot.git
      user@server:~$ cd u-boot
    
  2. ARMクロスコンパイラ(32bit)をインストールする。

      user@server:~$ apt-get install gcc-arm-linux-gnueabi
    
  3. Raspberry Pi 3B (32bit) 用の.configファイルを生成する。

      user@server:~$ make ARCH=arm CROSS_COMPILE=arm-linux-gnueabi- USE_PRIVATE_LIBGCC=yes rpi_3_32b_defconfig
    
  4. U-Bootをビルドする。

      user@server:~$ make
    
  5. 生成したバイナリ(u-boot.bin)をSDカードにコピーする。

      user@server:~$ mount /dev/sdb1 /mnt/tmp
      user@server:~$ cp u-boot.bin /mnt/tmp/
      user@server:~$ umount /mnt/tmp
    

ネットワークブートサーバの設定

ネットワークブートサーバの設定は、ブートイメージの作成ルートファイルシステムの取得DHCP/TFTPサーバの構築NFSサーバの構築の4ステップ必要になる。

ブートイメージの作成以外は、前回の記事で構築した環境をそのまま利用する。

ブートイメージの作成

  1. メインラインカーネルをビルドするのに必要なパッケージをインストールする。

      user@server:~$ sudo apt install git bc bison flex libssl-dev make
    
  2. mainlineのkernelのソースコードをクローンする。

      user@server:~$ git clone --depth=1 https://github.com/torvalds/linux.git
      user@server:~$ cd linux
    
  3. ARM用の.configファイルを生成する。

      user@server:~$ make ARCH=arm CROSS_COMPILE=arm-linux-gnueabi- defconfig
    
  4. カーネルをビルドする。

      user@server:~$ make -j$(nproc) ARCH=arm CROSS_COMPILE=arm-linux-gnueabi- zImage modules dtbs
    
  5. 生成したカーネルイメージとDevice Tree blobsをtftpサーバの公開ディレクトリにコピーする。

      user@server:~$ sudo cp arch/arm/boot/dts/*.dtb /srv/tftpboot/
      user@server:~$ sudo cp arch/arm/boot/zImage /srv/tftpboot/
    

実行結果

  1. minicom経由でアクセスする。

     U-Boot 2020.04-00687-gd16d37bcd4 (Apr 29 2020 - 09:27:20 +0000)
    
     DRAM:  948 MiB
     RPI 3 Model B (0xa32082)
     MMC:   mmc@7e202000: 0, sdhci@7e300000: 1
     Loading Environment from FAT... *** Warning - bad CRC, using default environment
    
     In:    serial
     Out:   vidconsole
     Err:   vidconsole
     Net:   No ethernet found.
     starting USB...
     Bus usb@7e980000: scanning bus usb@7e980000 for devices... 3 USB Device(s) found
            scanning usb for storage devices... 0 Storage Device(s) found
    
  2. Device Tree blobsをメモリ上に展開する。

     U-Boot> dhcp ${fdt_addr_r} ${fdtfile}
     Waiting for Ethernet connection... done.
     BOOTP broadcast 1
     DHCP client bound to address 172.16.1.2 (6 ms)
     Using smsc95xx_eth device
     TFTP from server 172.16.1.1 our IP address is 172.16.1.2
     Filename 'bcm2837-rpi-3-b.dtb'.
     Load address: 0x2600000
     Loading: ##################################################  13.8 KiB
              1 MiB/s
     done
    
  3. カーネルイメージをメモリ上に展開する。

     U-Boot> tftp ${kernel_addr_r} zImage
     Waiting for Ethernet connection... done.
     Using smsc95xx_eth device
     TFTP from server 172.16.1.1; our IP address is 172.16.1.2
     Filename 'zImage'.
     Load address: 0x80000
     Loading: ##################################################  9.3 MiB
              2.7 MiB/s
     done
    
  4. カーネルパラメータを設定する。

     U-Boot> setenv bootargs root=/dev/nfs nfsroot=${serverip}:/srv/rootfs,vers=3,proto=tcp rw rootwait ip=${ipaddr}:${serverip}:${gatewayip}:${netmask}::eth0:off nfsrootdebug
    
  5. 指定番地にあるカーネルを起動させる。

     U-Boot> bootz ${kernel_addr_r} - ${fdt_addr_r}                                                                                                                            
     Kernel image @ 0x080000 [ 0x000000 - 0x953200 ]
     ## Flattened Device Tree blob at 02600000
        Booting using the fdt blob at 0x2600000
        Using Device Tree in place at 02600000, end 02606725
    
     Starting kernel ...
    
     <<< snip >>>
    
     Raspbian GNU/Linux 10 raspberrypi ttyS0
    
     raspberrypi login: 
    

おわりに

本記事では、U-Bootを利用してRaspberry Pi 3Bをネットワークブートする方法を紹介した。

U-Bootを利用したことで、Raspberry Pi 3Bで報告されている不具合を回避しながらも、SDカードの書き込み寿命の浪費を防ぐことができる。
U-Bootにはあらかじめスクリプトを書いておき、起動時に自動で実行する機構もあるので、そちらを利用するとより一層便利になる。

変更履歴

  • 2020/04/30: 記事公開
  • 2022/06/07: 章構成の修正

参考

SDカードを使わず、network bootでRaspberry Pi 3Bを起動する

SDカードにU-Bootのみ格納し、network bootでRaspberry Pi 3Bを起動する

SDカードを使わず、network bootでRaspberry Pi 3B+を起動する

Raspberry Pi の起動シーケンス

Raspberry Pi のブートで必要なファイル郡

U-Bootに関する説明資料

*1:Raspbianにあるカーネルはそのままだと起動できなかった

Raspberry Pi 3 Model B をネットワークブートで起動させる

関連記事

概要

Raspberry Pi 3 Model BからLinuxカーネル (Raspbian)をネットワークブートするための環境を構築する方法を解説する。

Raspberry Pi 3Bのネットワークブート概要図

Raspberry Pi 3Bは、SDカードを挿入せずにネットワーク上にあるネットワークサーバからブートイメージとルートファイルシステムを取得する。
また、ネットワークブート用のサーバとして、Ubuntu 18.04 LTS上にdnsmasqとnfs-kernel-serverをインストールした。

はじめに

組込みシステムの開発段階やデバッグ段階では、ネットワーク経由でシステムを起動(ネットワークブート)できることが理想的である。
ネットワークブートができる環境が構築されていれば、組込みシステムのファームウェア再書き込みせずにデータを共有することができる。

Raspberry PiARMプロセッサを搭載したシングルボードコンピュータの一つで、手軽に入手できる点や世の中に情報が多い点から組込みシステムの入門として用いられることが多い。

Raspberry Pi 3Bについて

Raspberry Pi 3 Model B(Raspberry Pi 3B)は複数のブートモードがある。下記は、Raspberry Pi 3Bの起動シーケンスを示している。

Raspberry Pi 3Bの起動シーケンス

Raspberry Pi 3B では、電源投入されるとOne-Time Programmable (OTP) メモリをロードする。
OTPメモリにはブートモードを決定するフラグが保存されており、Raspberry Pi 3B のGPUはこのフラグを基にブートを試みる。

各ブートモードは、ブートに必要なファイル (ブートイメージ) をそれぞれの方法でメモリにロードする。

Raspberry Pi 3B のブートイメージは下記のファイルがある。

状態 概要
config.txt システム構成パラメータ
start.elf 一般的なRaspberry Piファームウェア
fixup.dat start.elfのリンカファイル
bcm2710-rpi-3-b.dtb ハードウェアの構成情報(デバイスリーファイル)
cmdline.txt カーネルコマンドライン
kernel7.img カーネルイメージ
bootcode.bin ブートローダ

Raspberry Pi 3Bのネットワークブートでは、TFTPでブートイメージを取得する。 ネットワークブートを用いたRaspberry Pi 3Bの起動シーケンスを下記に示す。

ネットワークブートのシーケンス

  1. Raspberry Pi 3BではオンボードEthernetドライバの初期化をした後、LAN内にDHCPリクエスをブロードキャストする。
  2. DHCPリクエストを受け取ったネットワークブートサーバは、TFTPサーバのIPアドレスを送信する。
  3. Raspberry Pi 3Bは、TFTPサーバのIPアドレスからブートイメージを取得し、カーネルをブートする。

しかし、Raspberry Pi 3Bのネットワークブートには幾つかのバグが報告されている。

Known problems

  • DHCP requests time out after five tries
  • TFTP server on separate subnet not supported
  • DHCP relay broken
  • Raspberry Pi Boot string
  • DHCP UUID constant
  • ARP check can fail to respond in the middle of TFTP transaction
  • DHCP request/reply/ack sequence not correctly implemented

このため、Raspberry Pi 3Bでネットワークブートを試している人は少ない。*1 そこで本記事では、Raspberry Pi 3BからRaspbianをネットワークブートするための環境を構築する方法を紹介する。

実行環境

実験環境として、セグメントが二つに分かれている下記のネットワーク構成のものを使用する。

ネットワーク構成図

ホームセグメントは、一般的なPCや家電製品などが接続されており家庭用ルータによって管理されている。 開発用セグメントは、Raspberry Pi 3B が接続されておりネットワークブートサーバによって管理されている。

本運用では、ホームセグメントにあるクライアントPC (Windows 10 Home) からネットワークブートサーバ(Ubuntu 18.04 LTS) にSSH経由でアクセスする。 また、ネットワークブートサーバとRaspberry Pi 3Bはシリアル接続されている。

ネットワークブートサーバの詳細は下記のとおりである。

名前 詳細
OS Ubuntu 18.04 LTS
Kernel 5.3.0-40-generic
NIC (ホームセグメント側) eth0
NIC (開発側セグメント) eth1
IPアドレス (eth0) 192.168.1.11
IPアドレス (eth1) 172.16.1.1
ホスト名 server
Raspberry Pi 3Bのブートイメージ格納予定 /srv/boot
Raspberry Pi 3Bのルートファイルシステム格納予定 /srv/rootfs
シリアルデバイスファイル /dev/ttyUSB0
シリアルポートのボーレート 115200

また、使用するRaspberry Pi 3Bの詳細は下記のとおりである。

名前 詳細
ファームウェア 4.19.97-v7+
NIC eth0
IPアドレス 172.16.1.2 (DHCP)
ホスト名 raspberry
ストレージ なし

Raspberry Pi 3Bの初期セットアップ

本記事が目標とするネットワークブートの構成は下記のとおりである。

ネットワークブートのフロー図

Raspberry Pi 3BのUSBブートフラグをONにし、ネットワークブートで起動できるように設定をする。

  • 注意: USBブートフラグをONにするために、Raspbianの入ったSDカードが必要になる。

ネットワークブートサーバには、DockerコンテナとしてDHCP/TFTPサーバを構築しブートイメージを格納しておく。
また、ホストにNFSサーバを構築しルートファイルシステムを格納しておく。

今回は、Raspbian Buster Liteのブートイメージとルートファイルシステムを利用する。

この状態で、SDカードからRaspbianを起動する

  1. 現在のブートフラグを確認する (OPT bitより、Bit29がネットワークブートのフラグとなっている)

     pi@raspberry:~$ vcgencmd otp_dump | grep 17:
     > 17:1020000a    # 現在はOFFになっている
    
  2. /boot/config.txtを修正する

    pi@raspberry:~$ echo "program_usb_boot_mode=1" | sudo tee -a /boot/config.txt 
    
  3. 再起動する

     pi@raspberry:~$ sudo reboot
    
  4. 再度、ブートフラグを確認する

     pi@raspberry:~$ vcgencmd otp_dump | grep 17:
     > 17:3020000a    # 現在はONになっている
    

上記の設定により、OTPメモリにUSBブートフラグが設定された。
これ以降ネットワークブートが可能になったので、Raspberry Pi 3BからSDカードを抜いておく

ネットワークブートサーバの設定

ネットワークブートサーバの設定は、「ブートイメージとルートファイルシステムの取得」と「DHCP/TFTPサーバの構築」、「NFSサーバの構築」の3ステップ必要になる。

ブートイメージとルートファイルシステムの取得

  1. Raspbianのイメージを取得する

     user@server:~$ wget https://downloads.raspberrypi.org/raspbian_lite/images/raspbian_lite-2020-02-14/2020-02-13-raspbian-buster-lite.zip
     user@server:~$ unzip 2020-02-13-raspbian-buster-lite.zip
    
  2. 取得したディスクイメージをマウントする

     user@server:~$ sudo kpartx -a 2020-02-13-raspbian-buster-lite.img 
     user@server:~$ sudo mount /dev/mapper/loop0p1 /mnt/boot
     user@server:~$ sudo mount /dev/mapper/loop0p2 /mnt/rootfs
    
  3. ディスクイメージをTFTPサーバとNFSサーバの公開先ディレクトリにコピーする

     user@server:~$ sudo rsync -av /mnt/boot /srv/tftpboot/
     user@server:~$ sudo rsync -av /mnt/rootfs /srv/rootfs
    
  4. ディスクイメージをアンマウントする

     user@server:~$ sudo umount /mnt/rootfs
     user@server:~$ sudo umount /mnt/boot
     user@server:~$ sudo kpartx -d 2020-02-13-raspbian-buster-lite.img 
    
  5. カーネルコマンドラインを修正し、NFSルートとシリアルコンソールを設定する

     user@server:~$ sudo mv /srv/tftpboot/cmdline.txt /srv/tftpboot/cmdline.txt.old
     user@server:~$ echo "console=serial0,115200 console=tty1 root=/dev/nfs nfsroot=172.16.1.1:/srv/rootfs,vers=3,proto=tcp rw ip=dhcp rootwait elevator=deadline" | sudo tee /srv/tftpboot/cmdline.txt 
    
  6. UARTを有効にする

     user@server:~$ echo "enable_uart=1" | sudo tee -a /srv/tftpboot/config.txt 
    
  7. Raspberry Pi 3B側のファイルシステムテーブルを修正する

     user@server:~$ sudo mv /srv/rootfs/etc/fstab /srv/rootfs/etc/fstab.old
     user@server:~$ echo "proc            /proc           proc    defaults          0       0" | sudo tee /srv/rootfs/etc/fstab 
    

DHCP/TFTPサーバの構築

著者が使用しているDockerイメージはGitHubにて公開しているので、構築が手間な方はそちらを使用してほしい。

  1. ベースとして使用するAlpine Linuxのイメージを取得する

     user@server:~$ docker pull alpine:3.11.3
    
  2. Dockerコンテナを起動させる

     user@server:~$ docker run --privileged --net=host -v /srv/tftpboot:/srv  -it alpine:3.11.3 /bin/sh
    
  3. dnsmasqをインストールする

     / # apk update
     / # apk add dnsmasq
    
  4. dnsmasqの設定ファイルを修正する (修正箇所は下記のとおり)

     @@ -10 +10 @@
     -#port=5353
     +port=0
     @@ -21 +21 @@
     -#bogus-priv
     +bogus-priv
     @@ -106 +106 @@
     -#interface=
     +interface=eth1
     @@ -157 +157 @@
     -#dhcp-range=192.168.0.50,192.168.0.150,12h
     +dhcp-range=172.16.1.2, 172.16.1.65, 12h
     @@ -477 +477 @@
     -#pxe-service=x86PC, "Boot from local disk"
     +pxe-service=0, "Raspberry Pi Boot"
     @@ -499 +499 @@
     -#enable-tftp
     +enable-tftp
     @@ -502 +502 @@
     -#tftp-root=/var/ftpd
     +tftp-root=/srv
     @@ -664 +664 @@
     -#log-dhcp
     +log-dhcp
    
  5. dnsmasqを起動させる

     / # dnsmasq
    
  6. dockerコンテナからデタッチする

     / # <Ctrl-p> <Ctrl-q>
    

NFSサーバの構築

NFSサーバもDockerコンテナとして管理しても良いが、ディレクトリ共有のオーバーヘッドを懸念して、ホストに直接立てることにした。

  1. NFSサーバのnfs-kernel-serverをインストールする

     user@server:~$ sudo apt install nfs-kernel-server 
    
  2. NFSで公開するディレクトリを追加する

     user@server:~$ echo "/srv/rootfs  172.16.1.0/255.255.255.0(rw,sync,no_root_squash,no_subtree_check)" | sudo tee -a 
     /etc/exports 
     user@server:~$ sudo exportfs -ra
    

実行結果

minicom経由でアクセスする。

    user@server:~$ sudo minicom /dev/ttyUSB0 
    <--snip-->
    Raspbian GNU/Linux 10 raspberrypi ttyS0

    raspberrypi login: 

おわりに

本記事では、Raspberry Pi 3BにSDカードを挿入せずブートすることができるネットワークブートの方法を紹介した。
このように、バグが報告されているRaspberry Pi 3Bでもネットワークブートすることができた。

今回は、ネットワークブートサーバにdnsmasqとnfs-kernel-serverをインストールする手法を紹介したが、その他のパッケージ (isc-dhcp-serverやtftpd-hpa)を用いても構築することができる。
また、公開されているRaspbian Buster Liteのディスクイメージのルートファイルシステムをそのまま使用しているので、SSHホストキーの再生成は実施しておくとよい。

変更履歴

  • 2020/03/15: 記事公開
  • 2022/06/07: 章構成の修正

参考

SDカードを使わず、network bootでRaspberry Pi 3Bを起動する

SDカードにU-Bootのみ格納し、network bootでRaspberry Pi 3Bを起動する

SDカードを使わず、network bootでRaspberry Pi 3B+を起動する

Raspberry Pi の起動シーケンス

Raspberry Pi のブートで必要なファイル郡

付録

Dockerで使用したオプションは以下のとおりである。

オプション名 詳細 指定理由
--privileged コンテナを特権モードで動作させる DHCPリレーサービスで物理ノードを参照するため。
指定しないと、ARP-cache injection failed: Operation not permitted dockerとエラーになってしまう
--net=host ホスト側のネットワークスタックをコンテナに接続する ホスト側のネットワークセグメントでDHCPを公開する範囲を制限するため。
-v /srv/tftpboot:/srv ホスト側のブートイメージ格納先をマウントする コンテナ内のTFTPサービスからブートイメージを参照するため。

dnsmasqで指定したオプションは以下のとおりである。

パラメータ 詳細
port 0 DNS サーバーは不要なので無効化しておく
bogus-priv プライベートIPの逆引きは上位DNSに転送しない
interface eth1 開発用セグメントを指定する
dhcp-range 172.16.1.2, 172.16.1.65, 12h DHCPで払い出すIPアドレスの範囲を指定する
pxe-service Raspberry Pi Boot Raspberry Pi では「Raspberry Pi Boot」と指定されているパケットのみ受け付ける
enable-tftp TFTPサーバを有効化する
tftp-root /srv TFTPサーバが提供するルートディレクトリをコンテナ起動時にマウントしたディレクトリを指定する。
log-dhcp dhcp関連の詳細なログを出力する

*1:Raspberry Pi 3 Model B+ではバグが修正されているので、そちらで実施している人が多い

組込み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の解説