LeavaTailの日記

LeavaTailの日記

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

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

関連記事

概要

QEMUの vexpress-a9 (arm) で Linux 5.10を起動させながら、ファイル書き込みのカーネル処理を確認していく。

本章では、コードリーディング用にデバッグ情報を付与したLinuxカーネルのビルドBuildRootによる開発環境を構築した。

はじめに

一般的なOSはファイルという形式を通して、ハードディスクやフラッシュメモリといった記憶装置にデータを保存している。
この処理を担うのがファイルシステムと呼ばれる機構である。
一般的な利用者はこのことを意識せずに利用することができるが、ソフトウェアエンジニアは処理を理解していないとディスクIOパフォーマンスが悪化し、システム全体のパフォーマンスに大きく影響を及ぼす恐れがある。
そこで、アプリケーションがファイルを書き込んだ際にLinuxカーネルがどのような処理で記憶装置に読み書きされるかを順を追って説明する。

調査範囲

本記事では、SYSCALL_DEFINE(write)からデバイスドライバまでの処理を対象とする。

背景

一般的なOSでは、さまざまなコンポーネントから成り立っている。ファイルシステムもその一つである。 ファイルの書き込み処理一つとっても、多数のコンポーネントとの関係を持つ。

下記の図は、他サイトで掲載されているLinuxカーネルv4.10の構成図である。(2020年12月現在、Linuxカーネルv5.10.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完了通知をする。

つまりカーネルの書き込み処理は下記のようなシーケンスで実行する。

カーネルの処理シーケンス

Linuxでは、ユーザにデバイスの書き込みによるオーバーヘッドを見せないようにするために、ライトバック方式を採用している。
通常の場合、ファイルの書き込みをしたアプリケーションはページキャッシュをDirtyにするだけで処理を終了する。その後、カーネルスレッドが定期的にDirtyとなっているキャッシュを書き込む。

環境構成

本稿では、QEMUを用いて観測対象のLinuxカーネルを起動させる。 QEMUを利用することで、下記のような利点が得られる。

  • 実行環境による違いを緩和することができる
  • ホスト側から任意のタイミングでGDBでアタッチすることができる

本稿では、下記の環境で処理を確認していった。

Host側

概要 説明
Architecture x86_64
Board custom
Linux 5.11.0-40-generic
kernel config unknown
Userland Ubuntu Desktop 20.04.2
QEMU QEMU emulator version 5.1.0

Guest側

概要 説明
Architecture arm
Board vexpress-a9
Linux linux-5.10
kernel config vexpress_defconfig
Userland ubuntu-base-20.04.3

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のレイアウト イメージ図

また、Arm Developerなどに記載されているドキュメントを基にこのボードのメモリレイアウトを記す。

ARM Memory Layout

上記の概略図はあくまで著者の理解であり、正確なものではないので注意していただきたい。

さらにSDカードとして、ext2ファイルシステムでフォーマットされたイメージをアタッチする。

作成手順

ホスト環境の準備

ユーザランドには、下記の記事で構築したUbuntu-base 20.04を利用する。

leavatail.hatenablog.com

また、実験用のイメージ(1GB) を作成する。

    leava@leava-host:~/work$  dd if=/dev/zero of=ext2.img bs=1K count=1M
    leava@leava-host:~/work$  mkfs.ext2 ext2.img

さらに、実験用のイメージにテスト用のファイルを生成しておく。

    leava@leava-host:~/work$  sudo mount -t ext2 -o loop ./ext2.img /mnt
    leava@leava-host:~/work$  echo A | sudo tee /mnt/FILE
    leava@leava-host:~/work$  sudo umount 

カーネルの準備

カーネルのビルドには下記のDocker Imageを使用した。

github.com

  1. ビルド用のコンテナをアタッチする。

     leava@leava-host:~/kernel-build$ docker-compose exec arm /bin/bash    
     leava@kbuild:/work$ cd ~/work
    
  2. Linux Kernelのソースコードを入手する。

     leava@kbuild:~/work$ git clone git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
     leava@kbuild:~/work$ cd linux
     leava@kbuild:~/work/linux$ git checkout refs/tags/v5.10
    
  3. Buildrootのデフォルトの設定を使用する。

     leava@kbuild:~/work/linux$ make vexpress_defconfig
    
  4. カーネルのビルド

     leava@kbuild:~/work/linux$ make -j"$(nproc)" zImage dtbs
    
  5. 起動に必要なバイナリを移動させる。

     leava@kbuild:~/work/linux$ ln -s ~/work/linux/arch/arm/boot/zImage ~/work
     leava@kbuild:~/work/linux$ ln -s ~/work/linux/arch/arm/boot/dts/vexpress-v2p-ca9.dtb ~/work
    

ツールチェインの準備

ツールチェイン(GDB, ...)に関しては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 2021.08
    
  2. Buildrootのデフォルトの設定を使用する。

     leava@kbuild:/work/buildroot$ make qemu_arm_vexpress_defconfig
    
  3. Buildrootの設定を適宜修正する。

     toolchain  --->
       (glibc) C library
       Kernel Headers (Linux 5.10.x kernel headers)  --->    
       [*] Enable C++ support
       [*] Build cross gdb for the host
         [*]   TUI support
    
     System configuration  --->  
       (root) Root password 
    
     Kernel   --->
       [ ] Linux Kernel
    
    Filesystem images  ---> 
       [ ] ext2/3/4 root filesystem
    
     Host utilities  ---> 
       [*] host qemu 
          *** Emulators selection ***
         [*]   Enable system emulation
         [*]   Enable Linux user-land emulation
    
  4. Buildrootの設定からユーザランドを構築する。

     leava@kbuild:/work/buildroot-2020.11$ make
    

調査方法

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

     leava@leava-host:~/work$ ~/work/buildroot/output/host/bin/qemu-system-arm -M vexpress-a9 -smp 1 -m 1024 -kernel ~/work/zImage -dtb ~/work/vexpress-v2p-ca9.dtb -drive file=~/ext2.img,if=sd,format=raw -append "rootwait root=/dev/nfs console=ttyAMA0 ip=on nfsroot=/srv/rootfs/armhf/ user_debug=31 rw debug" -net nic,model=lan9118 -net user -nographic -s
     ...
     [    2.129955] Run /sbin/init as init process
     [    2.130136]   with arguments:
     [    2.130217]     /sbin/init
     [    2.130280]   with environment:
     [    2.130351]     HOME=/
     [    2.130406]     TERM=linux
     [init] Connect console: OK 
     [init] Mount filesystem: OK 
     [init] Mount filesystem additionaly: OK 
    
     Ubuntu 20.04.3 LTS 10.0.2.15 ttyAMA0
    
     10.0.2.15 login: root (automatic login)
    
     Welcome to Ubuntu 20.04.3 LTS (GNU/Linux 5.10.0+ armv7l)
    
      * Documentation:  https://help.ubuntu.com
      * Management:     https://landscape.canonical.com
      * Support:        https://ubuntu.com/advantage
    
     This system has been minimized by removing packages and content that are
     not required on a system that users do not log into.
    
     To restore this content, you can run the 'unminimize' command.
     Last login: Tue Nov 23 20:14:53 JST 2021 on ttyAMA0
     root@10:~# 
    
  2. SDカードにあるext2ファイルシステムをマウントする

     root@10:~# mount -t ext2 -o loop /dev/mmcblk0 /mnt
     root@10:~# cd /mnt
    
  3. Dirtyのページキャッシュをフラッシュする。 (dirty_expire_centisecs (30s) 待機して書き出しする)

     root@10:~# sync
     root@10:~# echo 3 > /proc/sys/vm/drop_caches
    
  4. ホスト側からGDBでattachする。

     leava@leava-host:~/work$ sudo buildroot/output/host/bin/arm-buildroot-linux-gnueabihf-gdb ./linux/vmlinux 
     GNU gdb (GDB) 8.3.1
     Copyright (C) 2019 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:
     <http://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
     (gdb)         
    
  5. 任意の関数 (ここでは、sys_writeに対して)ブレークポイントを設置する。

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

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

     root@10:~# echo -n "HELLO" >> /mnt/FILE
    

おわりに

本記事では、これから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を用いる方法に変更

参考