boot

Table of Contents

chez scheme on OpenBSD

一時期、WindowsとLinuxしかサポートしなかったと思ったら、何時の間にかBSDも サポートする様になってた。

ob$ git clone --filter=blob:none https://github.com/cisco/ChezScheme.git

普通にgit cloneしちゃうと、とんでもない過去帳まで取ってきてしまうので、 フィルターをかけるといいぞって案内が有った。 そして、取り寄せたソースでは、zlibとかの器が用意されてるだけ。

ob$ CC=cc ./configure
Submodule 'lz4' (https://github.com/lz4/lz4.git) registered for path 'lz4'
Submodule 'nanopass' (https://github.com/nanopass/nanopass-framework-scheme.git)
 registered for path 'nanopass'
Submodule 'stex' (https://github.com/dybvig/stex) registered for path 'stex'
Submodule 'zlib' (https://github.com/madler/zlib.git) registered for path 'zlib'
Submodule 'zuo' (https://github.com/racket/zuo) registered for path 'zuo'
Cloning into '/var/SRC/ChezScheme/lz4'...
Cloning into '/var/SRC/ChezScheme/nanopass'...
Cloning into '/var/SRC/ChezScheme/stex'...
Cloning into '/var/SRC/ChezScheme/zlib'...
Cloning into '/var/SRC/ChezScheme/zuo'...
 :

こんな風にして、充填しておこう。後はgmakeするだけーー。

Chez Scheme ここが起点で、マニュアル等を参照できる。

折角なので、河豚板で実行してみる。

(import (chezscheme))
(optimize-level 3)

(define N 1000000)

(define sbp (make-flvector N))
(define dbp (make-flvector N))
(define out (make-flvector N))

;; ランダム初期化
(do ([i 0 (+ i 1)])
    ((= i N))
  (flvector-set! sbp i (+ 100.0 (random 50.0)))
  (flvector-set! dbp i (+ 50.0 (random 40.0))))

;; MAP 計算
(define (compute-map-flvector sbp dbp out n)
  (do ([i 0 (+ i 1)])
      ((= i n))
    (let ([h (flvector-ref sbp i)]
          [l (flvector-ref dbp i)])
      (flvector-set! out i (+ (/ (- h l) 3.0) l)))))

(define t0 (current-time))
(compute-map-flvector sbp dbp out N)
(define t1 (current-time))

(printf "Chez Scheme flvector: ~a\n" (time-difference t1 t0))

実行結果

Chez Scheme flvector: #<time-duration 0.012894532>
;; 0.253986s real time, 0.253552s run time.  0.000000s spent in GC.

参考にguile3.0の結果も載せておく。

気を良くしてi386なマシンでも、挑戦。

ob$ gmake
bin/zuo ti3ob MAKE="gmake"
ti3ob/bin/ti3ob/scheme is up to date
ti3ob/bin/ti3ob/petite is up to date
running ti3ob/bin/ti3ob/scheme to build ti3ob/s/cmacros.so
allocation failed for code; maybe write+execute permission is not allowed
failed
 in build-one
 in loop
 in module->hash
gmake: *** [Makefile:8: build] エラー 1

このエラー、c/segment.c で出してるみたい。その回りを見るとMMAPなんてのが散見 される。ひょっとしてメモリー不足かと思い、ulimitで制限を緩めてみたけど、解消 せず。誰もこんな環境に踏み込む人が居ないので、地雷に当たったかな。素直に撤退。

gzip

前回カーネルの圧縮をgzip -c9 で、ギンギンにやってるって書いた。圧縮レベル(1-9) によって、圧縮比率や、作業時間がどの様に変化するか? チャッピー君にさっと試験 スクリプトを作成させた。

#!/bin/sh

INPUT=bsd
orig_size=$(stat -f "%z" "$INPUT")

printf "mode  %%   comp[ms]  decomp[ms]\n"
printf "---------------------------------\n"

for level in 1 2 3 4 5 6 7 8 9
do
    out="bsd.c$level.gz"

    # ---- 圧縮時間計測 ----
    # time の real を ms で取得
    comp_ms=$( ( /usr/bin/time -p gzip -c -$level "$INPUT" > "$out" ) 2>&1 \
        | awk '/real/ { printf("%.0f", $2 * 1000) }' )

    comp_size=$(stat -f "%z" "$out")

    # 圧縮率 %
    ratio=$(echo "scale=1 \n $comp_size * 100 / $orig_size" | bc )

    # ---- 伸長時間計測 ----
    decomp_ms=$( ( /usr/bin/time -p gunzip -c "$out" > /dev/null ) 2>&1 \
        | awk '/real/ { printf("%.0f", $2 * 1000) }' )

    printf "c%-2d  %-.1f  %-8s  %s\n" "$level" "$ratio" "$comp_ms" "$decomp_ms"
done

/bsd まずは、カーネルって言うバイナリーファイル。

mode  %   comp[ms]  decomp[ms]
---------------------------------
c1   53.3  830       200
c2   52.5  900       190
c3   51.9  990       190
c4   51.0  1110      180
c5   50.5  1350      180
c6   50.3  1740      180
c7   50.2  2050      180
c8   50.2  3080      180
c9   50.2  3420      180

チャッピーがバーのホステスみたいに、滞在時間を延長させようと、あれこれ提案 してくるものだから、誘いに乗って、 src/bin (tar) と言う、準テキストファイル。

mode  %   comp[ms]  decomp[ms]
---------------------------------
c1   29.4  90        30
c2   28.1  110       30
c3   27.2  120       20
c4   25.1  140       30
c5   24.3  180       30
c6   23.9  230       30
c7   23.9  250       30
c8   23.8  310       30
c9   23.8  320       30

少しは考えて、綴りが似てるのが並んでいそうな、dict/words

mode  %   comp[ms]  decomp[ms]
---------------------------------
c1   36.1  90        20
c2   34.9  110       30
c3   33.7  140       30
c4   31.3  130       30
c5   30.2  200       30
c6   30.2  320       30
c7   30.2  410       30
c8   30.2  620       30
c9   30.2  720       20

尚、時間の分解能は10msです。

gzip の伸長は LZ77 + ハフマン復号だけなので、 圧縮レベルが変わってもアルゴリズム構造は変わらない。

圧縮に関しては、探索窓サイズは固定(32KB)、マッチ探索の深さも限られる、 カーネルバイナリはすでに最適化・ストリップされており再利用パターンが少ない、 という理由で、元データの性質で決まる圧縮限界に到達していることを示しています。

release

河豚板の案内を見ると、release(8)が、参考になるよってポインターが紹介されてた。

  Create the tree of obj directories and begin the build:

        # cd /usr/src
        # make obj && make build

  Make the release and check the contents of the release tarballs:

        # export DESTDIR=your-destdir RELEASEDIR=your-releasedir
        # cd /usr/src/etc && make release
        # cd /usr/src/distrib/sets && sh checkflist
        # unset RELEASEDIR DESTDIR

7. Create boot and installation disk images
  The disk images install${VERSION}.img and install${VERSION}.iso are
  suitable for installs without network connectivity.  They contain the
  tarballs built in the previous steps.

        # export RELDIR=your-releasedir RELXDIR=your-xenocara-releasedir
        # cd /usr/src/distrib/$(machine)/iso && make
        # make install

オイラーがやった経験が有るのは、独自カーネルの作成までだ。それ以降の工程は 非常に参考になるな(抜粋)。

mk

上記に make obj なんてターゲットが出てくるけど、そんなの該当のMakefileに 登録されていないぞ。汎用的なんでMake用のライブラリィーにでもなっていりのか。 それらしいのが、該当Makefileにも有るぞ。例えば

.include <bsd.subdir.mk>

C言語のそれ(ex. #include <stdio.h>)みたいな形式になってる。それって何処だ?

ob$ cd /usr/share/mk/
ob$ ls
bsd.README          bsd.obj.mk          bsd.port.subdir.mk  bsd.sys.mk
bsd.dep.mk          bsd.own.mk          bsd.prog.mk         bsd.xconf.mk
bsd.lib.mk          bsd.port.arch.mk    bsd.regress.mk      bsd.xorg.mk
bsd.man.mk          bsd.port.mk         bsd.subdir.mk       sys.mk
ob$ grep obj: *.mk
bsd.obj.mk:obj:

obj-dirを作るにも厳密にやってるな。そして、これを実現する為に、<bsd.own.mk> まで利用してる。こういう風にして、水も漏らさない体制をひいているんだな。 安心であります。ちゃんと詳細なREADMEも付属しているしね。

installboot

河豚板作成のハイライトに相当するdistribを見ていたらinstallbootってのが出てきた。 install bootstrap on a disk って事なんだけど、

To install bootstrap on an OpenBSD amd64 machine, using
/usr/mdec/biosboot as the primary bootstrap and /usr/mdec/boot as the
secondary bootstrap:

   # installboot -v wd0 /usr/mdec/biosboot /usr/mdec/boot

こんな例が出てた。

ob$ cd /usr/mdec/
ob$ file *
biosboot: ELF 32-bit LSB executable, Intel 80386, version 1
boot:     ELF 32-bit LSB executable, Intel 80386, version 1
cdboot:   data
cdbr:     data
fdboot:   ELF 32-bit LSB executable, Intel 80386, version 1
mbr:      x86 boot sector
pxeboot:  data

これ、各種bootの集積場なんだな。

実際には、こんな現場で使用されてる。 distrib/i386/iso/Makefile

installboot -v -r ${MOUNT_POINT} `cat vnd` \
    ${DESTDIR}/usr/mdec/biosboot ${.OBJDIR}/boot

んでもって、installbootのソース

vm$ ls /usr/src/usr.sbin/installboot/
Makefile                   i386_nlist.c               octeon_installboot.c
bootstrap.c                i386_softraid.c            powerpc64_installboot.c
efi_bootmgr.c              installboot.8              softraid.c
efi_installboot.c          installboot.c              sparc64_installboot.c
efi_softraid.c             installboot.h              sparc64_installboot.h
hppa_installboot.c         landisk_installboot.c      sparc64_softraid.c
i386_installboot.c         loongson_installboot.c     stubs.c
i386_installboot.h         macppc_installboot.c       util.c

どんなマシン・タイプにも対応できる様に、色々と準備されてる。installboot.c,i386* あたりを見ておけば十分だろう。ざっと見すると、i386系では、コマンドの引数が 省略できる様に、デフォが設定されてた。それから、何が実行されるかドライランも 可能とな。早速、論よりrunします。

vm$ doas installboot -nv wd0
Using / as root
would install bootstrap on /dev/rwd0c
using first-stage /usr/mdec/biosboot, second-stage /usr/mdec/boot
would copy /usr/mdec/boot to //boot
looking for superblock at 65536
found valid ffs2 superblock
//boot is 6 blocks x 16384 bytes
fs block shift 2; part offset 2104544; inode block 56, offset 2928
expecting 64-bit fs blocks (incr 4)
master boot record (MBR) at sector 0
        partition 3: type 0xA6 offset 64 size 100663232
/usr/mdec/biosboot will be written at sector 64

これ、/dev/wd0なDISK装置に、インストール(の予行演習)してみてって指示。

ob$ doas installboot -nv sd0
Using / as root
would install bootstrap on /dev/rsd0c
using first-stage /usr/mdec/biosboot, second-stage /usr/mdec/boot
would copy /usr/mdec/boot to //boot
looking for superblock at 65536
found valid ffs2 superblock
//boot is 6 blocks x 16384 bytes
fs block shift 2; part offset 64; inode block 216, offset 11632
expecting 64-bit fs blocks (incr 4)
master boot record (MBR) at sector 0
        partition 3: type 0xA6 offset 64 size 488397104
/usr/mdec/biosboot will be written at sector 64

bootは、マシンが通電された時、boot時に指示されたカーネルを探し出して、それを メモリーにロードしてから、カーネルを起動させる機能を有する。 じゃ、bootのプログラムはどうやってロードして、起動させる?

その機構が、原始的な方法だと、mbr -> biosboot -> boot って流れだ。このうちの mbr,biosbootが、bootプログラムを起動する役割になってる。2段階になってるのは、 その手順が複雑だから。

boot

bootすなわちLinux界隈語だとgrubが、どう実装されてるか追跡する。まずは、 カーネルが起動するまでのメッセージをキャプチャする。

sakae@lu:~$ qe-i386
SeaBIOS (version 1.16.3-debian-1.16.3-2)

iPXE (https://ipxe.org) 00:03.0 CA00 PCI2.10 PnP PMM+5EFCAF60+5EF0AF60 CA00

Booting from Hard Disk...
Using drive 0, partition 3.
Loading......
probing: pc0 com0 apm pci mem[639K 1534M a20=on]
disk: fd0 hd0+
>> OpenBSD/i386 BOOT 3.67
switching console to com>> OpenBSD/i386 BOOT 3.67
boot> 0

booting hd0a:/bsd: 11049491+2429956+196616+0+1142784 [747466+107+629952+649230]4
entry point at 0x201000

[ using 2027332 bytes of bsd ELF symbol table ]
Copyright (c) 1982, 1986, 1989, 1991, 1993
        The Regents of the University of California.  All rights reserved.
Copyright (c) 1995-2024 OpenBSD. All rights reserved.  https://www.OpenBSD.org

OpenBSD 7.6 (SEE) #0: Fri Jan 31 05:43:30 JST 2025
  :

sys/arch/i386/stand内がマシン依存部分だ。

qe$ find .  | xargs grep Loading
./biosboot/biosboot.S:  movw    $load_msg, %si  /* "Loading" */
./biosboot/biosboot.S:  .asciz  "Loading"

probing/disk/versionの定義は、マシン依存の部類で、boot/conf.cに有った。 また、srt0.S なアセンブラ中で、32Bitマシンに変身して(ページングも可に設定) bootアプリが動作する様にしてる。

次はマシンに依存しないboot部。sys/stand/boot/

boot flow

bootのメイン・フローは、共通部にある、bootだ。

snprintf(prog_ident, sizeof(prog_ident),
    ">> OpenBSD/" MACHINE " %s %s", progname, version);
         :
       printf("booting %s: ", cmd.path);
	 :
       if ((fd = loadfile(cmd.path, marks, LOAD_ALL)) != -1) {
	 :
/* exec */
run_loadfile(marks, cmd.boothowto);

カーネルをロードして、そいつに制御を渡すっていう大まかな流れ。ここでは列挙 しなかったけど、ハイパネートした奴の起動やら、アップグレード時の処理も、要所 に挟めてある。

loadfile

要になる関数部分。sys/lib/libsa/loadfile.c

/* Open the file. */
if ((fd = open(fname, O_RDONLY)) < 0) {

/* Read the exec header. */
if ((nr = read(fd, &hdr, sizeof(hdr))) != sizeof(hdr)) {

        rval = elf32_exec(fd, &hdr.elf32, marks, flags);

普通にopenしてreadして(closeは、わざとしないって注意のコメント有り)なんだけど、 これだとboot(8)にある、Loading kernels compressed by gzip(1). の出番が無い。 ここで、数日悩んでしまったのだ。

よく考えたらカーネルはまだ起動していない。と言う事は、open/readのシステムコール は使えないじゃん。例によってガサ入れしましたよ。そしたら、sys/lib/libsa/cread.c に、一群を発見。 このファイルの冒頭に、#include "../libz/zlib.h" も発見。

static int gz_magic[2] = {0x1f, 0x8b}; /* gzip magic header */

こんなのも定義されてたから、ここで一手に引受ているんだな。 cread.cというファイル名は、コンプレスされたのを読み出すって理解でいいのかな。

それから、もう一つの悩み、 elf32_exec なんだけど、これが普通のelfを読んで いると思える。だけど、この関数は、 loadfile_elf.c に配置されてる。が、 そんなファイルは、構成リストには掲載されていないんだ。 でも、このファイルの内容を読むと、大事な情報を表示してるっぽい。

entry

その大事な情報ってのは、起動メッセージに entry point at 0x201000 って出てるけど、これが対応するんだな。 そして、その前に数種の数字が報告されてるけど、ロードされたサイズだろう。

qe# readelf -a /bsd
ELF Header:
  Entry point address:               0xd0201000
   :
Program Headers:
  Type           Offset   VirtAddr   PhysAddr   FileSiz MemSiz  Flg Align
  LOAD           0x000000 0xd0200000 0x00200000 0xa89a13 0xa89a13 R E 0x1000
  LOAD           0xa8a000 0xd0c8a000 0x00c8a000 0x251404 0x251404 R   0x1000
  LOAD           0xcdc000 0xd0edc000 0x00edc000 0x30008 0x30008 RW  0x1000
  LOAD           0xd0c000 0xd0f0d000 0x00f0d000 0x00000 0x117000 RW  0x1000
  OPENBSD_RANDOM 0xcd9000 0xd0ed9000 0x00ed9000 0x02404 0x02404 RW  0x4
   :
 Section to Segment mapping:
  Segment Sections...
   00     .text .kutext
   01     .rodata .openbsd.randomdata
   02     .data .kudata
   03     .bss
   04     .openbsd.randomdata  

下記の様なprintf マクロを使って、途中経過を表示してるからね。

loadfile_elf.c:139:       PROGRESS(("%s%lu", first ? "" : "+",
loadfile_elf.c:169:       PROGRESS(("+%lu",
loadfile_elf.c:238:                PROGRESS(("%s%ld", first ? " [" : "+",
loadfile_elf.c:267:       PROGRESS(("]"));

尚、下記のプラスで結合された数字列の左の方は、それぞれのファイルサイズを表わしている。

booting hd0a:/bsd: 11049491+2429956+196616+0+1142784 [747466+107+629952+649230]4

run_loadfile

sys/arch/i386/stand/libsa/elf32.c

#include "../../../../lib/libsa/loadfile.h"

#undef ELFSIZE
#define ELFSIZE  32

#include "../../../../lib/libsa/loadfile_elf.c"

ひょんな事から、こんな反則技に近い技法を発見しちゃった。コンパイラーの気持に なって考えてあげないとな。勿論elf32.cは構成リストに登録されてた。

src/sys/arch/i386/stand/libsa/exec_i386.c に、件の関数が定義されている。

cd.consdev = cn_tab->cn_dev;
cd.conspeed = com_speed;

        addbootarg(BOOTARG_BOOTMAC, sizeof(bios_bootmac_t), bootmac);

ucode_load();

/* Pass memory map to the kernel */
mem_pass();

printf("entry point at 0x%x\n", (int)entry);

/* stack and the gung is ok at this point, so, no need for asm setup */
(*(startfuncp)entry)(howto, bootdev, BOOTARG_APIVER, marks[MARK_END],
    extmem, cnvmem, ac, (int)av);
/* not reached */

こんな具合にカーネルを起動する準備をして、最後にカーネルを起動してる。

ucode_load

if (strcmp((char *)vendor, "GenuineIntel") == 0) {
        snprintf(path, sizeof(path),
            "%s:/etc/firmware/intel/%02x-%02x-%02x",
            cmd.bootdev, family, model, stepping);

uc.uc_addr = (uint64_t)buf;
uc.uc_size = (uint64_t)buflen;
addbootarg(BOOTARG_UCODE, sizeof(uc), &uc);

使っているCPUを調べて対応するパッチコードを取り出して、それをカーネルへの 土産にしてる。カーネルが起動した後に、各種デバイスのファームウェアと共に 一気にパッチをあてる戦法なのかな。だったら、bootの途中で読み込んでおく 必要は無いだろうに。

カーネルは汎用的であるべきだから、マシン依存コードを持ち込むのを避けるためだな。 チャッピーの意見も聞いてみた。 カーネルが起動した後にマイクロコードを直接読み込むのではなく、ブートローダーを介して引数として渡す設計が採用されている理由は、カーネルの汎用性や移植性を保つためです。カーネル内部にマイクロコードの更新を組み込むと、マシン依存コードが増え、移植性や保守性が低下します。また、ハードウェア依存の処理はできるだけカーネル外で管理し、システムの設定はブートローダーや外部で管理することで、より柔軟かつ安定した運用が可能になります。

ついでに調べた、下記のメッセージ

[ using 2027332 bytes of bsd ELF symbol table ]

ddb/db_elf.c が出力してたんで、カーネルの中での出来事だな。

etags from Makefile

ここまでやってきて、あれだけど、boot専用のetagsが有ったら便利だと思った。 Makefileを眺めると、SRCS変数にソースのリストが蓄積されてるんだけど、残念 な事に、そのソースはあちこちに分散してるんだ。だから、それぞれのソースに 適切なPATHを前置してあげないと、使えない。どうせ、それをやるなら、汎用的 なものにしたい。そうすれば、grepとかwcにも使えるぞ。

こういうアイデアが浮んだら、コーダーはチャッピーに任せてしまえば良い。 世の中は進歩してるんで、人間のやる事はアイデア出汁なんだな。別に後ろ暗い 事ではないからねと、自分を納得させる。

で、チャッピーは、makeのオプションである、-V を提示してきた。変数の内容を 確認できるとな。

ad$ pwd
/sys/arch/i386/stand/boot
ad$ MACHINE=i386 make -V S
${.CURDIR}/../../../..
ad$ MACHINE=i386 make -V .CURDIR
/sys/arch/i386/stand/boot

これを元にして、変数のSとSADIRそれに.CURDIRを展開したPATHを付けてリストを作成するスクリプトを 作成してくれた。

qe# export MACHINE-i386
qe# sh gen_slist.sh >slist.txt
ge$ cat slist.txt
srt0.S
conf.c
/sys/arch/i386/stand/boot/../../../../stand/boot/boot.c
/sys/arch/i386/stand/boot/../../../../stand/boot/bootarg.c
 :
/sys/arch/i386/stand/boot/../libsa/debug_i386.S
 :
/sys/arch/i386/stand/boot/../../../../lib/libz/inflate.c

マシン依存と非依存が入り乱れているな。

qe$ etags `cat slist.txt`
qe$ wc `cat slist.txt`
    :
   18420   77406  549315 total
qe$ grep  inflate `cat slist.txt` | sed 's#/.*/##'
cread.c:        if (inflateInit2(&(s->stream), -15) != Z_OK)
cread.c:                inflateEnd(&(s->stream));
cread.c:                inflateEnd(&(s->stream));
cread.c:                s->z_err = inflate(&(s->stream), Z_NO_FLUSH);
cread.c:                                        inflateReset(&(s->stream));
cread.c:                        inflateEnd(&(s->stream));
cread.c:                        inflateInit2(&(s->stream), -15);
 :
#!/bin/sh
# Usage:  sh gen_slist.sh > slist.txt

MAKEFILE=Makefile

S=$(make -f "$MAKEFILE" -V S)
SADIR=$(make -f "$MAKEFILE" -V SADIR)
CURDIR=$(make -f "$MAKEFILE" -V .CURDIR)

awk -v S="$S" -v SADIR="$SADIR" -v CURDIR="$CURDIR" '
BEGIN {
    current_path = ""
}

# .PATH: ディレクティブ
$1 == ".PATH:" {
    path = $2

    # ${S}, ${SADIR}, ${.CURDIR} を展開する
    gsub(/\$\{S\}/, S, path)
    gsub(/\$\{SADIR\}/, SADIR, path)
    gsub(/\$\{\.CURDIR\}/, CURDIR, path)

    current_path = path
    next
}

# SRCS= or SRCS+=
/^SRCS[ \t]*[+]?=/ {
    line = $0
    sub(/^SRCS[ \t]*[+]?=[ \t]*/, "", line)

    # 継続行対応
    while (match(line, /\\[ \t]*$/)) {
        line = substr(line, 1, RSTART-1)
        getline more
        sub(/[ \t]*$/, "", more)
        line = line " " more
    }

    n = split(line, a, /[ \t]+/)

    for (i = 1; i <= n; i++) {
        file = a[i]
        if (file == "") continue

        # file にも ${.CURDIR} が含まれる場合展開
        gsub(/\$\{\.CURDIR\}/, CURDIR, file)
        gsub(/\$\{S\}/, S, file)
        gsub(/\$\{SADIR\}/, SADIR, file)

        if (current_path == "") {
            print file
        } else {
            print current_path "/" file
        }
    }
}
' "$MAKEFILE"

README

南回り、北回りの遭遇、列島のホモ・サピエンス なんて本を読んだ。

高度成長期には、公共事業って事で、日本中を掘り返して巨大インフラが盛んに 造られた。その余波で、地中の遺跡が多数出土した。考古学者もウハウハで論文の ネタに困る事は無かった。

しかし、低成長期になると、それもままならない。国からのか細い資金が頼り。 税金が投入される訳だから、それなりの成果が求められる。いきおい、大胆な 事はできなくなる。この辺の事情は、番組とか映画の制作と一緒だな。 一度ヒットしたものの2番煎じで小粒なものしか出来無くなる。

日本人は、どこから来たか? その壮大なテーマを、石器技術の伝搬から解析 しよと奮闘された先生の報告。

出アフリカで人類は、中東に進出した。そこから更に移動してくんだけど、東に 進む場合、ヒマラヤ山脈の北側を通るか、南側を通るかしかない。その分岐点 あたりで、現地の考古学者と協力して、いろいろと発掘。ここら辺を掘ったら 遺跡が出土しそうって、石油の探査より感に頼る方法だな。

狼に襲われそうになりながらも、多数の石器を発掘。ひょっとしたらデータマイニング よりも凄い事じゃなかろうか。まあ、その結果をふまえて、日本での発掘。

場所は、群馬県から長野県にぬける高速道路あたり。八風山トンネルの真上あたり が良さそう。あちこちに頭を下げて、発掘許可を得る。各種の法律とかもからんで くるので、とんでもない書類が必要。

やっと認可が下りて、さて、どこを掘る? 限られた資金と期間。2メートルぐらい 掘らないと、目的の石器は出てこない(有ればの話ですが)。超ストレスだと思うぞ。 楽天的じゃないと、やれないよな。まあ、科学って、そういうものか。そうじゃ なくちゃ、発見は無いわな。孤独な戦いであります。

ああ、技術の伝搬って事なら、身近にあったな。 日本で東日本(関東など)が50Hz、西日本(関西など)が60Hzと周波数が異なる理由は、明治時代に東京電力はドイツ(AEG社)の50Hz発電機を、大阪電力はアメリカ(ゼネラル・エレクトリック社)の60Hz発電機をそれぞれ導入した歴史的経緯によります。現在も50Hzと60Hzの境界線(新富士変電所~佐久間周波数変換所~飛騨変換所)が存在します。 こういうのは、既知すぎて論文にならないな。

無料でLinuxカーネルの仕組みを学習できる「Linuxカーネルエクスプローラー」


This year's Index

Home