拾い物のコンパス

まともに書いたメモ

qemu上でLinuxカーネルを動かしたらエラーメッセージが止まらない問題への調査結果と対処法

qemu上にて自分でビルドしたカーネルを動かしたらエラーメッセージが止まらない.
これに対処するために原因調査,対処方法を探した話.
結果的に対処法は見つかったが,根本的な原因追求はできなかった.
知っている人がいれば情報募集中.

対象者

この記事はqemuLinuxカーネルを動かしたときに下記のようなメッセージが何度も出続ける現象に苦しむ人を対象とする.

process '-/bin/sh' (pid 15) exited. Scheduling for restart.
can't open /dev/tty3: No such file or directory

TL;DR

  • エラーメッセージの原因はinitrd(initramfs)内にあることまで突き止めた
  • vagrantubuntu(恐らくServer版)のinitrdを使えば問題を回避できた
    (initrdを替えられない事情がある人には無用な記事かも)

何があった?

カーネルを自分でビルドして,qemu上で起動したら下記エラーメッセージが止まらない.
メッセージが出すぎてまともにコマンド入力できない.

process '-/bin/sh' (pid 15) exited. Scheduling for restart.
process '-/bin/sh' (pid 16) exited. Scheduling for restart.
process '-/bin/sh' (pid 17) exited. Scheduling for restart.
can't open /dev/tty3: No such file or directory
can't open /dev/tty4: No such file or directory
can't open /dev/tty2: No such file or directory

ググってみると下記コマンドで改善するという情報があった.
$ ln -sf /dev/console /dev/tty3
しかしメッセージが多すぎてコマンドが打てないため,他の方法を探す必要があった.
他の記事を見てもOSアップデートで改善したとかしか出ず,原因まで書いたものを見つけられなかった.
だから自分で調べることにした.

環境構築

まずは今回の現象が再現する環境構築方法を記し,その後回避策を説明する.
手っ取り早く解決したい人はinitrdの用意まで読み飛ばすこと.
エラーメッセージにたどり着くためにまずは起動用のカーネルイメージ(bzImage)をビルドする必要がある.

bzImageのビルド

カーネルは全てビルドすると20GB以上のストレージと8GB以上のメモリ,数時間は必要となり自分の手持ちPCではできない.
ビルド中は数時間掛けてじわじわ使用メモリが増えていき,気づいたときはメモリ切れで強制終了,数時間が水の泡となるのは心に来る(経験談).
しかし考えてみればUbuntuなどでは起動に必要なvmlinuzは20GBも必要ない.
最小構成であればそこまで容量も必要ないのではないかと考えた.
bzImageがあれば最低限起動できるようだったため,シンプルにビルドする方法を選んだ.
カーネルのビルド方法は下記サイトを参考にした.
minimalなlinuxを作りたい、と思ったんだ - livaの雑記帳
Linux を(わりと)シンプルな構成でビルドして Qemu で起動する - Qiita
ビルドしたLinuxカーネルをブートできる最低限の環境を用意する(with Busybox & qemu) - 豆腐の豆腐和え

ビルド手順

このサイトからソースを持ってきた.
使ったバージョンは5.9.1で今は公開されていないが,5.9系ならそこまで違いは無いかと思う.
そこからはポチポチコマンドを実行していく.
makeするために必要なパッケージがあるかもしれないから,それはエラーメッセージを参照しながら補完してほしい.

$ tar xvf linux-5.9.1.tar.xz
$ cd linux-5.9.1
$ make allnoconfig
$ make menuconfig
    [*] 64-bit kernel

    -> General setup
    [*] Initial RAM filesystem and RAM disk (initramfs/initrd) support
    -> General setup
      -> Configure standard kernel features
    [*] Enable support for printk

    -> Executable file formats / Emulations
    [*] Kernel support for ELF binaries

    -> Device Drivers
      -> Character devices
    [*] Enable TTY

    -> Device Drivers
      -> Character devices
        -> Serial drivers
    [*] 8250/16550 and compatible serial support
    [*]   Console on 8250/16550 and compatible serial port
