netbsd
先週の枕で、石(おいらが石と言えば、それはCPUを指します)のコレクタの話をちょっと書いた。 今週はその続きで、石が入っている箱(いわゆるパソコン)の事を、ちらっと書きます。
NetBSDが入ったpmaxって言う箱。実物を見た事は無いんで、写真を探してみたよ。 そしたら、こちらに掲載されて いました。いわゆるワークステーションですな。
かの昔、ワークステーションとパソコンは何が違うか、なんてのを同僚とよく議論したっけ。 信頼性が高いのがワークステーション、すぐに壊れるのがパソコン。この意見を補強するものと して、価格が高い(高信頼性部品を使うから)のがワークステーションってのが有ったなあ。
その他、SCSIでDISKに接続してんのがワークステーション。IDE接続のしょぼいやつはパソコン ってのもあったな。インテル入ってるってのがパソコンで、ワークステーションはRISCな石が 入ってるってのも、有力な意見だったな。
Windowsが動くやつはパソコンで、UNIXが動くのがワークステーション。毎日、rebootしないと 固まってしまうのがパソコン、動いているのを忘れるぐらい、安定に動いてるのがワークステーション。 当時を思い出しちゃいましたよ。おいらが動かしてたワークステーションなんて、無事故無違反で 4年間無停止で動いてましたよ。ほっとけばまだ記録は伸びたはずなんだけど、長期の電源工事が 入るって事で、停止させたなあ。
ああ、ワークステーション対パソコンって題材の前に、ミニコン対ワークステーションってのも ありましたなあ。こちらは衆目が一致する所で、一人で持てない(重い、高価)のがミニコン。何とか 一人で持てるのがワークステーションだったかな。他にある違いとして、コアメモリーを 使ってるのがミニコンで、半導体メモリーを積んでるのがワークステーション。
こういう事を書くと、歳がばれるな。 こちらは、pmaxを集めていたドイツな人。堂々と自分の年齢を書いているよ。 他にもいろいろ収集してて、奥さんに 怒られないのだろうか? 人事ながら心配になるぞ。
自分用のnetbsd
最近は、使ってるFreeBSDもLinuxも、そのカーネルを自分用に再構築する事をしなくなったな。 堕落してますなあ。久しぶりに、カーネルだけでもコンパイルしてみるかな。
おいらは伝統的に、NFSが嫌いなんでその機能を削ぎ落としたカーネルを作ってみます。
pmax# cd sys/arch/pmax/conf pmax# vi GENERIC : pmax# config ./GENERIC Build directory is ../compile/GENERIC Don't forget to run "make depend"
カーネル機能の設定書を編集。そして自分の名前を刷り込みました。カーネル開発者には そそっかしい人が多いみたいで、注意書きが出てきました。
pmax# make depend : # create GENERIC/zsms.d mkdep -f zsms.d -- -G 0 -mno-abicalls -msoft-float -ffixed-23 -ffreestanding -f no-zero-initialized-in-bss -O2 -std=gnu99 -fno-strict-aliasing -Werror -Wall -Wno-main -Wno-format-zero-length -Wpointer-arith -Wmissing-prototypes -Wstrict- prototypes -Wswitch -Wshadow -Wcast-qual -Wwrite-strings -Wno-unreachable-code - Wno-sign-compare -Wno-pointer-sign -Wno-attributes -Werror -Dpmax -I. -I../ ../../../../common/include -I../../../../arch -I../../../.. -nostdinc -DMIPS1 - DMIPS3 -DLKM -DMAXUSERS=64 -D_KERNEL -D_KERNEL_OPT -I../../../../lib/libkern/../ ../../common/lib/libc/quad -I../../../../lib/libkern/../../../common/lib/libc/st ring -I../../../../lib/libkern/../../../common/lib/libc/arch/mips/string -I../ ../../../dist/ipf ../../../../dev/tc/zsms.c # create GENERIC/assym.d cat ../../../../arch/mips/mips/genassym.cf | genassym -- mkdep -f assym.dep -- -G 0 -mno-abicalls -msoft-float -ffixed-23 -ffreestanding -fno-zero-initializ ed-in-bss -O2 -std=gnu99 -fno-strict-aliasing -Werror -Wall -Wno-main -Wno-fo rmat-zero-length -Wpointer-arith -Wmissing-prototypes -Wstrict-prototypes -Wswit ch -Wshadow -Wcast-qual -Wwrite-strings -Wno-unreachable-code -Wno-sign-compare -Wno-pointer-sign -Wno-attributes -Werror -Dpmax -I. -I../../../../../common /include -I../../../../arch -I../../../.. -nostdinc -DMIPS1 -DMIPS3 -DLKM -DMAX USERS=64 -D_KERNEL -D_KERNEL_OPT -I../../../../lib/libkern/../../../common/lib/l ibc/quad -I../../../../lib/libkern/../../../common/lib/libc/string -I../../../.. /lib/libkern/../../../common/lib/libc/arch/mips/string -I../../../../dist/ipf sed -e 's/.*\.o:.*\.c/assym.h:/' < assym.dep >assym.d rm -f assym.dep :
カーネルは1000個ぐらいな*.hで出来上がっているので、それらの依存関係をMakefileの 中に書くと、保守不能になる。そこで、依存関係だけを先に調べておくって事をやります。 続いて、コンパイルです。
pmax# make : # create vers.c sh ../../../../conf/newvers.sh # compile GENERIC/vers.o cc -G 0 -mno-abicalls -msoft-float -ffixed-23 -ffreestanding -fno-zero-initiali zed-in-bss -O2 -std=gnu99 -fno-strict-aliasing -Werror -Wall -Wno-main -Wno-f ormat-zero-length -Wpointer-arith -Wmissing-prototypes -Wstrict-prototypes -Wswi tch -Wshadow -Wcast-qual -Wwrite-strings -Wno-unreachable-code -Wno-sign-compare -Wno-pointer-sign -Wno-attributes -Werror -Dpmax -I. -I../../../../../commo n/include -I../../../../arch -I../../../.. -nostdinc -DMIPS1 -DMIPS3 -DLKM -DMA XUSERS=64 -D_KERNEL -D_KERNEL_OPT -I../../../../lib/libkern/../../../common/lib/ libc/quad -I../../../../lib/libkern/../../../common/lib/libc/string -I../../../. ./lib/libkern/../../../common/lib/libc/arch/mips/string -I../../../../dist/ipf -c vers.c
長い長いコンパイルの最後は(下に実行時間が出てるけど、3時間)、バージョン情報の作成。これに続いて、リンカーが動き出して 各オブジェクトファイルを一つにまとめてくれます。
# link GENERIC/netbsd ld -Map netbsd.map --cref -T ../../../../arch/mips/conf/kern.ldscript -Ttext 0x8 0030000 -e start -G 0 -X -o netbsd ${SYSTEM_OBJ} ${EXTRA_OBJ} vers.o NetBSD 5.0.2 (GENERIC) #0: Mon Aug 6 10:02:35 JST 2010 text data bss dec hex filename 3374044 65408 316192 3755644 394e7c netbsd elf2ecoff netbsd netbsd.ecoff 11971.56 real 11473.89 user 475.35 sys
おまけで、ecoffなんてのも作ってくれてて、組み込みっぽいぞ。あと、嬉しいのは、mapファイルを 出力してくれてる事かな。
pmax# make install rm -f /onetbsd ln /netbsd /onetbsd cp netbsd /nnetbsd mv /nnetbsd /netbsd
最後にインストールです。一世代前のカーネルをonetbsdとして保存してます。この世代交代劇 にも、注意が払われていますなあ。昨今の国会と言うか永田町あたりも見習って欲しいものです。
netbsd.map
mapってどんな具合になってる? 見てくと
/home/sakae/src/sys/arch/pmax/compile/GENERIC/lib/compat/libcompat.a(kern_exit_43.o) init_sysent.o (compat_43_sys_wait) /home/sakae/src/sys/arch/pmax/compile/GENERIC/lib/compat/libcompat.a(compat_util.o) /home/sakae/src/sys/arch/pmax/compile/GENERIC/lib/ compat/libcompat.a(vfs_syscalls_30.o) (compat_offseterr) Allocating common symbols Common symbol size file multicast_register_if6 0x210 ip6_mroute.o booted_wedge 0x4 kern_subr.o
libcompat.aは、何から出来てるってのを、丁寧に解説してくれてるんだな。 それに続いて、シンボルの定義状況の説明か。続いて見て行くと
Memory Configuration Name Origin Length Attributes *default* 0x0000000000000000 0xffffffffffffffff Linker script and memory map 0x0000000000000000 _DYNAMIC_LINK = 0x0 .text 0x0000000080030000 0x2be400 0x0000000080030000 _ftext = . *(.text) .text 0x0000000080030000 0x570 locore.o 0x0000000080030224 longjmp 0x0000000080030388 mips_cp0_cause_write 0x000000008003052c logstacktrace 0x00000000800301e8 setjmp : .text 0x0000000080199980 0xc50 init_main.o 0x0000000080199988 calc_cache_size 0x0000000080199980 __secmodel_none 0x0000000080199a68 main .text 0x000000008019a5d0 0x7820 init_sysctl.o 0x00000000801a0894 fill_eproc 0x00000000801a029c sysctl_root_device 0x000000008019d280 sysctl_consdev :
メモリーの配置状況。main関数は、init_main.c で定義されてるってのが一発で分かるな。 これで、findとxargsとgrepで探し回る手間が省けるな。
.rodata 0x0000000080307030 0x250 vers.o 0x00000000803070c8 version 0x0000000080307040 sccs 0x0000000080307168 copyright 0x000000008030714c kernel_ident 0x0000000080307030 ostype 0x0000000080307038 osrelease
続いて、定数データのエリアとかが入ってる。続いて読み書きできる.data。エリアだけの 確保 .bssと続いてる。
cpu_arch interrupt.o machdep.o bus_dma.o mips_mcclock.o dec_3min.o dec_3maxplus.o mips_machdep.o trap.o db_interface.o kern_clock.o exec_ecoff.o
これは、シンボルがどこから参照されてるかってやつだな。例だと、いかにもってファイル から使われているな。
そんじゃ、おいらの刻印が埋め込まれているか、確認
pmax$ strings /netbsd | grep SAKAE _CFG_ident\011\011"SAKAE-$Revision: 1.158 $" @(#)NetBSD 5.0.2 (SAKAE-$Revision: 1.158 $) #2: Mon Aug 6 11:36:59 JST 2010 NetBSD 5.0.2 (SAKAE-$Revision: 1.158 $) #2: Mon Aug 6 11:36:59 JST 2010 SAKAE-$Revision: 1.158 $
ちゃんと記録が残ってますな。
syscall
糞石でシステムコールする時は、コール番号やら引数をスタックに積んで、int 80Hするんだったな。 何せ、レジスタが貧弱だからね。
レジスタがリッチなRISCではどうやるん? レジスタ渡しが理にかなっていそうだ。軽く、qtspim のHelpを調べてみると、例が出てた。
.data str: .asciiz "the answer = " .text li $v0, 4 # system call code for print_str la $a0, str # address of string to print syscall # print the string
システムコール番号をv0に入れ、引数をa0-a3 にセットしてから、syscall命令ね。NetBSDでも 同じかな? 調べてみる。
pmax$ cat sss.c #include <unistd.h> #include <fcntl.h> int fdi, fdo; char b[1]; int main(void) { fdi = open("in", O_RDONLY); fdo = open("out", O_WRONLY); while (0 < read(fdi, b, 1)) { write(fdo, b, 1); } close(fdi); close(fdo); }
後、システムコール番号をsys/syscall.h を見て調べておく。
/* syscall: "read" ret: "ssize_t" args: "int" "void *" "size_t" */ #define SYS_read 3 /* syscall: "write" ret: "ssize_t" args: "int" "const void *" "size_t" */ #define SYS_write 4 /* syscall: "open" ret: "int" args: "const char *" "int" "..." */ #define SYS_open 5 /* syscall: "close" ret: "int" args: "int" */ #define SYS_close 6
そして、KTRACEした結果
286 1 a.out CALL open(0x400e30,0,0x7fffdde4) 286 1 a.out RET open 3, 2113896960/0x7dff8200 286 1 a.out CALL open(0x400e34,1,0x7fffdde4) 286 1 a.out RET open 4, 3 286 1 a.out CALL read(3,0x440f14,1) 286 1 a.out RET read 1, 2113896960/0x7dff8200 286 1 a.out CALL write(4,0x440f14,1) 286 1 a.out RET write 1, 2113896960/0x7dff8200
まずは、軽くアセンブラリストを見る。
fdi = open("in", O_RDONLY); 400b20: 8f828018 lw v0,-32744(gp) 400b24: 00000000 nop 400b28: 24440e30 addiu a0,v0,3632 400b2c: 00002821 move a1,zero 400b30: 8f998038 lw t9,-32712(gp) 400b34: 00000000 nop 400b38: 0320f809 jalr t9 : fdo = open("out", O_WRONLY); 400b54: 8f828018 lw v0,-32744(gp) 400b58: 00000000 nop 400b5c: 24440e34 addiu a0,v0,3636 400b60: 24050001 li a1,1 400b64: 8f998038 lw t9,-32712(gp) 400b68: 00000000 nop 400b6c: 0320f809 jalr t9
syscallなんて命令出てこないな。でも、雰囲気的にjalr t9 がそれっぽいぞ。 gdbで追ったら、ダイナミックリンクのいやらしさが出てたので、-staticを付けて静的に コンパイルをしなおしました。
(gdb) b open Breakpoint 1 at 0x40079c (gdb) b read Breakpoint 2 at 0x40075c (gdb) run Starting program: /home/sakae/z/a.out Breakpoint 1, 0x0040079c in open () (gdb) disassemble Dump of assembler code for function open: 0x00400790 <open+0>: lui gp,0x7 0x00400794 <open+4>: addiu gp,gp,12464 0x00400798 <open+8>: addu gp,gp,t9 0x0040079c <open+12>: li v0,5 0x004007a0 <open+16>: syscall 0x004007a4 <open+20>: bnez a3,0x4007b4 <open+36> 0x004007a8 <open+24>: nop 0x004007ac <open+28>: jr ra (gdb) display/i $pc 1: x/i $pc 0x40079c <open+12>: li v0,5 (gdb) si 0x004007a0 in open () 1: x/i $pc 0x4007a0 <open+16>: syscall (gdb) info reg zero at v0 v1 a0 a1 a2 a3 R0 00000000 00000000 00000005 ffffffff 00425e90 00000000 7fffddbc 7fffeff0 t0 t1 t2 t3 t4 t5 t6 t7 R8 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 s0 s1 s2 s3 s4 s5 s6 s7 R16 7fffee84 7fffddb0 00000001 7fffddb4 0046bd0c 7fffeff0 00000000 00000000 t8 t9 k0 k1 gp sp s8 ra R24 00000000 00400790 00000000 00000000 00473840 7fffdd50 7fffdd50 004005e0 sr lo hi bad cause pc 0008ff3c 00000000 00000000 0040079c 00000024 004007a0 fsr fir 7dba30a0 00000000
やっと出てきましたねぇ。syscall。 R3000のマニュアルによると、syscallのビットパターン中に 空きがあるので、そこに番号を埋め込めるようです。でも、そんなのどのOSも使ってないぽい。 だって、syscallのビットパターンを取り出して、それをわざわざ解析するなんて、面倒でしょ。
syscallを調べてて知ったんだけど、syscallって言うシステムコールもあるのね。これは、 動的にシステムコールする時にでも使うんだろな。
なお、ありきたりのシステムコールじゃつまんないと言う人は、俺様用システムコールも つくれるみたい。その、指南書 が、あったよ。
TLB
おいらがMIPSを始めた時最初に訪れたPageにTLBなんてのが出てた。今まで知ったかぶりして 避けてたんだけど、ここでどんなものか調べてみた。 まずは包括的に仮想メモリ方式の分類 かな。 そしてMIPS特有な説明としてMIPS/R3000 とか、MIPSのアドレス変換周りをまとめてみた あたりが参考になるな。
更にもう一つ、ARMとMIPSを題材にした卒業研究 なんてのを見つけて、楽しい学生生活だなあなんて思ったり。。。
ついでに、GXemulのコマンドでtlbdumpした時、
cpu0: (index=0x0 random=0x9 wired=0x1) 0: vaddr=c00000ffc3f10000 (global): p0=0x000ff6000 D p1=0x000fc3000 D (4KB) 1: vaddr=c00000ffc3282000 (global): p0=0x00119c000 D p1=0x00119b000 D (4KB) 2: vaddr=c00000ff80004000 (asid 00): p0=(invalid) p1=(invalid) (4KB)
物理アドレスを表示した後に、D ってマークが付いてるけど、これ何を意味してるの? ソースを探ってみたぞ。(cpus/cpu_mips.cc - mips_cpu_tlbdump)
if (!(lo0 & ENTRYLO_V)) printf(" p0=(invalid) "); else { uint64_t paddr = lo0 & ENTRYLO_PFN_MASK; paddr >>= ENTRYLO_PFN_SHIFT; paddr <<= pageshift; paddr &= ~(mask >> 1); printf(" p0=0x%09"PRIx64" ", (uint64_t) paddr); } printf(lo0 & ENTRYLO_D? "D" : " ");
書き込み保護Bitの表示なのね。最初は保護(0)しといて運用。書き込みをすると例外発生。 書き込んだ時に(1)にする事により、汚れちゃったか判定出来るとな。こんな仕組みになってるんで 、TLBの例外は、参照系、書き込み系で分けているんだな。
それと、面白いと思うのは、TLBのどのラインを使うか、ランダムに選べるという仕組み (ランダムと言うと語弊があるな。単純にindexをデクリメントしてるだけだ)。ごちゃ ごちゃ確認するより、運任せでいいじゃんと言うとんでも設計が面白い。CPUはどこからどこまでも 論理的かと思ったら、投機実行(取りあえず先に走ります)して、だめだったらそんなの無かった 事にするとか、人の世を反映してんなあ。
おまけ
前回は、各種石の違いを眺めようって事でやってみたけど、 こちらの方は、NetBSDのツールチェーンを 使って、環境を作ってました。これ、なかなかのアイデアだなあ。