WRAP
WRAP
Advnet Calendar 2022 で盛り上りをみせているみたいだけど、それは全部揃ってから見る事にして、今はラッピングですよ。前回、gettimeofdayがラッピングされてて、それはマシンの差を吸収する為って、いいかげんな結論を出しちゃった。
でも、包んであるって事は、きっと大事な物が隠されているに違いない。野生の感が働くか? ちょいとコードを追跡してみたら、止めたい所が出て来た。
riscv$ gdb -q date Reading symbols from date... (gdb) b rdtime Breakpoint 1 at 0x17cc4: file /usr/src/lib/libc/arch/riscv64/gen/usertc.c, line 27. (gdb) r Starting program: /tmp/date Breakpoint 1, rdtime () at /usr/src/lib/libc/arch/riscv64/gen/usertc.c:27 27 __asm volatile("rdtime %0" : "=r"(ret)); (gdb) bt #0 rdtime () at /usr/src/lib/libc/arch/riscv64/gen/usertc.c:27 #1 tc_get_timecount (tk=<optimized out>, tc=0x3fffff66e4) at /usr/src/lib/libc/arch/riscv64/gen/usertc.c:37 #2 0x00000001773199b8 in tc_delta (tk=0x23b05e000, delta=<optimized out>) at /usr/src/lib/libc/sys/microtime.c:34 #3 bintime (bt=0x3fffff6720, tk=0x23b05e000) at /usr/src/lib/libc/sys/microtime.c:85 #4 0x0000000177319930 in _microtime (tvp=0x3fffff6770, tk=<optimized out>) at /usr/src/lib/libc/sys/microtime.c:101 #5 0x000000017731774a in _libc_gettimeofday_wrap (tp=0x3fffff6770, tzp=0x0) at /usr/src/lib/libc/sys/w_gettimeofday.c:31 #6 0x000000017730cc02 in _libc_time (t=0x177324ad0 <tval>) at /usr/src/lib/libc/gen/time.c:39 #7 0x000000017730a33a in main (argc=0, argv=0x3fffff6c60) at date.c:98
なんとlibcにも機種依存のコードが有るのね。想像もつかなかったぞ。折角なので、逆アセしてみた。
0x0000000177319cb8 <+0>: lw a2,76(a0) 0x0000000177319cba <+2>: li a3,1 0x0000000177319cbc <+4>: li a0,-1 0x0000000177319cbe <+6>: bne a2,a3,0x177319cca <tc_get_timecount+18> 0x0000000177319cc2 <+10>: li a0,0 => 0x0000000177319cc4 <+12>: rdtime a2 0x0000000177319cc8 <+16>: sw a2,0(a1) 0x0000000177319cca <+18>: sext.w a0,a0 0x0000000177319ccc <+20>: ret End of assembler dump.
ちょいと異色なオペコードが出ていたぞ。なんか特注っぽい臭いがする。
(gdb) si tc_get_timecount (tk=<optimized out>, tc=0x3fffff66e4) at /usr/src/lib/libc/arch/riscv64/gen/usertc.c:37 37 *tc = rdtime(); (gdb) n (gdb) p/x *tc $3 = 0x6ea6846d (gdb) c : (gdb) p/x *tc $5 = 0x99de3abd
アクセスの度に値がインクリメントされてる。ここはもう、取扱説明書の出番だな。
ぐだぐだ低レベルプログラミング(21) GD32VF103のサイクルカウンタ辺の実装
The RISC-V Instruction Set Manual Volume I: User-Level ISA page 22
RISC-V user-level ISA by takeoka 超要約バージョン
RISC-V Assembly Programmer's Manual
どうもCPUに内蔵されてるカウンターって事だな。
myprog
一応、自前のプログラムも作ってみた。いわゆる純粋培養ね。まあ、libcのそれをパクッただけだけど。
#include <sys/types.h> #include <stdio.h> static inline u_int rdtime(void) { uint64_t ret; __asm volatile("rdtime %0" : "=r"(ret)); return ret; } int main(){ printf("%u\n", rdtime()); printf("%u\n", rdtime()); }
何回か実行。確かにカウンターだ。それより驚くべき事は、システムコールしなくても、CPUの中のカウンターをアクセス出来る事。こんな事を許して委員会?
riscv$ ./a.out 3705945385 3706296898 riscv$ ./a.out 3761925097 3762262483
at PCAT
古典的なマシンって事で、10年前のThinkPad で動いてるFreeBSDのハードウェア・プロービングの結果。
[sakae@fb ~]$ dmesg | grep timer Event timer "LAPIC" quality 100 Event timer "HPET" frequency 14318180 Hz quality 450 Event timer "HPET1" frequency 14318180 Hz quality 440 Event timer "HPET2" frequency 14318180 Hz quality 440 Event timer "HPET3" frequency 14318180 Hz quality 440 Event timer "RTC" frequency 32768 Hz quality 0 attimer0: <AT timer> port 0x40-0x43,0x50-0x53 irq 0 on acpi0 Event timer "i8254" frequency 1193182 Hz quality 100 acpi_timer0: <24-bit timer at 3.579545MHz> port 0x408-0x40b on acpi0
ユーザーランドから、手軽に触れそうにないな。しっかり周辺機器扱いになってるし、CPU内蔵も、アクセスするには、それなりの作法が必要っぽい。
others WRAP
ラッピングされてるのは、他に無いか、少し探りをいれてみた。やっぱりボチボチでんなあ。
ob$ cd /usr/src/lib ob$ grep DEF_WRAP -rIl . ./libc/include/DETAILS ./libc/include/README ./libc/include/namespace.h ./libc/sys/ptrace.c ./libc/sys/w_clock_gettime.c ./libc/sys/w_fork.c ./libc/sys/w_gettimeofday.c ./libc/sys/w_sigaction.c ./libc/sys/w_sigprocmask.c ./libc/sys/w_vfork.c
ついでに、riscv64と名のつくarchの場所も調べておく。
ob$ cd /usr/src ob$ find . -type d -name riscv64 ./distrib/notes/riscv64 ./distrib/riscv64 ./lib/csu/riscv64 ./lib/libc/arch/riscv64 ./lib/libcrypto/arch/riscv64 ./lib/libm/arch/riscv64 ./libexec/ld.so/riscv64 ./sys/arch/riscv64 ./sys/arch/riscv64/riscv64 ./sys/stand/efi/include/riscv64
kernel side
kern/init_main.c/initclocks() -> kern/kern_clock.c/cpu_initclocks()
arch/riscv64/riscv64//clock.c
cpu_initclocks(void) { tb_timecounter.tc_frequency = tb_freq; tc_init(&tb_timecounter); tick_increment = tb_freq / hz; :
ハード屋はハードの諸元が、ついつい気になる。 tb_freq
ってどれぐらい? machdep.cに定義があった。 uint64_t tb_freq = 1000000;
ですって。素直に解釈すると1MHzだから、1usでカウントアップするんだな。
そして、hz は param.c で、100が設定されている。これ、糞石システムでは50だったか60だったように思うぞ。
そして、このファイルにも、rdtime()が定義されてた。カーネル側のやつね。riscv64/include/cpufunc.hに定義されてた。
#define rdcycle() csr_read(cycle) #define rdtime() csr_read(time) #define rdinstret() csr_read(instret) #define rdhpmcounter(n) csr_read(hpmcounter##n)
この定義に出て来る ## は、コンパイラーの工程プリプロセッサーの命令で、文字列の結合だ。
また、 csr_read()
は、riscvreg.h
#define csr_read(csr) \ ({ u_long val; \ __asm volatile("csrr %0, " #csr : "=r" (val)); \ val; \ })
に定義されてる。libc側の定義と比べると、より原理的だな。
boottime
kern_tc.c
に面白い構造体が定義されてた。
struct timehands { /* These fields must be initialized by the driver. */ struct timecounter *th_counter; /* [W] */ int64_t th_adjtimedelta; /* [T,W] */ struct bintime th_next_ntp_update; /* [T,W] */ int64_t th_adjustment; /* [W] */ u_int64_t th_scale; /* [W] */ u_int th_offset_count; /* [W] */ struct bintime th_boottime; /* [T,W] */ struct bintime th_offset; /* [W] */ struct bintime th_naptime; /* [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] */ };
この中にある、 th_boottime
って、いつセットされるんだろう? その前に bintimeって型は? 型はきっとヘッダーで説明されてるはず。time.h。
/* Time expressed as seconds and fractions of a second + operations on it. */ =>ruct bintime { time_t sec; uint64_t frac; }; /* * Functions for looking at our clocks: [get]{bin,nano,micro}[boot|up]time() * * Functions without the "get" prefix returns the best timestamp * we can produce in the given format. * * "bin" == struct bintime == seconds + 64 bit fraction of seconds. * "nano" == struct timespec == seconds + nanoseconds. * "micro" == struct timeval == seconds + microseconds. * * Functions containing "up" returns time relative to boot and * should be used for calculating time intervals. * * Functions containing "boot" return the GMT time at which the * system booted. * * Functions with just "time" return the current GMT time. :
色々な精度に対応出来るようになってるんだな。
じゃ、更新されるタイミングを捉える為、watch th0.th_boottime
しておく。それは、gdbが下記のように解釈するんだね。なおtimehands の型を使うグローバル変数に、th1ってのもあるけど、th0っとリングになってる。この使い分けがいまいち不明。
(gdb) info breakpoints Num Type Disp Enb Address What 2 hw watchpoint keep y th0.th_boottime breakpoint already hit 1 time
で、実行結果。
(gdb) bt #0 0xffffffc000346226 in tc_windup (new_boottime=0xffffffc000805bd8, new_offset=0x0, new_adjtimedelta=0xffffffc000805bd0) at\ /usr/src/sys/kern/kern_tc.c:712 #1 0xffffffc000345fd8 in tc_setrealtimeclock (ts=0xffffffc000805ca0) at /usr/src/sys/kern/kern_tc.c:532 #2 0xffffffc0003465ee in tc_setclock (ts=0xffffffc000805ca0) at /usr/src/sys/kern/kern_tc.c:567 #3 0xffffffc00021fcb8 in inittodr (base=1669878550) at /usr/src/sys/kern/kern_time.c:904 #4 0xffffffc000349004 in ffs_mountroot () at /usr/src/sys/ufs/ffs/ffs_vfsops.c:193 #5 0xffffffc00035d88a in dk_mountroot () at /usr/src/sys/kern/subr_disk.c:1346 #6 0xffffffc00030af30 in main (framep=<optimized out>) at /usr/src/sys/kern/init_main.c:456
面白い所から、呼び出されているな。
trans
ソースを見ていると、コメントで重要な事が書いてある。すらすら読めればいいんだけど、そこは、そう察してください。そこで、 google AI を使うか、 https://www.deepl.com/translator とかのお世話になる。
そんな時、冒頭にあるコメントマークが、何となく邪魔。マークを外したやつを与えたい。
そんな時は、emacsの矩形編集ですよ。 C-x r r で、矩形を選択。それをレジスターに保存、するんで任意の一文字でレジスターを与える。次は、新しいバッファーで、 C-x r i して、レジスターからペーストする。
また、emacsなら、ESC-; で、指定した部分を comment/uncomment 出来る。じゃ、viは? 我辞書にはそんな便利なやつは乗っていなかった。vimでも Vimのコメントアウトプラグインtcommentを試す な状況みたい。それより問題なのは、これらを使うと原本のソースを変更しちゃうって事だな。そんなの許しがたいぞ。しょうがないからスクリプトを作ろう。
#!/bin/sh # delete comment mark, Usage: paste comment line's then C-d cat | sed -e 's!/\*!!' | sed -e 's!\*/!!' | sed -e 's!\*!!'
まんまのスクリプトである。適当な場所にdcmとかの名前で配置。OpenBSDのソースの一節を実験台にしてみた。
sakae@deb:~$ dcm /* * Return the difference between the timehands' counter value now and what * was when we copied it to the timehands' offset_count. */ ## コメントをり貼付けてから、C-d して、入力の終了を教えてあげる。 Return the difference between the timehands' counter value now and what was when we copied it to the timehands' offset_count.
boot time
riscVを起動したら、こんなメッセージが出てきた。
root on sd0a (781d4a4515da21ce.a) swap on sd0b dump on sd0b WARNING: clock gained 4 days WARNING: CHECK AND RESET THE DATE! Automatic boot in progress: starting file system checks.
飲み屋のおねーちゃんみたいに、軽く避難された。
riscv$ sysctl -a | grep boot kern.boottime=Tue Dec 6 06:30:31 2022
起動時間は、それなりに正しい。と言う事は、OSは自力で現在時刻を察知してるって事になる。その機能はどう実現してるの? 大疑問である。
riscv$ last -10 sakae console Tue Dec 06 06:33 still logged in reboot ~ Tue Dec 06 06:31 shutdown ~ Fri Dec 02 06:28 sakae console Fri Dec 02 05:18 - shutdown (01:09)
ちなみに、過去の訪問日時を確認してみた。ちゃんと指摘の通りであったぞ。ねーちゃんの記憶力は抜群だな。
SPDX-License-Identifier
久し振りに、こじまみつひろ さんの記事が上ってた。 第44回 linux-6.0とSPDX
SPDXなんていうまとめが有るとな。で、FreeBSDをうろうろしてたら、
timetc.h
/*- * SPDX-License-Identifier: Beerware * * ---------------------------------------------------------------------------- * "THE BEER-WARE LICENSE" (Revision 42): * <phk@FreeBSD.org> wrote this file. As long as you retain this notice you * can do whatever you want with this stuff. If we meet some day, and you think * this stuff is worth it, you can buy me a beer in return. Poul-Henning Kamp * ---------------------------------------------------------------------------- * :
こんな楽しいライセンスを発見。日本人のオイラーなら、Sake だな。Sake駆動でソフト業界をうろうろ。それを体現したRubyの人がいたけど、お元気かしら?
/*- * `struct timecounter' is the interface between the hardware which implements * a timecounter and the MI code which uses this to keep track of time. * * A timecounter is a binary counter which has two properties: * * it runs at a fixed, known frequency. * * it has sufficient bits to not roll over in less than approximately * max(2 msec, 2/HZ seconds). (The value 2 here is really 1 + delta, * for some indeterminate value of delta.) */
dcmの出番ですよ、と。