$ make -j4 bzImage
$ find ./ -name bzImage
./arch/i386/boot/bzImage
./arch/x86_64/boot/bzImage
./arch/x86/boot/bzImage
$ mkdir work
$ cp ./arch/x86_64/boot/bzImage work
$ cd work

これでbzImageの作成は完了.
参考サイトでは起動できるところまでだったり(カーネルが起動し,panicするところまで),busyboxの用意があったりでややこしい.

ちゃんとしたinitrdを用意してやれば小さなLinuxとして使えるようだ.
というわけで今回使ったinitramfs-linux.imgは普段使っているLinux(Arch Linux, Kernel5.9.11-arch2-1)から抜き出した.

$ cp /boot/initramfs-linux.img work/initramfs-linux.img
$ qemu-system-x86_64 -kernel ./bzImage -initrd ./initramfs-linux.img -nographic -append "console=ttyS0"

実行後ブートメッセージが出力されて起動する.
panicも起こらない.
やった!悲劇はここからだ!!
エラーメッセージが止まらない・・・

例のエラーメッセージ

process '-/bin/sh' (pid 18) exited. Scheduling for restart.
process '-/bin/sh' (pid 19) exited. Scheduling for restart.
process '-/bin/sh' (pid 20) exited. Scheduling for restart.
can't open /dev/tty2: No such file or directory
can't open /dev/tty3: No such file or directory
can't open /dev/tty4: No such file or directory

調査する前にこの状態だとまともにコマンド入力もできないから,ホスト側から他のシェルを使ってqemuを停止.

$ kill -KILL `ps aux | grep qemu | awk -F" " '{print $2}' | head -n1`

調査

メッセージ内容からすると/dev/tty(2,3,4)が無いからメッセージが出力されているように見られる. まず調べるべきはttyデバイスが作成されるタイミング.
怪しいのは

辺りかと思い,詳細を調べた.
補足するとinitrdは起動時に読み込まれるファイルシステムinitrdの中にある最初に実行されるプログラムがinittab/systemdとなる.
昔はinittabだったようだが,今はsystemdがこの役割を担っているようだ.
/sbin/initsystemdへのリンクになっていたのを知ったときは訳がわからなかった.(感想)

しかしここで行き詰まった.
ttyデバイスはシェルで接続した瞬間作成されるようだ. よって問題のメッセージを読み解くと

  1. 自動で実行されるプロセスが/bin/shを実行
  2. 異常終了
  3. 再実行がスケジュール

/dev/ttyがなくて/bin/shが異常終了するのか,他の要因があるのかはわからない.
このプロセスがスケジューリングされるのはinitrd内にあるinittab/systemdで自動的に時実行されるプロセス(inittabならrc.S,systemdならtarget)と推察される.
つまり問題となるプロセスを登録していないinitrdがあれば問題のメッセージは出力されないのではないか.
というわけでvagrantubuntu(Server)からinitramfs.imgを取り出して試してみる.
ubuntuを選んだ理由はCUI起動のみという点でビルドしたカーネルイメージと環境が近いと考えたから.

initrdの用意(回避策はここから)

まずは初期設定.

$ vagrant init ubuntu/trusty64

作成されたVagrantfileにファイル取り出し用の共有フォルダを設定する.
config.vm.synced_folderの箇所を編集する.
ルールはconfig.vm.synced_folder <ホストのディレクトリパス> <ゲスト内でのディレクトリパス>
パスは各自の環境を入力.

SHELLの箇所に編集されたところがあるが,今回とは関係ないからコメントアウトのままでOK.

# -*- mode: ruby -*-
# vi: set ft=ruby :

