関連記事
- Part 1: 環境セットアップ
- Part 2: System call Interface
- Part 3: VFS
- Part 4: ext2 (1) write_iter
- Part 5: ext2 (2) write_begin
- Part 6: ext2 (3) get_block
- Part 7: ext2 (4) write_end
- Part 8: writeback (1) work Queue
- Part 9: writeback (2) wb_writeback
- Part 10: writeback (3) writepages
- Part 11: writeback (4) write_inode
- Part 12: block (1) submit_bio
- Part 13: block (2) blk_mq
- Part 14: I/O scheduler (1) mq-deadline
- Part 15: I/O scheduler (2) insert_request
- Part 16: I/O scheduler (3) dispatch_request
- Part 17: block (3) blk_mq_run_work_fn
- Part 18: block (4) block: blk_mq_do_dispatch_sched
- Part 19: MMC (1) initialization
- Part 20: PL181 (1) mmci_probe
- Part 21: MMC (2) mmc_start_host
- Part 22: MMC (3) mmc_rescan
概要
QEMUの vexpress-a9 (arm) で Linux Kernel v5.15を起動させながら、ファイル書き込みのカーネル処理を確認していく。
本章では、コードリーディング用にデバッグ情報を付与したLinuxカーネルのビルドとBuildRootによる実行環境を構築した。
はじめに
一般的なOSはファイルという形式を通して、ハードディスクやフラッシュメモリといった記憶装置にデータを保存している。
この処理を担うのがファイルシステムと呼ばれる機構である。
一般的な利用者はこのことを意識せずに利用することができるが、ソフトウェアエンジニアは処理を理解していないとディスクIOパフォーマンスが悪化し、システム全体のパフォーマンスに大きく影響を及ぼす恐れがある。
そこで、アプリケーションがファイルを書き込んだ際にLinuxカーネルがどのような処理で記憶装置に読み書きされるかを順を追って説明する。
本記事では、SYSCALL_DEFINE(write)
からデバイスドライバまでの処理を対象とする。
背景
一般的なOSでは、さまざまなコンポーネントから成り立っている。ファイルシステムもその一つである。 ファイルの書き込み処理一つとっても、多数のコンポーネントとの関係を持つ。
下記の図は、他サイトで掲載されているLinuxカーネルv4.10の構成図である。(2022年8月現在、Linuxカーネルv5.19.1がリリースされている)
"https://www.thomas-krenn.com/en/wiki/Linux_Storage_Stack_Diagram">Linux Storage Stack Diagram
このように、Linuxカーネルv4.10の時点でもたくさんのフローからファイルアクセスが成り立っている。(大まかな処理は最新カーネルでも変わらないのでこの図を基に説明を続ける)
ここでは、read(2)とwrite(2)について説明する。
read(2)
- VFSは、ファイルに対応するファイルシステムのread処理を呼び出す。
- ファイルシステムは、ファイルがキャッシュに載っているか確認する。(あればそれをApplicationに渡して終了する)
- ファイルシステムは、Block LayerにBIOを挿入する。
- Block Layerは、スケジューラによりBIOを並び替る。
- Block Layerは、Device DriverにRequestを発行する。
- Device Driverは、Physical devicesにIOを要求する。
- Physical devicesは、デバイスのファームウェアに則りデータの読み込みをする。
- Physical devicesは、カーネルにIO完了通知をする。
- (Direct_IOでなければ)カーネルは、読み込みしたデータをPage cacheとしてキャッシュする。
- カーネルは、Applicationにデータを渡して終了する。
write(2)
- VFSは、ファイルに対応するファイルシステムのwrite処理を呼び出す。
- (Direct_IOでなければ)ファイルシステムは、ファイルをキャッシュにしてApplicationに完了を通知する。
- ファイルシステムは、Block LayerにBIOを挿入する。
- Block Layerは、スケジューラによりBIOを並び替る。
- Block Layerは、Device DriverにRequestを発行する。
- Device Driverは、Physical devicesにIOを要求する。
- Physical devicesは、デバイスのファームウェアに則りデータの書き込みをする。
- Physical devicesは、カーネルにIO完了通知をする。
一般的なストレージに対する書き込み処理は、下記のようなライトバック方式で行われる。
ファイルの書き込みをしたアプリケーションはページキャッシュをDirtyにするだけで処理を終了する。その後、カーネルスレッドが定期的にDirtyとなっているキャッシュを書き込む。
環境構成
本稿では、QEMUを用いて観測対象のLinuxカーネルを起動させる。 QEMUを利用することで、下記のような利点が得られる。
- 実行環境による違いを緩和することができる
- ホスト側から任意のタイミングでGDBでアタッチすることができる
本稿では、下記の環境で処理を確認していった。
Host側
概要 | 説明 |
---|---|
Architecture | x86_64 |
Board | custom board |
Linux | 5.15.0-46-generic |
kernel config | unknown |
Userland | Ubuntu Desktop 22.04.1 |
Buildroot | buildroot 2022.08.1 |
QEMU | QEMU emulator version 7.0.0 |
Guest側
概要 | 説明 |
---|---|
Architecture | armhf |
Board | vexpress-a9 |
Linux | linux-5.15 |
kernel config | vexpress_defconfig |
Userland | Buildroot |
Storage | SD card |
File-Syste | ext2 |
Disk Scheduler | MQ-DEADLINE |
デバッグ機能について
vexpress_defconfig
でもカーネルを起動させることができるが、デバッグ容易性のために Kconfigの変更とデバッグ用のカーネルパッチを適用をする。
追加したデバッグ機能については次のRepositoryで管理している。
https://github.com/LeavaTail/buildroot-2022.08.1-qemu_arm_vexpressgithub.com
これを、buildrootディレクトリの配下にある board/qemu/arm-vexpress
以下に展開しておく。
実行基板について
QEMUでは、Versatile Express motherboardとCoreTile Express A9x4 daughterboardの組み合わせをvexpress-a9というボードでサポートしている。 それぞれの機器のデータシートはArm Developerに記載されている。
下記は、Arm Developerで記載されている機器のレイアウト図を引用している。
こちらは、Versatile Express motherboardのレイアウト図である。
https://developer.arm.com/documentation/dui0448/i/hardware-description/overview-of-the-coretile-express-a9-4-daughterboard
こちらは、CoreTile Express A9x4 daughterboardのレイアウト図である。
https://developer.arm.com/documentation/dui0448/i/hardware-description/overview-of-the-coretile-express-a9-4-daughterboard
これらの情報とQEMUの公式サイトに書かれている情報を基に、vexpress-a9の概略図を示す。
また、Linuxカーネル v5.15におけるメモリーマップを記す。
作成手順
実行環境の準備
Linuxカーネルのファイルアクセスをトレースするための実行環境をBuildRootにより作成する。
Buildrootを入手する。
leava@kbuild:/work$ git clone https://github.com/buildroot/buildroot.git leava@kbuild:/work$ cd buildroot leava@kbuild:/work/buildroot$ git checkout 2022.08.1
Buildrootのデフォルトの設定を使用する。
leava@kbuild:/work/buildroot$ make qemu_arm_vexpress_defconfig
Buildrootの設定を適宜修正する。
toolchain ---> (glibc) C library [*] Enable C++ support [*] Build cross gdb for the host [*] TUI support System configuration ---> /bin/sh (bash) ---> (root) Root password Kernel ---> (5.15) Kernel version (board/qemu/arm-vexpress/patches) Custom kernel patches Kernel configuration (Using a custom (def)config file) ---> (board/qemu/arm-vexpress/.config) Configuration file path Target packages [*] Show packages that are also provided by busybox Debugging, profiling and benchmark ---> [*] blktrace Development tools [*] binutils Filesystem and flash utilities [*] mmc-utils Networking applications [*] dropbear Host utilities ---> [*] host qemu *** Emulators selection *** [*] Enable system emulation [*] Enable Linux user-land emulation
Buildrootの設定からユーザランドを構築する。
leava@kbuild:/work/buildroot$ make
Buildrootで作成した環境を実行するためのスクリプトを用意する。
// 1: #!/bin/bash -x ( BUILDROOT_DIR="/usr/local/src/buildroot" BINARIES_DIR="${BUILDROOT_DIR}/output/images/" NFSROOT="/srv/nfsroot/armhf/buildroot" FSTYPE="ext2" SDCARD="/tmp/${FSTYPE}.img" EXTRA_ARGS="-nographic -s" TARGET_ROOTFS="/dev/nfs" EXTRA_CMDLINE="nfsroot=${NFSROOT},vers=3,tcp ip=on" CMDLINE="console=ttyAMA0,115200 rootwait root=${TARGET_ROOTFS} rw ${EXTRA_CMDLINE}" function gen_testimage () { DISTDEV="/mnt" mkfs.${FSTYPE} ${SDCARD} mount -t ${FSTYPE} -o loop ${SDCARD} ${DISTDEV} echo -n A > ${DISTDEV}/FILE umount ${DISTDEV} } if [ ! -e ${SDCARD} ]; then dd if=/dev/zero of=${SDCARD} bs=1K count=1M gen_testimage elif [ -z `blkid -o value -s TYPE ${SDCARD}` ]; then gen_testimage fi cd ${BINARIES_DIR} export PATH="/usr/local/src/buildroot/output/host/bin:${PATH}" exec qemu-system-arm -M vexpress-a9 -smp 1 -m 1024 \ -kernel zImage -dtb vexpress-v2p-ca9.dtb \ -drive file=${SDCARD},if=sd,format=raw \ -append "${CMDLINE}" \ -net nic,model=lan9118 -net user \ ${EXTRA_ARGS} )
ルートファイルシステムのカスタマイズ
Buildrootで生成したルートファイルシステムをNFS経由でmountできるようにカスタマイズする。
Host PCに下記パッケージをインストールする。
leava@leava-host:/srv/nfsroot$ sudo apt-get install nfs-kernel-server
Host PCでNFSサーバの設定する
leava@leava-host:/srv/nfsroot$ echo "/srv/nfsroot 127.0.0.1(rw,no_root_squash,no_subtree_check,insecure)" | sudo tee -a /etc/exports leava@leava-host:/srv/nfsroot$ sudo exportfs -v
Host PCにBuildrootで生成したルートファイルシステムを展開する
leava@leava-host:/srv/nfsroot$ sudo tar -xf output/images/rootfs.tar -C /srv/nfsroot/armhf/buildroot
テストスクリプトの作成
// 1: #!/bin/bash DEVFILE="/dev/mmcblk0" DIRECTORY="/mnt" TARGETFILE="FILE" if [ ! -e ${DEVFILE} ]; then echo "Target device is not exist" 1>&2 exit 1 fi mountpoint -q ${DIRECTORY} || mount -t ext2 ${DEVFILE} ${DIRECTORY} echo "Write: Test start" mount | grep ${DIRECTORY} sync echo 3 > /proc/sys/vm/drop_caches echo -n "HELLO" >> ${DIRECTORY}/${TARGETFILE} sync echo 3 > /proc/sys/vm/drop_caches umount ${DIRECTORY}
調査方法
-
leava@leava-host:~/work$ start-qemu.sh ... [ 2.193490][ T1] Run /sbin/init as init process Starting syslogd: OK Starting klogd: OK Running sysctl: OK Initializing random number generator: OK Saving random seed: [ 34.958887][ T96] random: dd: uninitialized urandom read (512 bytes read) OK Starting rpcbind: OK Starting network: ip: RTNETLINK answers: File exists Skipping eth0, used for NFS from 10.0.2.2 FAIL Starting dropbear sshd: OK Welcome to Buildroot buildroot login: root Password: #
ホスト側からGDBでattachする。
leava@leava-host:~/work$ cd /usr/local/src/buildroot/output/build/linux-5.15; ../../host/bin/arm-buildroot-linux-gnueabihf-gdb vmlinux GNU gdb (GDB) 10.2 Copyright (C) 2021 Free Software Foundation, Inc. License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html> This is free software: you are free to change and redistribute it. There is NO WARRANTY, to the extent permitted by law. Type "show copying" and "show warranty" for details. This GDB was configured as "--host=x86_64-pc-linux-gnu --target=arm-buildroot-linux-gnueabihf". Type "show configuration" for configuration details. For bug reporting instructions, please see: <https://www.gnu.org/software/gdb/bugs/>. Find the GDB manual and other documentation resources online at: <http://www.gnu.org/software/gdb/documentation/>. For help, type "help". Type "apropos word" to search for commands related to "word"... Reading symbols from vmlinux... (gdb) target remote :1234 Remote debugging using :1234 cpu_v7_do_idle () at arch/arm/mm/proc-v7.S:78 78 ret lr
任意の関数 (ここでは、
sys_write
に対して)ブレークポイントを設置する。(gdb) b sys_write
プログラムの実行を再開する。
(gdb) c
上記の環境で下記のコマンドを実行した場合のファイルアクセスの処理を調査する。
# write-exec.sh
おわりに
本記事では、これからLinuxカーネルのファイルアクセスの処理を追いかけるための環境構築をした。
次回の記事では、作成した環境を用いて「writeシステムコールの実態からVFSレイヤまで」の処理を追いかける。
変更履歴
- 2020/09/25: 記事公開
- 2020/11/22: 調査対象 (Syscall Interface ~ デバイスドライバ) を追加
- 2020/12/14: GDB接続手順の追記
- 2020/12/17: アーキテクチャをx86_64からARMに変更
- 2020/12/18: 調査するカーネルのバージョンを5.7.19から5.10に変更
- 2021/11/23: 環境構築をinitramfsからNFSを用いる方法に変更
- 2022/08/21: 調査するカーネルのバージョンを5.10から5.15に変更
- 2022/10/09: Buildroot製のルートファイルシステムに変更
参考
- Linux Performance
- Linuxパフォーマンスの調査に関して記載されている技術書
- Linux Storage Stack Diagram - Thomas-Krenn-Wiki-en
- NameBright - Domain Expired
- ファイルアクセスの特にブロックレイヤーの挙動について解説されている
- Documentation – Arm Developer
- Motherboard Expressのデータシート
- Documentation – Arm Developer
- ARM CoreTile Express A9のデータシート