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のツールチェーンを 使って、環境を作ってました。これ、なかなかのアイデアだなあ。