OpenBSD with qemu
久しぶりに実家へ行ったら、縁側に枯草が干してあった。義母さん、これ何ですか?って、思わず聞いてしまったぞ。
お灸にするよもぎとの事。デイサービスで行っている、足が痛い、膝が痛いと言う仲間に勧められて、貰ってきたとの事。コエンザイムじゃ駄目なんですか?って言うと、ハナタカさんの女房が、すり減った関節は、何やったって回復しないわよ。富士フィルムの研究員に聞いてみたいぞ。
医療大学がユーチューブに上げていた。他にも色々見つかる。これって、民間療法だな。 東洋医学と言えば、中国4000年の歴史を誇る、漢方薬。
今猛威を奮っている新型コロナウイルス(COVID-19)に効くやつは無いんですか? TVに出て来る映像は、西洋医学ばかり。 もう漢方では太刀打ち出来ないのかな?
オイラーが幼少の頃は体が弱く、よく風邪をひいた。おばちゃんが、ほうずきを干したやつを、煎じて飲まされたな。それから、体が丈夫になるようにと、ヤツメウナギやらマムシの黒焼きやら、、効いたような効かなかったような。。。
guile in WSL
先日debianが10.3になったんで、Windows10のWSLに入れてるdebianもupgradeしたんよ。 元々このシステムはミニマムなやつに、非常用のemacsぐらいしか入れていないので、upgradeされる物も極わずか。
で、upgradeされるパッケージの中に、何故かlibguileが候補として挙がってた。何かにくっついて入ってきたのもだな。ならば、ちょいとguileを足せば、オイラーも恩恵にあずかれるのではなかろうか。入れてあげた。
無事に動いたんで、WSLがどれぐらい遅いか確認してみた。
scheme@(guile-user)> (time (fibo 40)) clock utime stime cutime cstime gctime 16.29 16.17 0.02 0.00 0.00 0.00 $2 = 165580141
guile-2.2.4の結果です。ちなみにCentOSのguile-3.0でjit off の時の結果は、14.75だった。自前でguileをコンパイルして3.0にすれば、jitのターボチャージャーが働いて、5.0ぐらいにはなろうと予想する。
けどね、それをやるにはコンパイル環境やら用意しなきゃならん。gule-devを入れようとしたら、gccがわんさかと押し寄せて来る事が分かったので、入れるの止めた。
今入ってるのは、非常用のscheme環境と割り切っておこう。そうしないと取り留めつかなくなりますからね。
qemu on OpenBSD
以前やろうとして中断してたやつをやってみる。OpenBSDの中にqemuを入れて、そこでゲストOSとしてOpneBSDを動かしkernelの観察日記をつけるあれ。
環境はdebianに作ってあったので、それをそのまま持ってきて起動。
o32$ ./boot qemu-system-i386: cannot set up guest memory 'pc.ram': Cannot allocate memory
メモリー不足で動かんってのが、前回までの粗筋。さてどうする?
qemu-system-i386 -m 256 -net nic -net user,hostfwd=tcp::2022-:22 disk
メモリーを256Mも割り当てしなくても十分に動くでしょ。128Mにしたら起動した。 でも遅いな。 64Bitのシステムなら、もっと早く動くんではなかろうか?
OpenBSD(amd64) with qemu
果たして64Bitシステムで32Bit用のカーネルをコンパイル出来るのか? FAQを調べてみたら、どうやら出来そうだ。これはもうやってみる鹿。カーネルとかをコンパイルするんなら、wobjのグループに属してるべきなんてtipsを知ったぞ。
ちょいと過去の記事を漁ってみたら、 2015年にOpenBSD 探検って取り上げてる。それから2018年にもAudio in OpenBSDってので、qumeで動かし、kernelの挙動を確認するやつをね。その時は、OpenBSDでコンパイルしたやつをウブに持って行って、そこで動かしてた。
今度は、そんな面倒かけずにOpenBSD内で完結させる作戦だ。まずはコンパイルだな。
obsd# cd /usr/src/sys/arch/i386/conf obsd# cp GENERIC DEBUG obsd# vi DEB makeoptions DEBUG="-g" ## <-- ADD this line obsd# config DEB obsd# cd ../compile/DEB obsd# make depend obsd# COPTS="-O0" make
取り合えずdebug用のカーネルをコンパイル。エラーになったぞ。
#radeondrm* at pci? # ATI Radeon DRM driver #drm0 at radeondrm? primary 1 #drm* at radeondrm? #wsdisplay0 at radeondrm? primary 1 #wsdisplay* at radeondrm? mux -1
こんな具合に、RedeonをGENERICからコメントアウトしたよ。そしたらコンパイル成功した。 本当にi386用が出来ているか、確認。
ob$ file bsd.gdb bsd.gdb: ELF 32-bit LSB executable, Intel 80386, version 1
i386が進化してamd64になったんで、大丈夫なんだな。49Mの巨大なサイズだったよ。 Makefileを探ってみたら、
.if ${MACHINE} == "amd64" CFLAGS+= -m32 AFLAGS+= -m32 LDFLAGS= -melf_i386 LINKFLAGS+= ${LDFLAGS} .endif
こんな記述を発見。clangなコンパイラーでも、こういう事が出来るのね。こういうのクロスコンパイラーと呼んでいいものですか?
次は、i386の入ったdiskを作る。
qemu-img create -f qcow2 disk 1G qemu-system-i386 -m 256M -net nic -net user -cdrom install66.iso disk
心配してた256M問題も、何なくクリアして動いてくれた。X関係は入れないので(それとソース関係も)、1Gあれば足りるでしょ。swapは無くてもいいんだけど、警告が出てくるのがいやなので、猫の額程(50m)を割り当てたよ。
尚、初回の起動は、sshdのキーを作成したり、デフォルトのダエモン君が起動したりするので、いやになる程待たされる。一息ついてくるのが良い。
これで、起動して、不必要なデーモンが立ち上がらないように設定。
vm$ cat /etc/rc.conf.local smtpd_flags="NO" ntpd_flags="NO" slaacd_flags="NO" sndiod_flags="NO" pflogd_flags="NO"
それから、起動時にlibとkernelの再配置をしないようにコメントアウト。これをしないと、loginプロンプトが出てくるまで、膨大な時間がかかる。
vm$ grep reorder /etc/rc reorder_libs() { echo -n 'reordering libraries:' ### reorder_libs ### /usr/libexec/reorder_kernel &
大体、こんな環境で動いている。
vm$ uname -a OpenBSD vm.my.domain 6.6 DEB#0 i386 vm$ ps awx PID TT STAT TIME COMMAND 1 ?? I 0:01.19 /sbin/init 92597 ?? IU 0:00.08 dhclient: em0 [priv] (dhclient) 8726 ?? Ip 0:00.10 dhclient: em0 (dhclient) 31432 ?? IpU 0:00.30 syslogd: [priv] (syslogd) 36726 ?? Sp 0:00.62 /usr/sbin/syslogd 72106 ?? I 0:00.15 /usr/sbin/sshd 25249 ?? Sp 0:00.43 /usr/sbin/cron 80478 ?? I 0:01.95 sshd: sakae [priv] (sshd) 84518 ?? S 0:01.23 sshd: sakae@ttyp0 (sshd) 62837 p0 Sp 0:00.51 -ksh (ksh) 22034 p0 R+pU 0:00.07 ps -awx 6582 C0 I+pU 0:00.19 /usr/libexec/getty std.9600 ttyC0 85758 C1 I+pU 0:00.26 /usr/libexec/getty std.9600 ttyC1 25696 C5 I+pU 0:00.23 /usr/libexec/getty std.9600 ttyC5 vm$ df -k Filesystem 1K-blocks Used Avail Capacity Mounted on /dev/wd0a 1014302 758314 205274 79% /
あっ、大事なカーネルの事を忘れていた。bsd.oldは、インストール時のオリジナルな奴。bsdの方は、今回作成したやつ。drmな奴を削ったにも関わらずに肥大化してるのはdebug情報を内在してるからかな。
vm$ ls -l /bsd* -rwx------ 2 root sakae 17210279 Feb 12 06:22 /bsd -rwx------ 2 root sakae 17210279 Feb 12 06:22 /bsd.booted -rwx------ 1 root wheel 13942980 Feb 11 15:53 /bsd.old -rw------- 1 root wheel 8860963 Feb 11 15:41 /bsd.rd
起動は、下記スクリプト
ob$ cat gdb-boot qemu-system-i386 -m 256 -s -net nic -net user,hostfwd=tcp::2022-:22 disk
何が味噌と言うか、gdbなんだようと言うのは、さりげなく紛れ込んでいる、-s にある。
これ、OSが起動中のtopで捉えたcpu利用状況。
load averages: 0.74, 0.21, 0.08 ob.localdomain 06:09:26 36 processes: 35 idle, 1 on processor up 0:35 CPU0: 3.8% user, 0.0% nice, 0.4% sys, 0.0% spin, 0.0% intr, 95.8% idle CPU1: 37.1% user, 0.0% nice, 2.8% sys, 0.0% spin, 0.0% intr, 60.1% idle CPU2: 37.5% user, 0.0% nice, 2.6% sys, 0.0% spin, 0.0% intr, 59.9% idle CPU3: 4.6% user, 0.0% nice, 1.0% sys, 0.0% spin, 0.0% intr, 94.4% idle Memory: Real: 185M/975M act/tot Free: 991M Cache: 393M Swap: 0K/518M
cpu1,2に跨って、負荷がかかっている。qemuは負荷が重いので協力して箏に当たっているのかな? kernelのコンパイルの時は、cpu1,2,3の順番でプロセスが動いていたぞ。cpu0は、マスターなんで、指図するだけか。
qemuのあれ
pkgから入れたqemuのバージョン
ob$ qemu-system-i386 -version QEMU emulator version 4.1.0 Copyright (c) 2003-2019 Fabrice Bellard and the QEMU Project developers
そして、manからgdb関係のオプションを抜粋
-S Do not start CPU at startup (you must type 'c' in the monitor). -gdb dev Wait for gdb connection on device dev. Typical connections will likely be TCP-based, but also UDP, pseudo TTY, or even stdio are reasonable use case. The latter is allowing to start QEMU from within gdb and establish the connection via a pipe: (gdb) target remote | exec qemu-system-i386 -gdb stdio ... -s Shorthand for -gdb tcp::1234, i.e. open a gdbserver on TCP port 1234.
qemuの起動スクリプトに、-S(大文字に注意)を付けると、CPUの電源onから、gdbの配下に入る事が出来る。すなわちOSの起動を追跡出来るんだ。(まあ、根性無いからやらないけど)
遊び方
先に、gdb-bootでOSを起動しておく。loginになるまで時間がかかる(約2分)。 適当にログイン。qemuのコンソールは、Xの上に立ち上がるぞ。そこにloginが無難だ。
ssh -p2022 localhost
するって手もあるけど、gdbが動いちゃうとTCP通信が途絶しちゃうんで注意。
やおら、
ob$ pwd /usr/obj/sys/arch/i386/compile/DEB ob$ cat .gdbinit target remote :1234
gdbを起動した時の自動実行ファイルを用意しとく。このdirに降りてくるには、wobjのグループになってれば良い。
ob$ gdb -q bsd.gdb Reading symbols from bsd.gdb...done. acpicpu_idle () at /usr/src/sys/dev/acpi/acpicpu.c:1188 1188 break; (gdb) b microtime Breakpoint 1 at 0xd08e6956: file /usr/src/sys/kern/kern_tc.c, line 234. (gdb) c Continuing.
gdbを起動すると、kernelの空回りルーチンの何処かで止まる。止めたい所にBPを置いてから継続。
Breakpoint 1, microtime (tvp=0xf3638bb8) at /usr/src/sys/kern/kern_tc.c:234 234 bintime(&bt); (gdb) bt #0 microtime (tvp=0xf3638bb8) at /usr/src/sys/kern/kern_tc.c:234 #1 0xd068bf7d in sys_gettimeofday (p=0xd1c39900, v=0xf3638cb4, retval=0xf3638cac) at /usr/src/sys/kern/kern_time.c:336 #2 0xd06bae59 in mi_syscall (p=0xd1c39900, code=67, callp=0xd10f91cc <sysent+804>, argp=0xf3638cb4, retval=0xf3638cac) at /usr/src/sys/sys/syscall_mi.h:92 #3 0xd06bab16 in syscall (frame=0xf3638cf0) at /usr/src/sys/arch/i386/i386/trap.c:597 #4 0xd09afd1d in Xsyscall_untramp () #5 0xf3638cf0 in ?? () #6 0x1aa1a77b in ?? () Backtrace stopped: previous frame inner to this frame (corrupt stack?)
loginした側で、dateって叩いてみたら、gettimeofdayのシステムコールが発せられ、その中で使われている関数に飛んできた。後は普通のアプリを探るのと一緒だ。
間違った所にBPを置いてしまって、さっぱりHitしない時は、C-c C-c すればgdbが再度現れるんで、正しい所にBPを置けばよい。使い終わったらdetachすれば良い。
ulimit
i386版でqemuを起動した時、メモリー足りないが出てた。リソース配布状況を調べてみる。
OpenBSD(amd64)
ob$ ulimit -a time(cpu-seconds) unlimited file(blocks) unlimited coredump(blocks) unlimited data(kbytes) 1572864 stack(kbytes) 4096 lockedmem(kbytes) 668358 memory(kbytes) 2001980 nofiles(descriptors) 512 processes 256
OpenBSD(i386)
o32$ ulimit -a time(cpu-seconds) unlimited file(blocks) unlimited coredump(blocks) unlimited data(kbytes) 524288 stack(kbytes) 4096 lockedmem(kbytes) 680932 memory(kbytes) 2039272 nofiles(descriptors) 512 processes 128
どうも、マシンによってえこひいきが有るな。特にdataの割り当て。と言う事で、ulimit -d 1572864 して、増やしてあげたら、無事に起動した。やれやれ。
bintime
sys_gettimeofdayが利用してる内部ルーチンにBPを置いて追跡。
(gdb) bt #0 bintime (bt=0xf1d108e0) at /usr/src/sys/kern/kern_tc.c:210 #1 0xd08e6967 in microtime (tvp=0xf1d10938) at /usr/src/sys/kern/kern_tc.c:234 #2 0xd068bf7d in sys_gettimeofday (p=0xd17c3608, v=0xf1d10a34, retval=0xf1d10a2c) at /usr/src/sys/kern/kern_time.c:336 :
この中では、自由変数(グローバル変数ね)が、いきなり参照されてる。(kern_tc.c)どんなデータかひとまず確認。どうやら、時間関係を保持してる共通の構造体だな。th_boottimeには、OSの起動時間が入っていた。th_microtimeなんかは、現在の時刻データだ。
(gdb) p *timehands $3 = { th_counter = 0xd1103ce4 <hpet_timecounter>, th_adjtimedelta = 0, th_adjustment = 0, th_scale = 184467440736, th_offset_count = 4121071613, th_boottime = { sec = 1581542082, frac = 15284937090680771013 }, th_offset = { sec = 585, frac = 15469855674470014203 }, th_microtime = { tv_sec = 1581542668, tv_usec = 667220 }, th_nanotime = { tv_sec = 1581542668, tv_nsec = 667220656 }, th_generation = 15232, th_next = 0xd1105d54 <th0> }
注意深く観察してると、10秒間隔ぐらいで呼び出されている。ひょっとしてcronの仕業? 元を断ってみるって事でcronを停止させた。
けど、やっぱり呼び出されている。でも、呼び出しのルーツが違う。カーネル内から呼び出されている。disk関係なんで、syncする為の内部スレッドからかな。
(gdb) bt #0 bintime (bt=0xf1cddd80) at /usr/src/sys/kern/kern_tc.c:210 #1 0xd08e6907 in nanotime (tsp=0xf1cdddb8) at /usr/src/sys/kern/kern_tc.c:225 #2 0xd0cc41e9 in enqueue_randomness (val=40131) at /usr/src/sys/dev/rnd.c:294 #3 0xd04ca347 in disk_unbusy (diskp=0xd180b030, bcount=2048, blkno=6592, read=0) at /usr/src/sys/kern/subr_disk.c:1273 #4 0xd05a2f09 in wddone (v=0xd180b000) at /usr/src/sys/dev/ata/wd.c:571 #5 0xd0dfb398 in wdc_ata_bio_done (chp=0xd18230dc, xfer=0xd168bf60) at /usr/src/sys/dev/ata/ata_wdc.c:606 #6 0xd0df9b20 in wdc_ata_bio_intr (chp=0xd18230dc, xfer=0xd168bf60, irq=1) at /usr/src/sys/dev/ata/ata_wdc.c:553 #7 0xd04332eb in wdcintr (arg=0xd18230dc) at /usr/src/sys/dev/ic/wdc.c:968 #8 0xd078cee5 in pciide_compat_intr (arg=0xd18230dc) at /usr/src/sys/dev/pci/pciide.c:1945 #9 0xd06c69ab in intr_handler (frame=0xf1cde0a8, ih=0xd181b0c0) at /usr/src/sys/arch/i386/i386/machdep.c:4026 #10 0xd09b2a86 in Xintr_ioapic2_untramp ()
これ以外にもsys_gettimeofdayが呼ばれているなあ。これユーザーランド側からの呼び出しだ。 はてcron以外に誰が呼び出している?思い付くのは、sshdが利用してるNICのハートビート間隔の確認かな?
kern_tc.c
冒頭から見て行くと、色々なヒントが見つかる。例えば
int sysctl_tc_hardware(void *, size_t *, void *, size_t); int sysctl_tc_choice(void *, size_t *, void *, size_t);
こんなプロトタイプ宣言が有る。ここから、
ob$ sysctl -a | grep choice kern.timecounter.choice=i8254(0) acpihpet0(1000) tsc(2000) acpitimer0(1000) ob$ sysctl -a | grep hardware kern.timecounter.hardware=tsc
こんな情報を引き出せた。意味する所は、カーネルが使う時間用のカウンターの選択肢は色々あるよ。その中で一番正確そうな、tscってのを使う事にしましたって事だな。
/* * Locks used to protect struct members, global variables in this file: * I immutable after initialization * t tc_lock * w windup_mtx */ struct timehands { /* These fields must be initialized by the driver. */ struct timecounter *th_counter; /* [w] */ int64_t th_adjtimedelta; /* [tw] */ int64_t th_adjustment; /* [w] */ u_int64_t th_scale; /* [w] */ u_int th_offset_count; /* [w] */ struct bintime th_boottime; /* [tw] */ struct bintime th_offset; /* [w] */ struct timeval th_microtime; /* [w] */ struct timespec th_nanotime; /* [w] */ /* Fields not to be copied in tc_windup start with th_generation. */ volatile u_int th_generation; /* [w] */ struct timehands *th_next; /* [I] */ };
gdbで参照してみた構造体の意味付け。
数有るカウンターからtscが選ばれていた。ここら辺になって来ると、マシンのアーキテクチャに依存するんで、/sys/arch/i386に移動して、探ってみる。
ob$ find . -name '*.c' | xargs grep tsc ./i386/codepatch.c: * Copyright (c) 2014-2015 Stefan Fritsch <sf@sfritsch.de> ./i386/db_disasm.c: { "invlpg", FALSE, NONE, op2(MEx,2), "swapgs\0rdtscp" }, ./i386/db_disasm.c:/*31*/ { "rdtsc", FALSE, NONE, 0, 0 }, ./i386/pctr.c:#define usetsc (cpu_feature & CPUID_TSC) ./i386/pctr.c: st->pctr_tsc = rdtsc(); ./i386/pctr.c: st->pctr_tsc = rdtsc(); ./i386/pctr.c: } else if (usetsc) { ./i386/pctr.c: if (usetsc) ./i386/pctr.c: st->pctr_tsc = rdtsc(); ./i386/ucode.c: * Copyright (c) 2018 Stefan Fritsch <fritsch@genua.de> ./isa/clock.c: __asm volatile("rdtsc" : "=A" (last_count)); ./isa/clock.c: __asm volatile("rdtsc" : "=A" (count));
i386/pctr.cが怪しそう。
#define usetsc (cpu_feature & CPUID_TSC)
ほー、CPUに機能が搭載されてるかで利用の可否を決めてるっぽい。ってか、ない袖は振れぬ。
ob$ dmesg | grep TSC cpu0: FPU,VME,DE,PSE,TSC,.... :
ちゃんと機能が搭載されてました。
いやぁ、楽しいねぇ。たった一つのシステムコールから、こういう拡がりを得られるなんて。