LeavaTailの日記

LeavaTailの日記

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

Linuxカーネルのファイルアクセスの処理を追いかける (1) 環境セットアップ

関連記事

概要

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/de/wikiDE/images/e/e0/Linux-storage-stack-diagram_v4.10.png

"https://www.thomas-krenn.com/en/wiki/Linux_Storage_Stack_Diagram">Linux Storage Stack Diagram

このように、Linuxカーネルv4.10の時点でもたくさんのフローからファイルアクセスが成り立っている。(大まかな処理は最新カーネルでも変わらないのでこの図を基に説明を続ける)
ここでは、read(2)とwrite(2)について説明する。
read(2)

  1. VFSは、ファイルに対応するファイルシステムのread処理を呼び出す。
  2. ファイルシステムは、ファイルがキャッシュに載っているか確認する。(あればそれをApplicationに渡して終了する)
  3. ファイルシステムは、Block LayerにBIOを挿入する。
  4. Block Layerは、スケジューラによりBIOを並び替る。
  5. Block Layerは、Device DriverにRequestを発行する。
  6. Device Driverは、Physical devicesにIOを要求する。
  7. Physical devicesは、デバイスファームウェアに則りデータの読み込みをする。
  8. Physical devicesは、カーネルにIO完了通知をする。
  9. (Direct_IOでなければ)カーネルは、読み込みしたデータをPage cacheとしてキャッシュする。
  10. カーネルは、Applicationにデータを渡して終了する。

write(2)

  1. VFSは、ファイルに対応するファイルシステムのwrite処理を呼び出す。
  2. (Direct_IOでなければ)ファイルシステムは、ファイルをキャッシュにしてApplicationに完了を通知する。
  3. ファイルシステムは、Block LayerにBIOを挿入する。
  4. Block Layerは、スケジューラによりBIOを並び替る。
  5. Block Layerは、Device DriverにRequestを発行する。
  6. Device Driverは、Physical devicesにIOを要求する。
  7. Physical devicesは、デバイスファームウェアに則りデータの書き込みをする。
  8. 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://documentation-service.arm.com/static/5e9074b78259fe2368e2acd9?token= https://developer.arm.com/documentation/dui0448/i/hardware-description/overview-of-the-coretile-express-a9-4-daughterboard

こちらは、CoreTile Express A9x4 daughterboardのレイアウト図である。

https://documentation-service.arm.com/static/5e9db8569931941038de23df?token= https://developer.arm.com/documentation/dui0448/i/hardware-description/overview-of-the-coretile-express-a9-4-daughterboard

これらの情報とQEMUの公式サイトに書かれている情報を基に、vexpress-a9の概略図を示す。

vexpress-a9のレイアウト イメージ図

また、Linuxカーネル v5.15におけるメモリーマップを記す。

ARM memory Layout

作成手順

実行環境の準備

Linuxカーネルのファイルアクセスをトレースするための実行環境をBuildRootにより作成する。

  1. 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
    
  2. Buildrootのデフォルトの設定を使用する。

     leava@kbuild:/work/buildroot$ make qemu_arm_vexpress_defconfig
    
  3. 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
    
  4. Buildrootの設定からユーザランドを構築する。

     leava@kbuild:/work/buildroot$ make
    
  5. 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できるようにカスタマイズする。

  1. Host PCに下記パッケージをインストールする。

     leava@leava-host:/srv/nfsroot$ sudo apt-get install nfs-kernel-server
    
  2. 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
    
  3. 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}

調査方法

  1. QEMU上でLinuxカーネルを起動する。

     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:
     #
    
  2. ホスト側から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
    
  3. 任意の関数 (ここでは、sys_writeに対して)ブレークポイントを設置する。

     (gdb) b sys_write
    
  4. プログラムの実行を再開する。

     (gdb) c
    
  5. 上記の環境で下記のコマンドを実行した場合のファイルアクセスの処理を調査する。

     # 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製のルートファイルシステムに変更

参考