xv6-riscv
inittodr
前回の疑問、risc-VなOSは、どうやって実時刻を取り込むか? そのヒントが、起動時の関数、inittodrにありそうなので、詳細に観察する。
at riscV
(gdb) bt #0 inittodr (base=<optimized out>) at /usr/src/sys/kern/kern_time.c:925 #1 0xffffffc000349004 in ffs_mountroot () at /usr/src/sys/ufs/ffs/ffs_vfsops.c:193 #2 0xffffffc00035d88a in dk_mountroot () at /usr/src/sys/kern/subr_disk.c:1346 #3 0xffffffc00030af30 in main (framep=<optimized out>) at /usr/src/sys/kern/init_main.c:456
これ、前回驚いた部分だ。マウントなんてのが出てきてるから。でもソースをじっくり読むと、このマウントの最後で盲腸のようにinittodrが呼び出されてる。そう、最後にファイルシステムにwriteした時刻を引数にして呼出。
(gdb) p ts $2 = {tv_sec = 1670307502, tv_nsec = 741576000} (gdb) p rtctime $3 = {tv_sec = 1670307502, tv_usec = 741576} (gdb) p base $4 = 1670221283 (gdb) p deltat $5 = 86219
つらつら中を進めていくと、時刻に関する変数が出て来る。このEPOC数は人間には意味不なんで、dateに頼って、人間可読にさせる。リナだと下記のように格好悪い指定方法を取る。
[sakae@arch ~]$ date --date='@1670307502' Tue Dec 6 03:18:22 PM JST 2022 [sakae@arch ~]$ date --date='@1670221283' Mon Dec 5 03:21:23 PM JST 2022
Dec 5 は、最終利用時間。そしてDec 6 は現在のオペレーション時間。その差が3日以上だと、飲み屋のオネーチャン風に嫌味を言うロジックになってる。
riscv# sysctl -a | grep boot kern.boottime=Tue Dec 6 15:18:21 2022
んでもって、OSは、最終的にboot時刻を確定させたって訳。
ここまではいいんだけど、リアル現実時刻をどうやって入手してる。超怪しいのは下記の関数。この関数にBPを置いてもヒットしなかったので(往々にして、そういう事は有る)ステップで歩兵してみた。
(gdb) 887 todr_gettime(todr_handle, &rtctime) != 0 || (gdb) s gfrtc_gettime (handle=0xffffffc002da24d8, tv=0xffffffc000805cb0) at /usr/src/sys/dev/fdt/gfrtc.c:66 66 struct gfrtc_softc *sc = handle->cookie;
ガーン、変な所へ連れて怒れたぞ。そのおかげで、面白い景色が見られたぞ。
/* * Google Goldfish virtual real-time clock described in * https://android.googlesource.com/platform/external/qemu/+/master/docs/GOLDFISH-VIRTUAL-HARDWARE.TXT */ int gfrtc_gettime(todr_chip_handle_t handle, struct timeval *tv) { struct gfrtc_softc *sc = handle->cookie; uint64_t tl, th; tl = bus_space_read_4(sc->sc_iot, sc->sc_ioh, TIME_LOW); th = bus_space_read_4(sc->sc_iot, sc->sc_ioh, TIME_HIGH); NSEC_TO_TIMEVAL((th << 32) | tl, tv); return 0; }
金魚さん文書が発掘された。ググル様がドロイドのqemu用に開発した、仮想ハードとURLは言ってる。
今流行のデジタル・ツインだな。現実をコンピューターの中に再現する事を、このように表現するらしい。DXなんて言葉も流行ってるけど(政府のお墨つき)、脅迫観念にかられるように扇動してるんだな。
まあ、ここまで辿りつけば、後は簡単。 time.h でも見ておけば良い。
static inline void NSEC_TO_TIMEVAL(uint64_t ns, struct timeval *tv) { tv->tv_sec = ns / 1000000000L; tv->tv_usec = (ns % 1000000000L) / 1000; }
こんなカウンターなんて現実に有るのか? お空を飛び回るGPS衛星に搭載されてるに違いない。 ナノセコンド分解能で、1970-01-01 からの経過時間をカウントする奴ね。
電波は真空中では、1nsで約30cm進む。正確に時間を測定出来れば、それを距離に容易に換算できる。これがGPSの原理。余談になるけど、わざわざ真空中と断ったのには裏がある。銅線の中だと1nsで約20cmしか進まないんだ。真空中との速度の比率を電波業界では、短縮率なんて呼んでいる。世界中の海底に張り巡らされている光ファイバーの短縮率はいかに? ゼロ遅延・低遅延 に答が出てた。この海底ケーブルにもGAFAMの魔の手が。。。
最近、車の自動運転が持て囃されている。30cmなんて分解能じゃ、脱輪しちゃうぞ。どうする? 何回も測定して平均を取れば、精度は向上する。測定回数の平方根に比例して精度向上。 統計処理で精度を向上させましたって、偉そうに言うけど、中心極限定理とか煙にまく言葉が出てくるけど、結局、これが原理さ。
at i386
古典的な糞石界隈ってかパソコンでは、どうなってる。まずは気になる時計の読み出し変換関係だな。
(gdb) p *todr_handle $4 = {cookie = 0x0, bus_cookie = 0x0, todr_gettime = 0xd0676cf0 <rtcgettime>, todr_settime = 0xd0676e20 <rtcsettime>, todr_setwen = 0x0}
/usr/src/sys/arch/i386/isa/clock.cに、rtc関係の定義が有った。
extern todr_chip_handle_t todr_handle; struct todr_chip_handle rtc_todr; void rtcinit(void) { rtc_todr.todr_gettime = rtcgettime; rtc_todr.todr_settime = rtcsettime; todr_handle = &rtc_todr; }
同じ所に、読み出し関数が。
int rtcgettime(struct todr_chip_handle *handle, struct timeval *tv) { : dt.dt_year = clock_expandyear(bcdtobin(rtclk[MC_YEAR])); tv->tv_sec = clock_ymdhms_to_secs(&dt) - utc_offset; tv->tv_usec = 0; return 0;
気になる clock_ymdhms_to_secs
関数は、 kern/clock_subr.c
に有った。何が気になるって、うるう秒の処理をしてるかだ? してなかった。でもさすがに、うるう年の処理はしてた。ロジックでちゃんと記述出来るからね。
その点、うるう秒は地球の回転の気分次第だからね。ロジックで表わせないのよ。IT業界は、このうるう秒を忌み嫌って、廃止しろと盛んに言ってる。それで、何年か先までは、うるう秒を実施しない確約を取り付けたそうだ。 うるう秒、2035年までに廃止へ おまけかどうかは知らないけれど、夏時間ってのも廃止に舵をきったみたいだ。コンピュータにとっては、はなはだ迷惑な制度だからね。変な理屈を付けて、正統化するのさ。
繋がり探求
riscvのinittodrをやった時、いきなり金魚RTCが出てきてた。どういう仕組み? i386のそれで遊んでいる時、ハンドルを調べる知恵がついたので、同様にやってみた。
(gdb) p *todr_handle $2 = { cookie = 0xffffffc002da2480, bus_cookie = 0x0, todr_gettime = 0xffffffc00031a924 <gfrtc_gettime>, todr_settime = 0xffffffc00031a9aa <gfrtc_settime>, todr_setwen = 0x0 }
出てきましたねぇ。じゃ、これがどんなタイミングでセットされる? arch/riscvの下を漁ってもヒットしない。ならば、watch todr_handle
する鹿。
simplebus1 at mainbus0: "soc" syscon0 at simplebus1: "poweroff" syscon1 at simplebus1: "reboot" syscon2 at simplebus1: "test" plic0 at simplebus1 gfrtc0 at simplebus1
起動メッセージをじっくり読めば出てきているじゃん。
#0 0xffffffc00031a90c in gfrtc_attach (parent=<optimized out>, self=<optimized out>, aux=<optimized out>) at /usr/src/sys/dev/fdt/gfrtc.c:116 #1 0xffffffc0002e6c9a in config_attach (parent=0xffffffc002da0800, match=<optimized out>, aux=0xffffffc000805b98, print=0xffffffc00029cce4 <simplebus_print>) at /usr/src/sys/kern/subr_autoconf.c:412 #2 0xffffffc00029cc30 in simplebus_attach_node (self=<optimized out>, node=<optimized out>) at /usr/src/sys/arch/riscv64/dev/simplebus.c:241 #3 0xffffffc00029c698 in simplebus_attach (parent=<optimized out>, self=0xffffffc002da0800, aux=0xffffffc000805d10) at /usr/src/sys/arch/riscv64/dev/simplebus.c:121 #4 0xffffffc0002e6c9a in config_attach (parent=0xffffffc002da4000, match=<optimized out>, aux=0xffffffc000805d10, print=0xffffffc000364a42 <mainbus_print>) at /usr/src/sys/kern/subr_autoconf.c:412 #5 0xffffffc000364964 in mainbus_attach_node (self=0xffffffc002da4000, node=1364, submatch=0xffffffc000364bbc <mainbus_match_status>) at /usr/src/sys/arch/riscv64/dev/mainbus.c:254 #6 0xffffffc0003644c4 in mainbus_attach (parent=<optimized out>, self=0xffffffc002da4000, aux=<optimized out>) at /usr/src/sys/arch/riscv64/dev/mainbus.c:145 #7 0xffffffc0002e6c9a in config_attach (parent=0x0, match=<optimized out>, aux=0x0, print=0x0) at /usr/src/sys/kern/subr_autoconf.c:412 #8 0xffffffc0002e6e48 in config_rootfound (rootname=0xffffffc000623abc "mainbus", aux=0x0) at /usr/src/sys/kern/subr_autoconf.c:337 #9 0xffffffc00038e096 in cpu_configure () at /usr/src/sys/arch/riscv64/riscv64/autoconf.c:53 #10 0xffffffc00030adb4 in main (framep=<optimized out>) at /usr/src/sys/kern/init_main.c:336
直接、ドライバーの登録と相なっている。これは、カーネルの構成仕様だな。
# simplebus0 simplebus* at fdt? # Platform Level Interrupt Controller plic* at fdt? early 1 syscon* at fdt? early 1 gfrtc* at fdt?
これが、最終的にカーネルをコンパイルする為のMakefileになるのか。
$S/dev/ofw/ofw_regulator.c $S/dev/ofw/ofw_thermal.c \ $S/dev/fdt/gfrtc.c $S/dev/fdt/ociic.c $S/dev/fdt/virtio_mmio.c \ $S/dev/fdt/if_dwge.c $S/dev/fdt/xhci_fdt.c $S/dev/fdt/syscon.c \ $S/dev/fdt/if_cad.c $S/dev/fdt/cdsdhc.c $S/dev/fdt/dwmmc.c \ $S/dev/fdt/dwpcie.c $S/dev/fdt/com_fdt.c $S/dev/fdt/dapmic.c \
んでもって、BPが矢印の所に有る時
if (todr_handle == NULL || => todr_gettime(todr_handle, &rtctime) != 0 || rtctime.tv_sec < (MINYEAR - 1970) * SECYR) {
逆アセしてみた。けど、チンプンカンプンだな。
=> 0xffffffc00021fc06 <+110>: ld a2,16(a0) 0xffffffc00021fc08 <+112>: addi a1,s0,-80 0xffffffc00021fc0c <+116>: jalr a2 0xffffffc00021fc0e <+118>: bnez a0,0xffffffc00021fc20 <inittodr+136> 0xffffffc00021fc10 <+120>: ld a0,-80(s0) 0xffffffc00021fc14 <+124>: lui a1,0x5fdd4 0xffffffc00021fc18 <+128>: addiw a1,a1,639 # 0x5fdd427f 0xffffffc00021fc1c <+132>: blt a1,a0,0xffffffc00021fc98 <inittodr+256>
mytime
以前にちょっとやったriscvのユーザーランド側でのdate。gettimeofdayなシステムコールが呼び出されないのに、時刻を得る疑問を再審査。思いっきり駆動プログラムを単純化して、再出発。
#include <time.h> time_t tval; int main(){ time(&tval); }
強引にgettimeofdayにBPを置いて実行。
(gdb) b gettimeofday Function "gettimeofday" not defined. Make breakpoint pending on future shared library load? (y or [n]) y Breakpoint 1 (gettimeofday) pending. (gdb) r Starting program: /home/sakae/a.out Breakpoint 1, _libc_gettimeofday_wrap (tp=0x3ffffe07d0, tzp=0x0) at /usr/src/lib/libc/sys/w_gettimeofday.c:24 24 struct timekeep *timekeep = _timekeep; (gdb) p *_timekeep $1 = {tk_version = 0, tk_scale = 1844674407370, tk_offset_count = 1010546621, tk_offset = {sec = 2239, frac = 3465506777401170787}, tk_naptime = {sec = 0, frac = 0}, tk_boottime = {sec = 1670532364, frac = 6747256356074596705}, tk_generation = 223822, tk_user = 1, tk_counter_mask = 4294967295}
なんと、時刻の片割れ、 tk_boottime
が出てきた。少し歩兵すると、
(gdb) s _microtime (tvp=0x3ffffe07d0, tk=<optimized out>) at /usr/src/lib/libc/sys/microtime.c:101 101 if (bintime(&bt, tk)) (gdb) l 96 int 97 _microtime(struct timeval *tvp, struct timekeep *tk) 98 { 99 struct bintime bt; 100 101 if (bintime(&bt, tk)) 102 return -1; 103 BINTIME_TO_TIMEVAL(&bt, tvp); 104 return 0; 105 }
bintimeってカーネル側にも有ったなあなんて思いながら、そこを通過。
(gdb) p bt $4 = {sec = 1670535163, frac = 3151190854715468712} : riscv$ date -r 1670535163 Fri Dec 9 06:32:43 JST 2022
現在時刻が得られている。と言う事は、諸悪の根源は、 _timekeep
に有ったんだねぇ。こやつは、
lib/dlfcn/init.c/_libc_preinit()
の中で定義されてた。ざっと見すると、libcがダイナミックロードされる時に、実行されるとな。
Breakpoint 1, _libc_preinit (argc=1, argv=0x3ffffe3138, envp=0x3ffffe3148, cb=0x6591397c0 <_dl_cb_cb>) at /usr/src/lib/libc/dlfcn/init.c:88 (gdb) 111 if (_tc_get_timecount) { (gdb) 112 _timekeep = (void *)aux->au_v; (gdb) n 113 if (_timekeep && (gdb) p *_timekeep $1 = {tk_version = 0, tk_scale = 1844674407370, tk_offset_count = 1326143163, tk_offset = {sec = 3559, frac = 4384947829658466767}, tk_naptime = {sec = 0, frac = 0}, tk_boottime = {sec = 1670532364, frac = 6747256356074596705}, tk_generation = 355827, tk_user = 1, tk_counter_mask = 4294967295}
出てきましたねぇ。新式のriscvだと、なるべくシステムに負担をかけないような配慮がされてるんだな。平たく言うと、バイパスね。それに対して糞石方面は旧道、すいすいとはいかない。
spim
以前ちょっとやったMIPSのエミュレータを再び取出してみた。流行のデジタル・ツイン。
sakae@deb:~$ spim SPIM Version 8.0 of January 8, 2010 Copyright 1990-2010, James R. Larus. All Rights Reserved. See the file README for a full copyright notice. Loaded: /usr/lib/spim/exceptions.s (spim)
素直に入れると、こんなバナーが出て来る。ちょっと煩わしいので、
CPU/spim-utils.c/{write_startup_message(),initialize_world()}
を変更してから、コンパイルしたよ。作者様、勝手な改悪ごめんなさい。
hello.as
.data msg: .asciiz "Hello world.\n" .text .globl main main: li $v0, 4 # 4 = 文字列表示のシステムコール番号 la $a0,msg syscall jr $ra
Makefile
run: spim -file hello.as
実行結果
make -k spim -file hello.as Hello world.
これでemacs上で自由に編集してからcompile and go 出来るな。ああ、コンパイルって言っちゃったけど、それは単にmakeする為の方便さ。
呼出番号145なんてのを使おうとすると(アマチュア無線の2Mバンドの共通呼出周波数)
make -k spim -file hello.as Unknown system call: 145
一応チェックしてるな。その他のエラーもそれなりに指摘してくれるよ。
make -k spim -file hello.as spim: (parser) syntax error on line 10 of file hello.as eyscall ^ Attempt to execute non-instruction at 0x0040002c
それからX上で使えるxspimも有るけど、オイラーには文字が細か過ぎて使えない。ちょいと不便なのは、 print_all_regs
(hex) で、浮動少数点のレジスターもダンプされる事。現状INTだけのREGだけで十分なんだけどな。改造しちゃうか。(display-utils.c)
ついでなのでCPUの下を見ると、SYSCALLが列挙されてた。アセンブラだけでコードを書くなら、取り敢えずこれだけ有れば十分だろう。
#define PRINT_INT_SYSCALL 1 #define PRINT_FLOAT_SYSCALL 2 #define PRINT_DOUBLE_SYSCALL 3 #define PRINT_STRING_SYSCALL 4 #define READ_INT_SYSCALL 5 #define READ_FLOAT_SYSCALL 6 #define READ_DOUBLE_SYSCALL 7 #define READ_STRING_SYSCALL 8 #define SBRK_SYSCALL 9 #define EXIT_SYSCALL 10 #define PRINT_CHARACTER_SYSCALL 11 #define READ_CHARACTER_SYSCALL 12 #define OPEN_SYSCALL 13 #define READ_SYSCALL 14 #define WRITE_SYSCALL 15 #define CLOSE_SYSCALL 16 #define EXIT2_SYSCALL 17
xv6-riscv
2周遅れで、xv6のriscv版が出てるのを知った。大本のMITが糞石は教育上宜しくないってんで、riscvにスイッチしたんだ。アプルみたいにARMを採用しなかったのは、察してくださいって事だ。
ウブ用にMakefileが用意されてる。MACな人は頑張って環境作ってねって事らしい。MITは私学なんで、みんなピカピカのMACかと思ったら、そうでもないのね。安心した。
オイラーはArchLinuxと言う臍曲がりなんで、ちょいとMakefileを修整したけどね。
WSL2 と QEMU と Vim で xv6 のステップ実行環境をつくる
xv6: シンプルで Unix 風な教育用オペレーティングシステム (pdf)
おいおい、やってみるか。ちゃんと解説書も日本語になってるしね。至れり尽せりだな。