# All Vagrant configuration is done below. The "2" in Vagrant.configure
# configures the configuration version (we support older styles for
# backwards compatibility). Please don't change it unless you know what
# you're doing.
Vagrant.configure("2") do |config|
  # The most common configuration options are documented and commented below.
  # For a complete reference, please see the online documentation at
  # https://docs.vagrantup.com.

  # Every Vagrant development environment requires a box. You can search for
  # boxes at https://vagrantcloud.com/search.
  config.vm.box = "ubuntu/trusty64"

  # Disable automatic box update checking. If you disable this, then
  # boxes will only be checked for updates when the user runs
  # `vagrant box outdated`. This is not recommended.
  # config.vm.box_check_update = false

  # Create a forwarded port mapping which allows access to a specific port
  # within the machine from a port on the host machine. In the example below,
  # accessing "localhost:8080" will access port 80 on the guest machine.
  # NOTE: This will enable public access to the opened port
  # config.vm.network "forwarded_port", guest: 80, host: 8080

  # Create a forwarded port mapping which allows access to a specific port
  # within the machine from a port on the host machine and only allow access
  # via 127.0.0.1 to disable public access
  # config.vm.network "forwarded_port", guest: 80, host: 8080, host_ip: "127.0.0.1"

  # Create a private network, which allows host-only access to the machine
  # using a specific IP.
  # config.vm.network "private_network", ip: "192.168.33.10"

  # Create a public network, which generally matched to bridged network.
  # Bridged networks make the machine appear as another physical device on
  # your network.
  # config.vm.network "public_network"

  # Share an additional folder to the guest VM. The first argument is
  # the path on the host to the actual folder. The second argument is
  # the path on the guest to mount the folder. And the optional third
  # argument is a set of non-required options.
  config.vm.synced_folder "/home/<user>/kernel-5.9.1/work/", "/shared"

  # 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
  #   vb.gui = true
  #
  #   # Customize the amount of memory on the VM:
  #   vb.memory = "1024"
  # end
  #
  # View the documentation for the provider you are using for more
  # information on available options.

  # Enable provisioning with a shell script. Additional provisioners such as
  # Ansible, Chef, Docker, Puppet and Salt are also available. Please see the
  # documentation for more information about their specific syntax and use.
  # config.vm.provision "shell", inline: <<-SHELL
  #   yes | apt install autoconf bison libtool automake autopoint pkg-config
  # SHELL
end

編集が終わったら起動する.

$ vagrant up
$ vagrant ssh
(vagrant)$ uname -a
Linux vagrant-ubuntu-trusty-64 3.13.0-170-generic #220-Ubuntu SMP Thu May 9 12:40:49 UTC 2019 x86_64 x86_64 x86_64 GNU/Linux
(vagrant)$ cp /boot/initrd.img-3.13.0-170-generic /shared

これで指定した共有フォルダ内にimgファイルが保存される.
保存したimgを使ってカーネルイメージを再度実行してみる.
オリジナルのままだと名前が長いから改名した.

$ mv ./initrd.img-3.13.0-170-generic initrd_ubuntu.img
$ qemu-system-x86_64 -kernel ./bzImage -initrd ./initrd_ubuntu.img -nographic -append "console=ttyS0"

メッセージは出なくなった!

結論

問題のエラーメッセージはinitrd内(inittab/systemdが有力)に登録されたプロセスが原因と推測される.
問題となるプロセスが登録されていないinitrdがあればメッセージを回避できる.

所感

カーネルをビルドして使うなら用途に応じてinitrdも作り込まないといけないってことか.

参考

linux - can't open /dev/tty(2,3,4). No such file or directory - Stack Overflow

minimalなlinuxを作りたい、と思ったんだ - livaの雑記帳

Linux を(わりと)シンプルな構成でビルドして Qemu で起動する - Qiita

ビルドしたLinuxカーネルをブートできる最低限の環境を用意する(with Busybox & qemu) - 豆腐の豆腐和え

The Linux Kernel Archives