qemu上でLinuxカーネルを動かしたらエラーメッセージが止まらない問題への調査結果と対処法
qemu上にて自分でビルドしたカーネルを動かしたらエラーメッセージが止まらない.
これに対処するために原因調査,対処方法を探した話.
結果的に対処法は見つかったが,根本的な原因追求はできなかった.
知っている人がいれば情報募集中.
対象者
この記事はqemuでLinuxカーネルを動かしたときに下記のようなメッセージが何度も出続ける現象に苦しむ人を対象とする.
process '-/bin/sh' (pid 15) exited. Scheduling for restart. can't open /dev/tty3: No such file or directory
TL;DR
- エラーメッセージの原因はinitrd(initramfs)内にあることまで突き止めた
- vagrantのubuntu(恐らく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/init
がsystemd
へのリンクになっていたのを知ったときは訳がわからなかった.(感想)
しかしここで行き詰まった.
ttyデバイスはシェルで接続した瞬間作成されるようだ.
よって問題のメッセージを読み解くと
- 自動で実行されるプロセスが
/bin/sh
を実行 - 異常終了
- 再実行がスケジュール
/dev/tty
がなくて/bin/sh
が異常終了するのか,他の要因があるのかはわからない.
このプロセスがスケジューリングされるのはinitrd内にあるinittab/systemd
で自動的に時実行されるプロセス(inittabならrc.S,systemdならtarget)と推察される.
つまり問題となるプロセスを登録していないinitrd
があれば問題のメッセージは出力されないのではないか.
というわけでvagrantのubuntu(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) - 豆腐の豆腐和え