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を採用しなかったのは、察してくださいって事だ。

mit-pdos / xv6-riscv

ウブ用にMakefileが用意されてる。MACな人は頑張って環境作ってねって事らしい。MITは私学なんで、みんなピカピカのMACかと思ったら、そうでもないのね。安心した。

オイラーはArchLinuxと言う臍曲がりなんで、ちょいとMakefileを修整したけどね。

xv6-riscv のメモリ管理方法

WSL2 と QEMU と Vim で xv6 のステップ実行環境をつくる

QEMUでxv6-riscv

xv6: シンプルで Unix 風な教育用オペレーティングシステム (pdf)

RISC-V の CSR 転送命令と特権命令の機構と実装

xv6におけるfilesystemのLayer(下層編)

計算機構成 慶應大学でも

おいおい、やってみるか。ちゃんと解説書も日本語になってるしね。至れり尽せりだな。


This year's Index

Home