/ ^ ?
   西田 亙の本:GNU 開発ツール -- hello.c から a.out が誕生するまで --

Categories Books | Hard | Hardware | Linux | MCU | Misc | Publish | Radio | Repository | Thoughts | Time | UNIX | Writing | プロフィール


2006-07-31 (Mon)

[Publish] Oversea Publishing

自費出版への道

突然ですが、現在自費出版の準備を進めています。私がかねてより手懸けたかったテーマは、「ハードウェアとソフトウェアの接点」なのですが、市場の雑誌や書籍はいずれかの側に大きく傾いているために、これまで受け皿がありませんでした。

また、このページでも再三ご紹介している通り、過去の優れた書籍はことごとく絶版という憂き目に合っています。著者と言えども、原稿がひとたび出版社にわたってしまえば、自分の意志で再版を決めることはできないのです。

レイアウトや書籍の装丁についても、ほとんどの場合は出版社におまかせであり、自分のイメージ通りの本を納得いくまで、作り込める訳ではありません。

もちろん、自費出版には大きなリスクが伴う訳ですが、妻からの強力なサポートを得て、自分の夢を書籍という形で実現してみることにしました。

Computer Architecture Series

こんなことを言うと、本当に鬼に笑われてしまいそうなのですが、正直に告白しますと、自費出版を決意する前から頭の中では "Computer Architecture Series" というシリーズ名が決まっていました。

通常、アーキテクチャという言葉はハードウェアに対して使われますが、私はソフトウェアもまた、様々なデータ構造とアルゴリズムに基づいた構造物だと考えています。ですから、「ハードウェアとソフトウェアの接点」という思いを込めながら、Architecture という言葉を選びました。

ハードウェアとソフトウェアを語るとは、大風呂敷もよいところ、神をも恐れぬ不届き者と揶揄されるかもしれませんが、私は本気です。Computer Architecture Series は、以前このメモ上でもご紹介した、Building blocks 方式でストーリーを紡ぎ上げていきます。

GNU開発ツール

第1巻のテーマとして何を選択するか、長い間迷い続けたのですが、まずは「道具の習得」から始めようということで、"GNU開発ツール" を取り上げることにしました。

既に雑誌連載や特集などを通じて、GNU開発ツールに関しては多くのページを寄稿してきましたが、今回の原稿は約2年のブランクの間に一から練り直した、全く新しいものです。具体的な内容については、近々このページ上でもご紹介できると思います。

紙を選ぶ

紙・紙・紙 この貴重な体験を進行形式でご紹介しておきたいと思います。既に、いくつかの失敗も経験していますが、出版において最も大切なことは、信頼できる印刷会社さんとの出会いではないかと思います。

今回は、妻の会社のパンフレットや広告デザインでお世話になっている、明星企画さんにレイアウトから印刷・製本までをお願いしました。本作りは家造りにも似ており、紙の選定、装丁の種類、レイアウトの決定など、こまごまとした打ち合わせが必要です。また、校正作業に入ると、途方もない量の修正箇所が発生します。このような手間暇かかる問題に、嫌な顔一つ見せることもなく、根気強く、かつ丁寧に付き合って頂けるスタッフに出会うことが、何よりも大切であると思います。

紙の選定ひとつをとっても、それこそ星の数ほどの種類がありますので、この中から自分たちの感性に合ったひとつの紙を拾い上げるためには、かなりの労力を必要とします。

紙の選定作業 今回の書籍デザインにあたっては、妻の会社のデザイナーである清家さんにお世話になっていますが、紙の質に関しては「黒いインクと紙とのコントラスト」を配慮して、「白い紙」を目指すことになりました。

"紙が白いのは当たり前じゃん" という方もおられるかもしれませんが、注意深く観察すると、ほとんどの紙には黄色や赤み・青みが入っています。純白に近い印刷用紙というのは、実は極めて少数派なのです。

「紙を求めて三千里」ではありませんが、私達のイメージを紙の専門卸業者さんに伝えながら、打ち合わせが続きます。私達の理想に一番近い紙が見つかったものの、在庫がありません。仕方なく、2番目の候補で作業を進めていたところ、卸業者さんから電話あり。「日本中の在庫を調べたところ、ある倉庫でご希望の紙を見つけました!」

紙厚を測る これで、ようやく紙が決定しました。

紙の質と共に大きなファクターが厚さです。薄すぎると安っぽい感じになってしまいますし、ラインマーカーが裏に染み出てしまいます。厚すぎるとページをめくる際にそそり立ってしまい、具合が良くありません。薄すぎず、厚すぎずの紙厚を求め、ゲージで測り手触りを確かめながら、最適の厚さを絞り込んでいきます。

色々大変なことも多いのですが、様々な業種の方達と進める本作りは、とても楽しく、充実感があります。

印刷間近

出版作業は既に校正の最終段階に入っており、印刷も間近です。予約受付開始もカウントダウンに入っていますが、産声を上げる寸前の Oversea Publishing をご紹介いたします。読者の方に心から喜んで頂ける書籍を目指して、スタッフ共々鋭意作業中ですので、ご期待ください。


2006-07-30 (Sun)

[UNIX] malloc failure (その4)

いよいよ、malloc failureシリーズも最終回。前回作成した malloc_null.c は、ライブラリ中の malloc をハイジャックし、ゼロを呼び出し元に返すだけでしたが、今回は本来の malloc を内部で呼び出し、メモリ割り当てを実行できる wrapper function に挑戦してみましょう。

Wrapper function を実装するためには、malloc ライブラリ関数のエントリアドレスの取得など初期化処理が必要になります。一般のアプリケーションであれば、処理の前後で初期化・終了処理を行うことは簡単ですが、共有オブジェクトで実現するとなると、はてと悩んでしまいます。

実は、GCCにはこのような場合のために、特別な仕掛けが用意されているのです。

__attribute__((constructor)), __attribute__((deconstructor))の活用

まず、次に示す puts_initfini.c を用意してください。

    1  #include <stdio.h>      // fprintf(), stderr
    2
    3  void init(void) __attribute__((constructor));
    4  void fini(void) __attribute__((destructor));
    5
    6  void init() {
    7    fprintf(stderr, "puts_initfini.so: initialized.\n");
    8   }
    9
   10  void fini() {
   11    fprintf(stderr, "puts_initfini.so: finalized.\n");
   12   }
   13
   14  int puts(const char* msg) {
   15    return fprintf(stderr, "puts_initfini.so: %s\n", msg);
   16   }

前回作成した puts 関数に加えて、init, fini 関数のふたつが加えられています。いずれも簡単なメッセージを表示するだけのものですが、3・4行のプロトタイプ宣言に注目してください。

3行目では、GCCの拡張機能である __attribute__ 指定子を使い、init 関数をコンストラクターとして宣言しています。このコンストラクターはC++の概念とはことなり、該当モジュールがロードされた際に、初期化関数として自動実行されることを意味しています。

4行目でも同様にして、fini 関数をデストラクターとして宣言しています。この結果、本モジュールがアンロード(unload)される際に、終了処理として fini 関数が自動的に実行されるようになります。

puts_initfini.c を共有オブジェクトしてビルドし、前回使用した puts.c および puts_test.c と併せて実験してみましょう。まず、puts.so, puts_initfini.so 共有オブジェトを作成します。

$ gcc -Wall -fPIC -shared -o puts.so puts.c
$ gcc -Wall -fPIC -shared -o puts_initfini.so puts_initfini.c
$ file puts.so puts_initfini.so
puts.so:          ELF 32-bit LSB shared object, Intel 80386, version 1 (SYSV), not stripped
puts_initfini.so: ELF 32-bit LSB shared object, Intel 80386, version 1 (SYSV), not stripped

次に、puts_test 実行可能ファイルを作成し、最初に puts.so でテストします。

$ gcc -Wall -o puts_test puts_test.c
$ LD_PRELOAD="./puts.so" ./puts_test
puts.so: Hello, world!

前回通り、正常に動作しています。次に、今回作成した puts_initfni.so の出番です。

$ LD_PRELOAD="./puts_initfini.so" ./puts_test
puts_initfini.so: initialized.
puts_initfini.so: Hello, world!
puts_initfini.so: finalized.

init, fini 関数が適切に呼び出されていることが確認できます(puts_initfini.c は Linuxだけでなく、*BSDでも動作します)。このテクニックは、共有ライブラリを作成する際によく使われますが、考えれば考えるほど不思議なコードの動きです。どうしてこのような芸当が可能になるかについては、ELFの内部構造を学ぶ必要があります(ヒントは .ctors, .dtors セクションです)。

dlsym 関数によるライブラリ関数のアドレス取得

下準備は以上で整いましたので、いよいよ動的リンクによるアドレス取得に挑戦してみましょう。静的リンクとはことなり、動的リンクでは外部参照シンボルのアドレスは、プロセス起動時まで決まっていません。アドレス解決はダイナミックリンカーローダの役割ですが、今回のようにプログラマが意図的にアドレス解決を行うことができるように、dlsym(Dynamic Linking loader: SYMbol)関数が用意されています。

 SYNOPSIS
      #include <dlfcn.h>

      void *dlopen(const char *filename, int flag);
      void *dlsym(void *handle, const char *symbol);

  dlsym
      The  function  dlsym() takes a "handle" of a dynamic library returned by
      dlopen and the NUL-terminated symbol name, returning the  address  where
      that  symbol  is loaded into memory.  If the symbol is not found, in the
      specified library or any of the libraries that were automatically loaded
      by  dlopen()  when  that library was loaded, dlsym() returns NULL.  (The
      search performed by dlsym() is breadth first through the dependency tree
      of  these  libraries.)   Since the value of the symbol could actually be
      NULL (so that a NULL return from dlsym() need not  indicate  an  error),
      the  correct  way to test for an error is to call dlerror() to clear any
      old error conditions, then call dlsym(), and then call dlerror()  again,
      saving  its  return  value into a variable, and check whether this saved
      value is not NULL.

      There are two special pseudo-handles, RTLD_DEFAULT and  RTLD_NEXT.   The
      former  will  find  the first occurrence of the desired symbol using the
      default library search order.  The latter will find the next  occurrence
      of  a  function  in  the  search  order after the current library.  This
      allows one to provide a wrapper around  a  function  in  another  shared
      library.

man ページに書かれている通り、本来は dlopen 関数と併せて使われますが、Wrapper function を実装する場合は、第一引数に RTLD_NEXT マクロ(Linux, BSD 共に -1L で定義)を指定し、第二引数にシンボル名を指定すると、目的のシンボルアドレスが返されます。

この RTLD_NEXT マクロを指定する点がミソですが、具体的なコードで説明した方が理解が早いかと思います。次の、puts_wrapper.c をご覧ください。

    1  #include <stdio.h>      // fprintf(), stderr, stdout
    2  #include <dlfcn.h>      // RTLD_NEXT, dlsym()
    3  #include <unistd.h>     // _exit()
    4
    5  static void init(void) __attribute__((constructor));
    6  static void fini(void) __attribute__((destructor));
    7
    8  static int (*libc_puts)(const char* msg);
    9
   10  static void init(void) {
   11    libc_puts = dlsym(RTLD_NEXT, "puts");
   12    fprintf(stderr, "puts_wrapper.so: libc_puts = %p\n", libc_puts);
   13    if (libc_puts == 0)
   14      _exit(1);
   15   }
   16
   17  static void fini(void) {
   18    fprintf(stderr, "puts_wrapper.so: terminated.\n");
   19   }
   20
   21  int puts(const char* msg) {
   22    fprintf(stdout, "puts_wrapper.so: ");
   23    return (*libc_puts)(msg);
   24   }

Name polution を避けるために、今回から global 宣言が不要なシンボルは全て static 宣言を行っています。8行目で関数ポインタ変数 libc_puts を定義し、11行目で dlsym 関数を利用して「ライブラリ関数の puts シンボル」のアドレスを入手しています。

本来であれば、puts_wrapper.c 自身の内部で定義されている puts 関数のアドレスが返されるところですが、RTLD_NEXT マクロが指定されているため、ダイナミックリンカーローダは次のライブラリ(すなわちCライブラリ)の探索を行い、目的のアドレスを得ることができます。

puts ライブラリ関数のエントリアドレスさえ入手してしまえば、後は簡単です。23行で関数ポインタ変数を介して、間接的に puts を呼び出し、仕事は終了です。このあたりは、アドレスを低レベルで自在に操れるC言語の独壇場と言えるでしょう。

ちなみに failmalloc は、GNU C ライブラリ版 malloc の拡張機能であるフック関数を利用して、wrapping を実現しています。*BSD は GNU 開発ツールは採用しているものの、システムの根幹となる基本ライブラリは自前で実装していますので、failmalloc の仕組みは使えません。

それでは実験です。まず Linux の場合ですが、RTLD_NEXT マクロを使用するためには、_GNU_SOURCE マクロを定義しておく必要があります。また、内部で使用している dlsym 関数に対して、-ldl オプション(libdl)を指定する必要があります。

Debian $ gcc -Wall -D_GNU_SOURCE -fPIC -shared -o puts_wrapper.so puts_wrapper.c -ldl
Debian $ LD_PRELOAD="./puts_wrapper.so" ./puts_test;echo $?
puts_wrapper.so: libc_puts = 0xb7ecb540
puts_wrapper.so: Hello, world!
puts_wrapper.so: terminated.
0

puts ライブラリ関数の wrapping に成功しています。次に OpenBSD ですが、こちらは _GNU_SOURCE マクロも -ldl オプションも必要ありません。

OpenBSD $ gcc -Wall -fPIC -shared -o puts_wrapper.so puts_wrapper.c   
OpenBSD $ LD_PRELOAD="./puts_wrapper.so" ./puts_test;echo $?
puts_wrapper.so: libc_puts = 0x53e95f8
puts_wrapper.so: Hello, world!
puts_wrapper.so: terminated.
0

Linux と BSD では共有ライブラリのアドレスがことなっていますが、どちらも正常に動作しています。

malloc_wrapper

ここまでの知識の総括として、malloc の wrapper function を作成してみましょう(malloc_wrapper.c)。

    1  #include <stdio.h>      // fprintf(), stderr
    2  #include <dlfcn.h>      // RTLD_NEXT, dlsym()
    3  #include <unistd.h>     // _exit()
    4
    5  static void init(void) __attribute__((constructor));
    6  static void fini(void) __attribute__((destructor));
    7
    8  static void* (*libc_malloc)(size_t);
    9  static int calls = 0;
   10  static long long allocated = 0LL;
   11
   12  static void init() {
   13    libc_malloc = dlsym(RTLD_NEXT, "malloc");
   14    fprintf(stderr, "malloc_wrapper.so: libc_malloc = %p\n", libc_malloc);
   15    if (libc_malloc == 0)
   16      _exit(1);
   17   }
   18
   19  static void fini() {
   20    fprintf(stderr, "malloc_wrapper.so: %d calls, %lld bytes allocated.\n", \
   21            calls, allocated);
   22   }
   23
   24  void* malloc(size_t size) {
   25    void* ptr;
   26
   27    calls++;
   28    ptr = (*libc_malloc)(size);
   29    allocated += (long long) size;
   30    // fprintf(stderr, "malloc_wrapper.so: malloc(%d)\n", size);
   31    return ptr;
   32   }

コードについて、説明の必要はないと思います(30行は画面がうるさくなるのでコメントアウト)。calls, allocated 変数を用意し、malloc のコール回数、割り当てた総メモリ容量を最後に出力するようにしています。ビルドは次の通りです。

Debian $ gcc -Wall -D_GNU_SOURCE -fPIC -shared -o malloc_wrapper.so  mmalloc_wrapper.c -ldl

早速、ls, awk, perl で実験してみましょう。

Debian $ LD_PRELOAD="./malloc_wrapper.so" ls -F /
malloc_wrapper.so: libc_malloc = 0xb7e1ad80
bin/    dev/   initrd.img@  media/  proc/  spoon@  tmp/  vmlinuz@
boot/   etc/   lib/         mnt/    root/  srv/    usr/
cdrom@  home/  lib64/       opt/    sbin/  sys/    var/
malloc_wrapper.so: 189 calls, 157037 bytes allocated.
$ LD_PRELOAD="./malloc_wrapper.so" awk 'BEGIN{print "Hello, world"}' < /dev/null
malloc_wrapper.so: libc_malloc = 0xb7ed5d80
Hello, world
malloc_wrapper.so: 54 calls, 7114 bytes allocated.
$ LD_PRELOAD="./malloc_wrapper.so" perl -e 'print "Hello, world!\n"'
malloc_wrapper.so: libc_malloc = 0xb7ebfd80
Hello, world!
malloc_wrapper.so: 652 calls, 251432 bytes allocated.

上手にラッピングできているようです。後は、malloc_wrapper.c を改造し、一定頻度もしくは一定回数呼び出し後に NULL を返したり、メッセージを syslog 経由で出力したりと、色々楽しめると思います。

動的リンクと共有ライブラリは、サーバーもしくは組み込みシステムの構築・管理を行う上で避けては通れない重要な部分ですが、これまでその技術背景が語られることはあまりなかったようです。

この機会に「空気と水」の存在を再認識するのも良いでしょう。


2006-07-22 (Sat)

[UNIX] malloc failure (その3)

libc hijack は楽し

今回は、いよいよ malloc failure シリーズの最終回第3回。楽しい "libc hijack" を始める前に、軽く準備体操をしておきましょう。次に示す、puts_test.c をご用意ください。

int puts(const char*);

int main() {
  puts("Hello, world!");
  return 0;
 }

printf 関数の代わりに puts 関数を用いた、簡易版 Hello, world! プログラムです。

$ gcc -Wall -o puts_test puts_test.c 
$ ./puts_test 
Hello, world!
$ nm -u puts_test
         w __gmon_start__
         w _Jv_RegisterClasses
         U __libc_start_main@@GLIBC_2.0
         U puts@@GLIBC_2.0

いつも通りビルドを行い、実行可能ファイルの内部で __libc_start_main および puts シンボルが未定義になっていることを確認します。当然のことながら、puts_test は単体で正常に動作しますが、この時の puts 関数は共有Cライブラリ libc.so に由来しています。

それでは、ここからが本題です。puts_test を実行すると、裏方でダイナミックリンカーローダ(ld-linux.so.2)が起動され、最終リンクを完成させるのでした。

この時、ダイナミックリンカーローダはシンボル解決のために、あらかじめ指定された libc.so を探索するのですが、LD_PRELOAD と呼ばれる環境変数が定義されていると、指定されたファイルを先に探索するような仕掛けが組み込まれているのです。man ld.so を再度ご覧ください。

      LD_PRELOAD
             A  whitespace-separated  list  of additional, user-specified, ELF
             shared libraries to be loaded before all  others.   This  can  be
             used to selectively override functions in other shared libraries.
             For setuid/setgid ELF binaries, only libraries  in  the  standard
             search directories that are also setgid will be loaded.

ここに書かれている通り、ダイナミックリンカーローダは LD_PRELOAD が定義されていると、記述されている共有オブジェクトを先にメモリー上にロードし、シンボル探索を行います。結果として、普段使用しているCライブラリー関数を Override、分かりやすい言葉で表現すれば Hijack することが可能になるのです。

それでは、Hijack 用の puts 関数を用意してみましょう(puts.c)。

#include <stdio.h>     // fprintf()

int puts(const char* msg) {
  return fprintf(stderr, "puts.so: %s\n", msg);
 }

乗っ取り版 puts 関数は、内部で fprintf 関数を用いて引数のメッセージを標準エラー出力(ファイルディスクリプター2番)に出力しています。

実際に、puts.c から共有オブジェクトファイルを生成してみましょう。共有オブジェクトを出力する場合は、gcc に対してふたつのオプションを指定します。

$ gcc -Wall -fPIC -shared -o puts.so puts.c
$ file puts.so
puts.so: ELF 32-bit LSB shared object, Intel 80386, version 1 (SYSV), not stripped

-fPIC は Position Independent Code の略であり、日本語に訳すと位置独立型実行コードを意味しています。

次の -shared は、名前の通り共有オブジェクトファイルを出力するためのオプションです。慣習として、共有オブジェクトの拡張子は so (Shared Object)が指定されます。

共有オブジェクトの概念を理解するためには、機械語の知識が必要になりますので、詳細は割愛しますが、当面はふたつのオプションを定型句として覚えておきましょう。

それでは、libc-hijack に挑戦です。

$ LD_PRELOAD="./puts.so" ./puts_test
puts.so: Hello, world!
$ ./puts_test 
Hello, world!

環境変数 LD_PRELOAD に puts.so をセットしますが、この時カレントディレクトリを意味する ./ を忘れないようにしてください。ダイナミックリンカーローダは共有オブジェクトをロードする際に、絶対パスを必要としますので、puts.so だけではロードエラーとなります。

表示されたメッセージを見ると、"Hello, world!" の前に "puts.so: " が前置されていますから、libc-hijack は見事に成功しています。

同一の実行可能ファイルでありながら、LD_PRELOAD によってプロセスが全く違う挙動を示すことは、不思議な気もしますが、これこそが動的リンクの裏側でダイナミックリンカーローダが活躍している証拠です。

malloc_dumb

ここまでの知識があれば、failmalloc のひな形を作ることは簡単です。呼び出されると常に NULL を返す、malloc 関数を作成してみましょう(malloc_dumb.c)。

#include <sys/types.h>  // size_t

void* malloc(size_t size) {
  return (void*) 0;
 }

わずか3行のプログラムであり、説明の必要もありません。先ほどと同じように、共有オブジェクトを作成します。

$ gcc -Wall -fPIC -shared -o malloc_dumb.so malloc_dumb.c
$ LD_PRELOAD="./malloc_dumb.so" ls /
ls: memory exhausted

試しに ls コマンドで効果を試してみると、"memory exhausted" エラーが表示されました。malloc_dumb.so は hijack に成功したようです。

malloc_null

malloc_dumb.so でも実験には使えるのですが、出来れば malloc の呼び出し履歴を引数と共に観察してみたいものです。そこで、malloc 呼び出しを記録する malloc_null.c を用意しました(2006/7/23 UINT_MAX に修正)。

#include <unistd.h>    // write()
#include <limits.h>    // UINT_MAX

int itoa(unsigned int num, char* buf) {
  int cnt = 0;
  char local[ 11 ];
  char *b = buf, *l = local;

  if (num > UINT_MAX) {
    *b = 0;
    return 0;
   }

  while (num) {
    *l++ = (char) ((num % 10) + '0');
    num /= 10;
   }
  l--;

  while (l >= local) {
    *b++ = *l--;
    cnt++;
   }
  *b = 0;

  return cnt;
 }

void* malloc(unsigned size) {
  char buf[ 11 ];
  int len;
 
  write(1, "malloc_null: malloc(", 20);
  len = itoa(size, buf);
  if (len) write(1, buf, len);
  write(1, ")\n", 2);
  return (void*) 0;
 }

引数のサイズを表示するために printf は敢えて使わず、独自の itoa 関数(64bit環境の方は適宜コードを修正してください)と write 関数を用いていますが、これは printf 自身が内部で malloc を呼び出している場合への配慮です。

malloc_null.c は、呼び出された際の引数を表示し、呼び出し元にゼロを返すだけの簡単なプログラムですが、大変面白い実験を行うことができます(引数を表示している点がミソです)。

$ gcc -Wall -fPIC -shared -o malloc_null.so malloc_null.c 

failmalloc とはことなり、malloc_null.c は Linux, BSD どちらの環境上でもビルド可能です(Mac OS X は不可)。

gcc

それでは、手始めに gcc を解析してみましょう。

$ gcc -dumpversion
4.0.4
$ LD_PRELOAD="./malloc_null.so" gcc -dumpversion
malloc_null: malloc(60)
malloc_null: malloc(352)
malloc_null: malloc(31)
malloc_null: malloc(31)
malloc_null: malloc(60)
malloc_null: malloc(34)
malloc_null: malloc(34)
malloc_null: malloc(24)
malloc_null: malloc(8)
malloc_null: malloc(40)

gcc: out of memory allocating 40 bytes after a total of 0 bytes

結果ですが、何とも中途半端なエラー検出になっています。本来は最初の60バイトのアローケーション時にエラーを検出すべきですが、gcc は9回のエラーを見逃し、10回目でようやく停止しています。

良いように解釈すれば、内部で Allocation error を検出し、何度か再割り当てに挑戦しているとも考えられますが、引数の値から判断するとそうでもなさそうです。なぜ Segmentation fault を起こさずに、プロセスが走り続けているのか、興味があるところです。

as

GCC パッケージがこの調子ですと、binutils パッケージも先が思いやられそうです。

$ as -v
GNU assembler version 2.16.91 (i486-linux-gnu) using BFD version 2.16.91 20060118 Debian GNU/Linux
$ LD_PRELOAD="./malloc_null.so" as -v        
malloc_null: malloc(60)
malloc_null: malloc(352)
malloc_null: malloc(34)
malloc_null: malloc(34)
malloc_null: malloc(60)
malloc_null: malloc(31)
malloc_null: malloc(31)
malloc_null: malloc(20)
malloc_null: malloc(4)
malloc_null: malloc(33)

as: out of memory allocating 33 bytes after a total of 0 bytes

結果は予想通りでした。最初9回の見逃しパターンは gcc と似通っています。

スクリプト言語シリーズ

メモリ資源の扱いはお手の物のはずの、スクリプト言語を見てみましょう。

$ gawk --version
GNU Awk 3.1.5
Copyright (C) 1989, 1991-2005 Free Software Foundation.

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
$ LD_PRELOAD="./malloc_null.so" awk '{}'
malloc_null: malloc(60)
malloc_null: malloc(352)
malloc_null: malloc(31)
malloc_null: malloc(31)
malloc_null: malloc(60)
malloc_null: malloc(33)
malloc_null: malloc(33)
malloc_null: malloc(60)
malloc_null: malloc(34)
malloc_null: malloc(34)
malloc_null: malloc(60)
malloc_null: malloc(30)
malloc_null: malloc(30)
malloc_null: malloc(21)
malloc_null: malloc(5)
malloc_null: malloc(36)
awk: fatal: init_groupset: groupset: can't allocate 36 bytes of memory (Success)

まず最初に GNU awk 3.1.5 ですが、ご覧の通り満身創痍であります。

$ perl -v

This is perl, v5.8.8 built for i486-linux-gnu-thread-multi

Copyright 1987-2006, Larry Wall

Perl may be copied only under the terms of either the Artistic License or the
GNU General Public License, which may be found in the Perl 5 source kit.

Complete documentation for Perl, including FAQ lists, should be found on
this system using "man perl" or "perldoc perl".  If you have access to the
Internet, point your browser at http://www.perl.org/, the Perl Home Page.

$ LD_PRELOAD="./malloc_null.so" perl
malloc_null: malloc(2712)
Segmentation fault

Perl 5.8.8 は、最初のコールで撃沈。

$ ruby -v
ruby 1.8.4 (2005-12-24) [i486-linux]
$ LD_PRELOAD="./malloc_null.so" ruby   
malloc_null: malloc(80)
Segmentation fault

Ruby 1.8.4 も同じく一撃で停止。GNU ツールに比べると、Perl, Ruby の方が潔く健全に思えてきます。

$ python -V
Python 2.3.5
$ LD_PRELOAD="./malloc_null.so" python   
malloc_null: malloc(36)
Fatal Python error: Py_Initialize: can't make first interpreter
Aborted

Python 2.3.5 は、きちんと最初のエラーを検出し、エラーメッセージを出力しています。

$ lua -v
Lua 5.1  Copyright (C) 1994-2006 Lua.org, PUC-Rio
$ LD_PRELOAD="./malloc_null.so" lua           
Lua 5.1  Copyright (C) 1994-2006 Lua.org, PUC-Rio
malloc_null: malloc(3)
xmalloc: out of virtual memory

目下、私の大のお気に入りである Lua 5.1 も、最初のエラーを捉えており、今回チェックしたアプリケーションの中では最も適切なエラーメッセージを出力しています(xmallocはアロケーションエラーをトラップするためのライブラリ関数)。

今後の発展

次なる目標は、乗っ取り版 malloc から本来のCライブラリー版 malloc を呼び出し、一定の頻度や指定された回数後にアロケーションエラーを引き起こすことですが、このためにはダイナミックロード機能を活用する必要があります。

ということで、"その4"に続きます(次回は本当に最終回?)。


2006-07-18 (Tue)

[UNIX] malloc failure (その2)

リンク方式に見る Linux と BSD の違い

failmalloc の仕組みを理解するためには、"動的リンク" に関する知識が必要になります。C言語によるプログラム開発は、プリプロセス・コンパイル・アセンブル・リンクの4工程を経ますが、最後のリンクはその方式により、動的リンクと静的リンクのふたつに分類されます。

BSD 環境では、危機管理のためにシステムの基幹部分に関するプログラムは静的リンク、それ以外の一般ユーザーアプリケーションは動的リンクにより作成されています。

これに対して、Linux 環境ではほぼ全てのプログラムが動的リンクにより作成されています(Debian Sarge で確認したところ、静的リンクで作成された実行可能ファイルは /sbin/ldconfig ただひとつでした)。この事実が意味するところは、これからの解説で明らかになります。

未完成のプログラム

それでは、恒例の hello.c を題材にして解析を進めましょう。

#include <stdio.h>      // puts()

int main() {
  puts("Hello, world!");
  return 0;
 }

都合により、printf に代わり puts 関数を使用していますが、ごく当たり前の Hello, world! プログラムです。

$ gcc -Wall -o hello hello.c
$ ./hello
Hello, world!

毎度お馴染みの gcc ドライバで、実行可能ファイル hello をビルドしています。ここまでは、どの入門書にも書かれていることですが、私達が本当に知るべき問題は hello の内部に隠されています。

$ file hello
hello: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), for GNU/Linux 2.4.1,
dynamically linked (uses shared libs), not stripped

file コマンドで hello の正体を確認すると、"動的リンクに基づく ELF 実行可能ファイルである" と表示されました。動的リンクというのは、最終的なリンク作業が、実行時に行われることを意味しています。別の表現をすれば、hello は現時点では "リンクが未完了" なのです。

$ nm -u hello
        w __gmon_start__
        w _Jv_RegisterClasses
        U __libc_start_main@@GLIBC_2.0
        U puts@@GLIBC_2.0

nm コマンドの -u (Undefined) オプションは、オブジェクトファイル中の未定義シンボルを表示するためのものです。この結果から、hello は __libc_start_main, puts を外部参照しており、このふたつのシンボルのリンクは現時点で完了していないことが分かります。

上段に表示されている w は Weak symbol の略ですが、今回の解析には必要ないので、grep コマンドで必要な情報のみを残しておきましょう。

$ nm -u hello | grep "U "
        U __libc_start_main@@GLIBC_2.0
        U puts@@GLIBC_2.0

hello が未完成ということは、起動する前に誰かが "完成させる" 必要があります。この "誰か" については、ldd (List Dynamic Dependency)コマンドで知ることができます。

$ ldd hello
       linux-gate.so.1 =>  (0xffffe000)
       libc.so.6 => /lib/tls/libc.so.6 (0xb7e6f000)
       /lib/ld-linux.so.2 (0xb7fb0000)

詳細については割愛しますが、hello を完成させるのは、/lib/ld-linux.so.2 であり、その際には puts 関数を提供する GNU C library (libc6: libc.so.6) が使われます。

完成済みのプログラム

次に、動的リンクの対極に位置する、静的リンクについて見てみましょう。gcc ドライバを用いて、静的リンクでプログラムをビルドするためには、-static オプションを指定します。

$ gcc -Wall -static -o hello_static hello.c
$ wc -c hello hello_static 
  6985 hello
502393 hello_static
509378 total

hello 実行可能ファイルは 6985 バイトでしたが、静的リンク版の hello_static は 502K バイトにも及んでいます。なぜこれほどのサイズ差が生じるのかについては、場を改めて解説いたします。

$ file hello_static 
hello_static: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV),
for GNU/Linux 2.4.1, statically linked, not stripped
$ ldd hello_static 
       not a dynamic executable
$ nm -u hello_static |grep "U "
$

file コマンドで hello_static を解析すると、今度は "静的リンクに基づく ELF 実行可能ファイルである" と表示されました。ldd コマンドでは何も表示されませんので、hello_static は単独で完成したプログラムファイルであることが分かります。これは、他者の助けを借りず、自力で起動できるプログラムであることを意味しています。hello_static 内部に、未定義シンボルは存在しません。

$ ./hello
Hello, world!
$ ./hello_static
Hello, world!

しかしながら、シェル上から観察する限り、どちらも全く同じ挙動を示しており、hello が外部プログラムの助けを借りている様子は、伺うことができません。

隠されたプログラムインタープリタ

そこで登場するコマンドが readelf です。同コマンドに -l オプションを指定して、hello プログラムを解析してみてください。

$ readelf -l hello

Elf file type is EXEC (Executable file)
Entry point 0x80482e0
There are 7 program headers, starting at offset 52

Program Headers:
  Type           Offset   VirtAddr   PhysAddr   FileSiz MemSiz  Flg Align
  PHDR           0x000034 0x08048034 0x08048034 0x000e0 0x000e0 R E 0x4
  INTERP         0x000114 0x08048114 0x08048114 0x00013 0x00013 R   0x1
      [Requesting program interpreter: /lib/ld-linux.so.2]
  LOAD           0x000000 0x08048000 0x08048000 0x0050c 0x0050c R E 0x1000
  LOAD           0x00050c 0x0804950c 0x0804950c 0x00104 0x00108 RW  0x1000
  DYNAMIC        0x000520 0x08049520 0x08049520 0x000c8 0x000c8 RW  0x4
  NOTE           0x000128 0x08048128 0x08048128 0x00020 0x00020 R   0x4
  GNU_STACK      0x000000 0x00000000 0x00000000 0x00000 0x00000 RW  0x4

 Section to Segment mapping:
  Segment Sections...
   00     
   01     .interp 
   02     .interp .note.ABI-tag .hash .dynsym .dynstr .gnu.version .gnu.version_r .rel.dyn .rel.plt .init .plt .text .fini .rodata .eh_frame 
   03     .ctors .dtors .jcr .dynamic .got .got.plt .data .bss 
   04     .dynamic 
   05     .note.ABI-tag 
   06     

実行可能ファイル中の "プログラムヘッダー" と呼ばれる構造が表示されますが、この中の INTERP というヘッダーに注目してください。INTERP ヘッダーの内部に、"Requesting program interpreter: /lib/ld-linux.so.2" という記載がありますが、これは hello がプログラムインタープリタとして /lib/ld-linux.so.2 を必要としていることを意味しています。

インタープリタと言えば、往年の BASIC が有名ですが、ELF におけるプログラムインタープリタは、プログラム実行時に最終的なリンクを完成させる "ダイナミック・リンカーローダ" の役目を負っています。

LD.SO(8)                                                               LD.SO(8)

NAME
      ld.so/ld-linux.so - dynamic linker/loader

DESCRIPTION
      ld.so  loads the shared libraries needed by a program, prepares the pro-
      gram to run, and then runs it.   Unless  explicitly  specified  via  the
      -static  option  to ld during compilation, all Linux programs are incom-
      plete and require further linking at run time.

man ページに記載されている通り、ダイナミック・リンカーローダは、プログラムが必要とする外部ライブラリ(共有ライブラリ)をメモリ上にロードし、未完成プログラムの実行前に最終リンクを行います。

それでは、静的リンク版 hello_static のプログラムヘッダーはどのような構造になっているのでしょうか。

$ readelf -l hello_static
 
Elf file type is EXEC (Executable file)
Entry point 0x80480f0
There are 4 program headers, starting at offset 52

Program Headers:
  Type           Offset   VirtAddr   PhysAddr   FileSiz MemSiz  Flg Align
  LOAD           0x000000 0x08048000 0x08048000 0x6a344 0x6a344 R E 0x1000
  LOAD           0x06b000 0x080b3000 0x080b3000 0x00d34 0x01f98 RW  0x1000
  NOTE           0x0000b4 0x080480b4 0x080480b4 0x00020 0x00020 R   0x4
  GNU_STACK      0x000000 0x00000000 0x00000000 0x00000 0x00000 RW  0x4

 Section to Segment mapping:
  Segment Sections...
   00     .note.ABI-tag .init .text __libc_freeres_fn .fini .rodata __libc_atexit __libc_subfreeres .eh_frame 
   01     .ctors .dtors .jcr .data.rel.ro .got .got.plt .data .bss __libc_freeres_ptrs 
   02     .note.ABI-tag 
   03     

予想通り、INTERP ヘッダーは登録されていませんでした。

ダイナミック・リンカーローダの挙動を掴む

ダイナミック・リンカーローダは黒子として、私達の目が届かない裏方でひっそりと作業していることになります。こうなると、一目その姿を見てみたい・・というのが、人情。そこで、今度は strace (System TRACE)コマンドの登場です。

strace は、指定されたプログラム内部で実行されるシステムコールの呼び出し状況を引数付きで、つぶさに解析するためのシステムツールです。まず最初に、静的リンク版 hello_static を解析してみましょう。

$ strace ./hello_static
execve("./hello_static", ["./hello_static"], [/* 20 vars */]) = 0
uname({sys="Linux", node="Sarge", ...}) = 0
brk(0)                                  = 0x80b5000
brk(0x80d6000)                          = 0x80d6000
fstat64(1, {st_mode=S_IFREG|0644, st_size=225, ...}) = 0
mmap2(NULL, 131072, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb7f34000
write(1, "Hello, world!\n", 14Hello, world!
)         = 14
munmap(0xb7f34000, 131072)              = 0
exit_group(0)                           = ?

前半で初期化のためのシステムコールがいくつか実行されますが、hello_static 内部の main 関数から呼び出される puts 関数の実体は、最後から3番目の write システムコールに対応します。

次に、動的リンク版 hello を見てみましょう。

$ strace ./hello
execve("./hello", ["./hello"], [/* 20 vars */]) = 0
uname({sys="Linux", node="Sarge", ...}) = 0
brk(0)                                  = 0x804a000
access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)
mmap2(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb7ef1000
access("/etc/ld.so.preload", R_OK)      = -1 ENOENT (No such file or directory)
open("/etc/ld.so.cache", O_RDONLY)      = 3
fstat64(3, {st_mode=S_IFREG|0644, st_size=24726, ...}) = 0
mmap2(NULL, 24726, PROT_READ, MAP_PRIVATE, 3, 0) = 0xb7eea000
close(3)                                = 0
access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)
open("/lib/tls/libc.so.6", O_RDONLY)    = 3
read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0\260O\1"..., 512) = 512
fstat64(3, {st_mode=S_IFREG|0755, st_size=1270928, ...}) = 0
mmap2(NULL, 1276892, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0xb7db2000
mmap2(0xb7ee0000, 32768, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x12e) = 0xb7ee0000
mmap2(0xb7ee8000, 7132, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0xb7ee8000
close(3)                                = 0
mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb7db1000
mprotect(0xb7ee0000, 20480, PROT_READ)  = 0
set_thread_area({entry_number:-1 -> 6, base_addr:0xb7db18e0, limit:1048575, seg_32bit:1, contents:0, read_exec_only:0, limit_in_pages:1, seg_not_present:0, useable:1}) = 0
munmap(0xb7eea000, 24726)               = 0
fstat64(1, {st_mode=S_IFREG|0644, st_size=1569, ...}) = 0
mmap2(NULL, 131072, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb7d91000
write(1, "Hello, world!\n", 14Hello, world!
)         = 14
munmap(0xb7d91000, 131072)              = 0
exit_group(0)                           = ?

かなりの数のシステムコールが追加されていますが、先頭部分で /etc/ld.so.nohwcap, /etc/ld.so.preload, /etc/ld.so.cache ファイルへのアクセスが行われています。hello は外部ファイルへアクセスしていませんので、これらはダイナミック・リンカーローダの仕業であることが分かります。確かに、裏方で /lib/ld-linux.so.2 が作業しているようです。

ここで、man ld.so を再度参照してみてください。

      LD_TRACE_LOADED_OBJECTS
             If present, causes the program to list its dynamic library depen-
             dencies, as if run by ldd, instead of running normally.

外部からダイナミック・リンカーローダを制御するために、いくつかの環境変数が用意されています。この中のひとつ、LD_TRACE_LOADED_OBJECTS が定義されていると、ダイナミック・リンカーローダはリンク処理を行わず、対象プログラムが依存している外部ファイルのリストのみを表示して終了します。試してみましょう。

$ LD_TRACE_LOADED_OBJECTS=1 ./hello
       linux-gate.so.1 =>  (0xffffe000)
       libc.so.6 => /lib/tls/libc.so.6 (0xb7de4000)
       /lib/ld-linux.so.2 (0xb7f25000)

bash 上で、起動プロセスに対して一時的に環境変数を指定する場合は、このようにコマンドの前に記述します。1 で定義された LD_TRACE_LOADED_OBJECTS 環境変数は、hello の前に起動される /lib/ld-linux.so.2 に伝えられ、外部依存リストのみを表示して、終了します。

さて、この出力リストに見覚えはないでしょうか。この結果は、ldd コマンドの出力と全く同じですね。

$ which ldd
/usr/bin/ldd
$ file `which ldd`
/usr/bin/ldd: Bourne-Again shell script text executable

実は、ldd コマンドの実体はシェルスクリプトになっており、内部では先ほどの実行例と同じように、LD_TRACE_LOADED_OBJECTS 環境変数を 1 に定義して、対象プログラムを実行しています。

Linux と OpenBSD の /sbin/init

ダイナミック・リンカーローダの仕組みを理解し、その存在を意識できるようになると、途端に心配事が雨雲のように頭の中を広がり、夜も眠れなくなってしまう方もいらっしゃることでしょう。

「動的リンクを受けたプログラム群の生命線は、外部ライブラリはもちろんのこと、ダイナミック・リンカーローダである。もしもこの生命線が絶たれてしまったら、私のサーバーどうなっちゃうの?!」これは、システム管理者として極めて正しい直感と言えます。

PC-UNIX の起動時、カーネルが最初に実行するプロセスは /sbin/init (swapper)ですが、まず OpenBSD 上の /sbin/init を観察してみましょう。

$ uname -a
OpenBSD xxx.xxx.xxx 3.6 GENERIC#278 i386
$ file /sbin/init
/sbin/init: executable, regular file, no read permission
$ ldd /sbin/init
/sbin/init:
ldd: /sbin/init: Permission denied
$ ls -l /sbin/init
-r-x------  1 root  bin  219552 Jan 16  2005 /sbin/init

一般ユーザが file, ldd コマンドを用いて /sbin/init を解析しようとすると、アクセス拒否を受けます。ls コマンドでパーミッションを確認すると、root ユーザのみ Read/eXecute 可能となっています(root ユーザですら Write は許可されていない点に注目)。BSD において、ファイルパーミッションは単なる飾りではなく、長年にわたる失敗と経験に基づいた、最良の設定が施されています。BSD は「失敗することを知っている」のです。

スーパーユーザとして /sbin/init の解析を続けます。

$ sudo file /sbin/init
/sbin/init: ELF 32-bit LSB executable, Intel 80386, version 1, for OpenBSD, statically linked, stripped
$ sudo ldd /sbin/init
/sbin/init:
ldd: /sbin/init: not a dynamic executable

OpenBSD 上の /sbin/init は静的リンクで作成されているため、万が一Cライブラリやダイナミック・リンカーローダに障害が発生したとしても、影響は一切受けないことが分かります。

$ sudo which mount
/sbin/mount
$ sudo file /sbin/mount
/sbin/mount: ELF 32-bit LSB executable, Intel 80386, version 1, for OpenBSD, statically linked, stripped

ちなみに、システム起動時のルートファイルシステムのマウント時に必要となる mount コマンドも、同じく静的リンクで作成されています。このように、BSD ではシステム起動に必要な基幹ツールはすべて静的リンクで作成されているため、仮にCライブラリやダイナミック・リンカーローダに致命的な障害が発生したとしても、最低限の環境で再起動することができるのです。

一方 Linux はどうでしょうか。

$ uname -a
Linux Sarge 2.6.12-1-686-smp #1 SMP Tue Sep 27 13:10:31 JST 2005 i686 GNU/Linux
$ ls -l /sbin/init
-rwxr-xr-x  1 root root 35244 2006-02-10 09:23 /sbin/init
$ file /sbin/init
/sbin/init: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), for GNU/Linux 2.2.0, dynamically linked (uses shared libs), stripped
$ ldd /sbin/init
        linux-gate.so.1 =>  (0xffffe000)
        libsepol.so.1 => /lib/libsepol.so.1 (0xb7f65000)
        libselinux.so.1 => /lib/libselinux.so.1 (0xb7f52000)
        libc.so.6 => /lib/tls/libc.so.6 (0xb7e19000)
        libdl.so.2 => /lib/tls/libdl.so.2 (0xb7e15000)
        /lib/ld-linux.so.2 (0xb7fa8000)

まず、/sbin/init のファイルパーミッション自体が OpenBSD とは似ても似つかぬものとなっています。このように、OpenBSD と Debian のファイルパーミッションには、"大人と赤子" ほどの違いがあるのです。

また、驚いたことに init プログラムが、動的リンクで作成されています。依存ライブラリの内容から見ると、libselinux.so を用いてセキュリティ強化を計っているようですが、これでは本末転倒ではないでしょうか。

Debian GNU/Linux では、/sbin/init だけでなく mount コマンドなどもすべて動的リンクで作成されているため、先ほどの心配にもあった通り、Cライブラリやダイナミック・リンカーローダのアップデートに失敗したり、障害が発生すれば、二度とシステムが起動することはありません。/sbin/init の段階でハングアップしてしまうからです。

Linux と BSD の違い

今回は、動的リンクの仕組みを簡単にご紹介しましたが、技術に対する理解が深まると様々なことが見えてきます。私達は、自分が置かれた環境を中心として世界観を構築しがちですが、たまには住み慣れた世界を離れ、新世界を探訪することも良いものです。「失敗することを知っている BSD」の世界は、Linux に欠けている危機意識と謙虚さ、そして用心深さを教えてくれることでしょう。

つづく

当初の予想を超えて、その3に突入です。


2006-07-15 (Sat)

[UNIX] malloc failure (その1)

failmalloc と危機管理

奥地氏の enbug diary で、とても刺激的なお題を見つけました。failmalloc と呼ばれる共有ライブラリパッケージを使ったお話ですが、要は "意図的にメモリ確保に失敗する malloc 共有ライブラリ" を使い、メモリ管理を内部で正しく行っているかどうか、"外部から" 簡単に検証してみようというものです。

failmalloc は英文の紹介ページですが、その内容はさすがです。なぜ、氏がこのような事を思いついたのかは、次の一言に集約されています。

 経験不足な人が書いたコードはエラーチェックが無茶苦茶である。
 要するに、失敗することを考えていない。

後で述べますが、私は Linux 環境もまた「失敗することを考えておらず、危機意識に欠けている」と常々感じていましたので、failmalloc とは波長がピッタリと一致。折しも、共有ライブラリに関する解説を書き終えたところでしたので、今日は久しぶりにテクニカルライターの観点から、この問題を捉え直してみたいと思います(ハードネタが続いていたことですし)。

failmalloc が教えるもの

既に、failmalloc はネット上のあちこちで話題に上がっているようですが、この問題は後半で紹介されている "スクリプト言語の不甲斐なさ" として済ませるべきものではありません。failmalloc が提起している問題はとても深く、その意味を汲み取るためには、まず failmalloc 自身の仕組みを理解する必要があります。

残念ながら、failmalloc は glibc に依存しているため、BSD や Mac OS X では再現できません。また、GNU 特有のパッケージ構成になっているため、ソースツリー内部は "雑音" が多く、コードの仕組みを読み取ることが難しくなっています。

そこで、今回は10〜30行の小さなプログラムを自分の手で組みながら、failmalloc が提示している問題を理解してみましょう。

man 2 malloc

最初に、malloc ライブラリ関数の仕様を再確認しておきます。


  SYNOPSIS
      #include <stdlib.h>

      void *malloc(size_t size);

  DESCRIPTION
      malloc()  allocates  size  bytes  and returns a pointer to the allocated
      memory.  The memory is not cleared.

  RETURN VALUE
      For  malloc(), the value returned is a pointer to the allocated
      memory, which is suitably aligned for any  kind  of  variable,  or
      NULL if the request fails.

最終行に書かれている "メモリー確保に失敗した時、malloc は NULL を返す" という点が、今回のポイントです。すべてのプログラマーが、malloc のインターフェースに従い、メモリー確保失敗を念頭に置いたコードを書いていれば、問題は発生するはずがありません。ところが、奥地氏が確かめてみると、さにあらず・・。

Segmentation fault 検証

さて、malloc から NULL が返された際、エラーとして対処しなければ、一体何が起きるのでしょうか?実際にコードで実験してみましょう。手始めに、malloc の動作テストプログラム malloc_success.c を用意します。

#include <stdio.h>     // printf()
#include <stdlib.h>    // malloc(), free()

#define BUFSIZE        16

int main() {
  int i;
  unsigned char* buf;
  unsigned char* ptr;

  ptr = buf = malloc(BUFSIZE);
  if (buf == 0) return 1;
  printf("buf = %p\n", buf);

  for (i = 0; i < BUFSIZE; i++)
    *(ptr++) = i % 256;

  ptr = buf;
  for (i = 0; i < BUFSIZE; i++)
    printf("%02X\n", *(ptr++));

  free(buf);
  return 0;
 }

16バイトのメモリ領域を確保し、その開始アドレスを buf ポインタ変数に格納した上で、領域全体を初期化するだけの単純なプログラムです。

$ gcc -Wall -o malloc_success malloc_success.c 
$ ./malloc_success ; echo $?
buf = 0x804a008
00
01
02
03
04
05
06
07
08
09
0A
0B
0C
0D
0E
0F
0

この実行例では、malloc により 0x804a008 番地からのメモリ領域が確保され、初期化は正常に終了しています。次に、強制的に NULL (0) を返すオリジナル malloc を実装し、NULL のチェックを削除した、malloc_fail.c を用意します。

#include <stdio.h>     // printf()

#define BUFSIZE        16

void* malloc(size_t size) {
  return (void*) 0;
 }

int main() {
  int i;
  unsigned char* buf;
  unsigned char* ptr;

  ptr = buf = malloc(BUFSIZE);
  printf("buf = %p\n", buf);

  for (i = 0; i < BUFSIZE; i++)
    *(ptr++) = i % 256;

  ptr = buf;
  for (i = 0; i < BUFSIZE; i++)
    printf("%02X\n", *(ptr++));

  return 0;
 }

ポインタ変数 buf にはゼロが設定されますから、ゼロ番地からの初期化が行われることになります。

$ gcc -Wall -o malloc_fail malloc_fail.c 
$ ./malloc_fail ; echo $?
buf = (nil)
Segmentation fault
139

最初のループで Segmentation fault が発生し、プロセスは異常終了してしまいました。failmalloc のページで、python, perl, ruby が軒並み Segmentation fault を起こしていたのは、malloc_fail と同じように、戻り値の確認を怠り、ゼロ番地に対するアクセスを許してしまったことが原因です。

メモリ保護機構

なぜ Segmentation fault が発生するのかについては、軽く一冊の本が書けてしまいますので、一般的には馴染みの薄いメモリ保護機構を簡単に実体験してみることにしましょう。

まず、プログラム(正確にはプロセス)中におけるデータや実行コードのアドレスを address.c により観察してみます。

#include <stdio.h>     // printf()
#include <unistd.h>    // getpid()

int data = 123;
const int rodata = 456;

int main(int argc, char** argv) {
  int block;

  printf("my pid  = %d\n", getpid());
  printf("&data   = %p\n", &data);
  printf("&rodata = %p\n", &rodata);
  printf("main    = %p\n", main);
  printf("&argc   = %p\n", &argc);
  printf("&argv   = %p\n", &argv);
  printf("&block  = %p\n", &block);

  while (1) ;

  return 0;
 }

address.c 中には、実行コードはもとより、.text, .data, .rodata の3セクション、およびスタック領域に存在するデータが定義されています。先頭で getpid を用いて自身のプロセスID(PID)を表示し、各種変数や関数の開始アドレスを表示した後、最後は while loop でブロックします。

$ gcc -Wall -o address address.c 
$ ./address 
my pid  = 9349
&data   = 0x804970c
&rodata = 0x8048598
main    = 0x80483b4
&argc   = 0xbfee0b70
&argv   = 0xbfee0b74
&block  = 0xbfee0b64

block 変数のアドレスを表示したところで、ハングアップしますので、コントロールZを入力し、address プロセスを一旦バックグラウンドに移行させます。

[1]+  Stopped                 ./address
$

実行例のPIDは 9349 でした。Linux の場合、/proc ディレクトリ中には、各プロセスのPIDをファイル名としたディレクトリが存在し、その内部には様々なプロセス情報が格納されています。

$ ls -F /proc
1/     2534/  3958/  5/     9138/      diskstats    kcore       self@
1044/  3/     3960/  6/     9349/      dma          key-users   slabinfo
1045/  3288/  3962/  7/     9383/      driver/      kmsg        stat
12/    3833/  3963/  710/   94/        execdomains  loadavg     swaps
121/   3839/  3965/  8/     95/        fb           locks       sys/
122/   3882/  3966/  8160/  acpi/      filesystems  meminfo     sysrq-trigger
123/   3888/  3979/  8163/  asound/    fs/          misc        sysvipc/
124/   3892/  3980/  8164/  buddyinfo  ide/         modules     tty/
125/   3901/  3981/  8240/  bus/       interrupts   mounts@     uptime
1398/  3926/  3982/  8243/  cmdline    iomem        mtrr        version
1646/  3932/  3983/  8244/  cpuinfo    ioports      net/        vmstat
1804/  3939/  3984/  9/     crypto     irq/         partitions
2/     3945/  4/     9132/  devices    kallsyms     scsi/
$ ls -F /proc/9349
attr/  cmdline  environ  fd/   mem     oom_adj    root@    stat   status  wchan
auxv   cwd@     exe@     maps  mounts  oom_score  seccomp  statm  task/

この中の maps ファイルを参照すると、テキスト形式でプロセスの mmap (Memory MAP)状況を得ることができます。

$ cat /proc/9349/maps
08048000-08049000 r-xp 00000000 03:04 713410     /usr/src/work/evil_malloc/address
08049000-0804a000 rw-p 00000000 03:04 713410     /usr/src/work/evil_malloc/address
b7e8b000-b7e8c000 rw-p b7e8b000 00:00 0 
b7e8c000-b7fba000 r-xp 00000000 03:04 1609       /lib/tls/libc-2.3.6.so
b7fba000-b7fbf000 r--p 0012e000 03:04 1609       /lib/tls/libc-2.3.6.so
b7fbf000-b7fc2000 rw-p 00133000 03:04 1609       /lib/tls/libc-2.3.6.so
b7fc2000-b7fc4000 rw-p b7fc2000 00:00 0 
b7fca000-b7fcd000 rw-p b7fca000 00:00 0 
b7fcd000-b7fe2000 r-xp 00000000 03:04 801        /lib/ld-2.3.6.so
b7fe2000-b7fe4000 rw-p 00015000 03:04 801        /lib/ld-2.3.6.so
bfecd000-bfee2000 rw-p bfecd000 00:00 0          [stack]
ffffe000-fffff000 ---p 00000000 00:00 0          [vdso]

address プロセスに許可されているメモリー領域の一覧ですが、先頭の2行に注目してください。data 変数の格納アドレスは 0x804970c 番地でしたから、同変数は2番目の 08049000-0804a000 領域に存在することが分かります。

maps 情報中の2桁目のフィールドはメモリーパーミッションのフラグを表していますが、08049000-0804a000 領域は rw-p に設定されていますので、Read/Write が許可されていることが分かります。

一方 const 宣言を受けている rodata 変数の格納アドレスは 0x8048598 番地でしたから、この変数は1番目の 08048000-08049000 領域に存在します。同領域のメモリーパーミッションは r-xp ですから、Read/eXecute が許可されていることになります。

maps 情報中に登録されていないメモリー領域に対するアクセス、登録されていてもメモリーパーミッションフラグに反するアクセス(Write フラグが OFF の領域への書き込みなど)は、直ちに Segmentation fault を引き起こし、プロセスはカーネルから強制終了させられます。

ゼロ番地へのアクセスが Segmentation fault を引き起こした理由は、これでお分かり頂けるかと思います。なお、Linux 上のプロセスの開始アドレスは maps 情報からも明らかな通り、0x08048000 番地という決まりになっています。ゼロ番地でない理由のひとつとして、頻度の高いメモリ先頭領域での保護違反を検出する目的があります。

最後に、バックグラウンドに移行させられている address プロセスを fg (ForeGround)コマンドで復帰させ、コントロールCで終了させておきましょう。

$ jobs
[1]+  Stopped                 ./address
$ fg
./address 

$ 

アドレスによるデータ操作とメモリーパーミッション

これだけでは、今ひとつメモリ保護機構の実感が湧きませんから、アドレスを通じて間接的にデータを操作する cast_read.c で理解を深めてみましょう。

#include <stdio.h>     // printf()

int data = 12345678;
const int rodata = 87654321;

int main() {

  printf("&data   = %p\n", &data);
  printf("&rodata = %p\n", &rodata);
  printf("data    = %d\n", *((int*) 0x0804967C));
  printf("rodata  = %d\n", *((int*) 0x08048538));

  return 0;
 }

内部で data, rodata 変数を定義し、それぞれの格納アドレスを表示した後、これらのアドレスを用いて間接的にふたつの変数内容にアクセスしています。アドレスの値は、環境によってことなりますので、出力された値に書き換えてください。

cast_read.c 中では、キャスト演算子が非常に重要な役割を演じています。C言語ではポインタ変数の使いこなしが大事だと言われますが、実践においては、キャスト演算子の理解と使いこなしが、より重要と言えるでしょう。

$ gcc -Wall -o cast_read cast_read.c 
$ ./cast_read 
&data   = 0x804967c
&rodata = 0x8048538
data    = 12345678
rodata  = 87654321

実行例では、data 変数が 0x804967c 番地、rodata 変数が 0x8048538 番地に格納されていました。ソースリスト中で、それぞれのアドレスへアクセスすると、変数を介することなく、その内容を参照することが出来ています。このように、変数名はアドレスの別名に過ぎないのです。

cast_read.c の知識を応用して、ゼロ番地への Read 参照を行ってみましょう(cast_read_zero.c)。

#include <stdio.h>     // printf()

int main() {

  printf("%d\n", *((int*) 0));
  return 0;
 }

もはや、説明の必要もありません。

$ gcc -Wall -o cast_read_zero cast_read_zero.c 
$ ./cast_read_zero 
Segmentation fault

意図的に Segmentation fault を再現できました。ゼロ番地だけでなく、maps に登録されていないメモリ領域に対する参照を行い、Segmentation fault を発生させてみてください。

実験ついでに、今度はアドレスを通して data 変数の内容を書き換える、cast_write_data.c に挑戦してみましょう。

#include <stdio.h>     // printf()

int data = 12345678;
const int rodata = 87654321;

int main() {

  printf("&data   = %p\n", &data);
  printf("&rodata = %p\n", &rodata);

  printf("data    = %d\n", *((int*) 0x0804969c));
  printf("rodata  = %d\n", *((int*) 0x08048558));

  *((int*) 0x0804969C) = 11223344;
  printf("data    = %d\n", *((int*) 0x0804969c));

  return 0;
 }

cast_read_data.c とほとんど同じですが、*((int*) 0x0804969c) = 11223344; により、data 変数を間接的に書き換えています。

$ gcc -Wall -o cast_write_data cast_write_data.c 
$ ./cast_write_data 
&data   = 0x804969c
&rodata = 0x8048558
data    = 12345678
rodata  = 87654321
data    = 11223344

意図通り、data 変数が 12345678 から 11223344 に書き換えられています。rodata 変数や、Read only 領域のアドレス(0x8048000)などへの書き込みを行い、Segmentation fault を確認してみてください。

つづく

以上で、ようやく基礎部分の終了です。長くなりましたので、楽しい libc-hijack はまた明日。


2006-07-11 (Tue)

[Hard][Linux] OLIMEX CS-E9301

赤い稲妻

OLIMEX CS-E9301 組み込み野郎の皆様、今日は極上のネタが入っております。 OLIMEX社Price List 中で、本日遂に CS-E9301 ボードが登場致しました。私は、CS-E9301 の発売がアナウンスされてからというもの、毎日毎日チェックを入れていたのですが、どれほどこの日を待ちわびたことでしょうか。

右の写真は6月下旬に公開されましたが、OLIMEX の場合は基板写真の公開後、Price List に値段が登場して、初めて注文可能になるという "しきたり" があるのです。

さて、この CS-E9301 の正体ですが、CIRRUS LOGIC社 ARM9 EP93 family の最下位にあたる EP9301 を搭載した、組み込みボードです。ARM9 は MMU を搭載していますから、MMU 非搭載の ARM7TDMI に使われる uClinux ではなく、通常の Linux カーネル(もちろん*BSDも)を搭載可能です。主要スペックは次の通り。

  • Size 110x90mm
  • CPU 166MHz ARM920T
  • FLash 16MB
  • SDRAM 32MB
  • IFs 2xRS-232C, 2xUSB 2.0, etc.
  • SD/MMC card
  • Ethernet CS8952
  • JTAG
  • Linux/Windows CE ready

ご覧のように、組み込み野郎の相手として、申し分ありません。気になるお値段は、USD $159.95 とこちらも、わんだほー。"ポチッとボタン" が無いことを神に感謝しましょう。

"Linuxから目覚める僕らのゲームボーイ" では ARM7TDMI を攻略した訳ですが、ARM7 では応用面が限られるため、私は ARM9 の時代が到来するのをそっと待ち続けていたのです。その突破口は恐らく OLIMEX が開いてくれるだろうと思っていたのですが、結果は予想通りでした。

CS-E9301 は、ARM9 のリファレンスボード(価格・機能の両面において)として、世の中に迎え入れられることでしょう。

CS-E9315、夢枕に立つ

しかしであります。私達は、こんなことで喜んでいる場合ではありません。実は、OLIMEX は既に次なる刺客を用意しているのです。それは、今年の10月発売予定の CS-9315 ボード

こちらは、EP93 family の 最上位版にあたる EP9315 を搭載した、モンスター組み込みボードです。24bitカラーの 480x272 pixels TFT液晶を搭載した上に、なんと VGA ready、SDRAM は余裕たっぷりの 64MB。EP9315 は IDE インターフェース(2ch)を内蔵していますから、CS-9315 にも IDE-HDD を接続できるかもしれません。これは、まさしく「21世紀のマイコン(MY COMputer)」であります。

一体、どれ位の値段設定になるのでしょうか。EP9315 が夢枕に立つ日が続きそうです・・。

OLIMEX が熱い

OLIMEX MSP430-169LCD 最近の OLIMEX は、この他にも数多くの新製品を発表しており、組み込み街道をばく進中です。例えば、右の MSP430-169LCD は、60KB Flash を持つ MSP430F169 に SD/MMC カードと携帯用 LCD (NOKIA 84x48 pixels)を組み合わせたボードです。マイコン野郎がこの写真を見たら "魂もって行かれてしもうた・・" になることでしょう。

これまで、マイコンに SD カード&携帯用LCD という組み合わせは、ありそうでないものでしたが、$49.95 で製品化してしまう OLIMEX はさすがです。私の推測では、この製品のアイデアの源流となったであろう MSP430 を使った面白い授業プロジェクト(米国の大学)が別にあるのですが、これについてはまた今度。

PIC, AVR, MSP430, ARM, and MAXQ

OLIMEX は元々 PCB 基板の小ロット受注メーカーとして有名でしたが、徐々にマイコン開発ボードの品数を増やしていき、現在では世界一の「マイコン野郎ご用達ショップ」に成長しています。

Development boards のページでは、各種マイコンの長所・短所が的確にまとめられており、彼らが各マイコンの特性を知り尽くしていることが分かります。

さて、PIC, AVR, ARM, MSP430 は多くの方がご存じだと思いますが、"MAXQ" とは一体何でしょうか?これは、日本ではまだほとんど知られていない、Maxim-Dallas社 の RISC マイコン(発表は2004年であり、現時点で最も新しいMCU)です。MAXQ の内部アーキテクチャというのが、これまたとんでもなくエキサイティングな代物なのですが、詳細はまたの機会に。


2006-07-01 (Sat)

[Thoughts][Hard] Micro-Master: a basic computer training kit

マイコン世代の強み

現在、プログラマーやエンジニアとして指導的立場にある方々の多くは、マイコンの洗礼を受けて育っていることでしょう。かってパソコン(Personal computer)が、マイコン(My computer)と呼ばれていた頃、マイコン少年達は、I/OやASCIIに掲載されたダンプリスト片手に、ゲームの機械語入力に興じていました。

今となっては信じがたいことですが、当時はショップの中で、MZ-80 や PC-8001 相手に、10KBを超える16進数のマシン語を延々と数時間も打ち込み続けるマイコン少年達が、列をなしていたのです。店員の方々も現在とは違い、随分と寛容であったと思います。

マシン語入力のために活用された機能が、今は亡きモニターコマンドです。当時のマイコンは電源ONと共に、ROM-BASIC が起動していましたが、MON コマンドでモニターモードに移行することで、メモリー内容の参照修正・機械語の実行・カセットテープへのLOAD/SAVEなどを行うことができたのです。

BASIC から入門したマイコン小僧達は、ゲームのコード入力を通じて、ある者は機械語の世界に目覚め、ハードウェア制御の妙味に取り憑かれていきます。かの Linus 氏もそうであったように、多くのプログラマーが8ビットマイコンを通じて、システムプログラミングの基礎を身につけたことでしょう。

しかしながら、現代のパソコンは "進化の途中" で ROM-BASIC とモニターコマンドを失ってしまいました。コンピュータを学ぼうとする若い人達にとって、64ビット全盛の現在は、さぞかし "学びづらい" 時代になっているのではないかと思います。

一方で、8ビット全盛時代を経験することができた世代は、恵まれていたと同時に、ある意味 "ずるい" と言えるかもしれません。自分達は、しっかりと基礎鍛錬を終えているにもかかわらず、後輩に対しては応用を教えるばかりで、基本の指導にはあたっていないからです。

マイコンよ、もう一度

私は自身の経験から、一見遠回りに見えようとも、最初に基本を学ぶことが何よりも大切であると信じていますから、今の若い人達に必要なものは最新の64ビットパソコンではなく、8ビットもしくは16ビットのマイコンではないかと常々考えてきました。

しかし、世の中は最新の技術やハードウェアの学習を勧めるばかりで、"温故知新(ふるきをたずねて新しきを知る)" を叫ぶ人は皆無に近い状況です。世の趨勢が正しいのか、それとも自分の考えが歪んでいるのか。自分の信念を疑ってしまうことも多いわけですが、時として、出版界やネット上で志を同じくする人々に出会うことがあります。

Elenco Electronics

Micro-Master ある日、8ビットマイコンのトレーニングキットを探している時に出会った、米国の Elenco という会社はそのひとつ。Elenco は学校教育用の電子教材や工具を扱うメーカーであり、自社開発キットも豊富に取り揃えています。充実した同社のカタログを眺めていると、時間が立つのを忘れてしまいそうです。

同社のキットの中で、一際目を引いたものが、Micro-Master。このキットのタイトルは "A Basic Computer Training Kit to Leartn the Fundamentals of Computers" となっており、中身は往年の i8085 CPU を使ったマイコントレーニングボードです。

トランク風のプラスチックケースの中には、基板とパーツが丁寧に梱包されており、ユーザは手作業でトレーニングボードを仕上げることになります。

Micro-Master the ultimate training board

Micro-Master マニュアル表紙 ボード上には、アドレス・データバスを操作するスイッチと、バス制御スイッチ、モニターROM を操作するための16進テンキーが用意されており、気分はミニコンのオペレーター。ちなみに、NEC TK-80 にアドレス・データバスの操作スイッチはありませんでした。後で述べるように、Micro-Master は、より深いハードウェアレベルでメモリ操作を学ぶことができるのです。

気になるお値段ですが、私が購入した時点で定価はUSD 149.00でした。現在は、166.50ドルに値上がりしているようですが、それでもこの手のトレーニングボードとしては、破格の安さだと思います。Z-80などを使った8ビットCPUトレーニングボードは、日本も含め数多く販売されていますが、"自分の手を動かし内部を理解できる" ボードは、私が調べた限り、この Micro-Master しか見あたりません。

8ビットマイコンに限らず、体験することと、理解することは、全く違う次元の話です。世の中のトレーニングボードのほとんどは、ユーザに体験を積ませるだけのものであり、"理解の高み" に導いてくれるものではありません。

この点において、Micro-Master は歴史に残る名トレーニングボードと言えます。まず私が驚かされたのは、110ページを超えるマニュアルでした。通常、電子工作キットについてくるマニュアルというのは、ペラペラの数ページと相場が決まっていますが、Elenco の製品に添付されているものは、立派な教科書と言って良い内容になっています。

Simplified architecture

Micro-Master System Block ボードの製作に関しても、通常はハンダづけの解説が機械的に進むものですが、Micro-Master の場合は、まず最初に電源回路とアドレス・データバスのスイッチ組付けを行い、次に RAM 周りを実装。マニュアル操作による RAM への READ/WRITE を学びます。仕組みを理解しながら進むことが、徹底されているのです。

右のブロック図(拡大可能)に示す通り、Micro-Master は CPU に 8085, RAM に I/O ポートを備えた 8156 (256B RAM), ROM に 2816 (2KB)を採用しており、マイコンシステムとしては最小限の構成となっています。PDP-11 もそうでしたが、コンピュータの基本構造はかくも単純なものなのです。

最初に登場する 8156 を使った RAM の学習では、アドレスバス9ビット、データバス8ビット、コントロールバスとして ALE (Address Latch Enable), RD (ReaD from memory), WR (WRite to memory), IO/M (Input Output/Memory) の計4ビットを 8085 CPU に代わり、ユーザがスイッチ操作でメモリ内容を制御します。

このように段階を踏みながら、Micro-Master の理解は進みます。現代でマイコンと言えば、PIC, AVR などの Micro Controller Unit (MCU)を指しますが、残念なことに MCU もまた、進化の過程でコントロールバス・アドレスバス・データバスを失ってしまいました。

3つのバスはコンピュータアーキテクチャを理解する上で、避けては通れない重要な概念ですが、もはや紙の上でしか読んだことのない人の方が多いのではないでしょうか。Micro-Master のような優れた教材が、今後も末永く生産されることを願ってやみません。

Tequipment.NET (追伸その1)

現時点で、国内に Micro-Master を取り扱っているお店はないようです。是非とも入手したい方は、Tequipment.NET を訪ねると良いでしょう。

Micro-Master の商品コードは MM-8000K で、こちらから注文可能です。定価は改定前のままであり、売価は現時点でUSD 127.46。危険です。"ポチっと病" に注意しましょう。

6502マイコン・ボード製作記 (追伸その2)

トランジスタ技術2006年5月号から、桑野雅彦氏による "6502マイコン・ボード製作記" という連載記事が始まっています。このタイトルを目にした時、私は思わず我が目を疑いましたが、夢ではなかったようです。連載は続いています。

6502 と言えば、ファミコンや Apple II に搭載された CPU として有名ですが、現在でも Western Design Center (WDC)社が市販していることに、桑野氏が目を付けられたようです。

残念ながら、誌面に限りがあるため、全くの初心者がこの内容を理解することは難しいでしょう。Micro-Master の次の教材としては、良いかもしれません。

それにしても、この企画を通したトランジスタ技術編集部の方は凄い。7月号の Reader's forum には、同連載へのエールがふたつ掲載されていますが、気になるのはどちらも往年のマイコン世代ということ。若い世代の人達にこそ、この記事の良さと内容を分かってもらいたいものです。

昨今は組み込み技術が花盛りのようですが、基本技を身につける前に応用技へ挑戦することは、大変な危険を伴います。まる1ヵ月、8ビットもしくは16ビットCPUの基本を学ぶことは、その後の10年を大きく変えてくれることでしょう。

Flite MPF-1B Microprocessor Training System (追伸その3)

8ビットマイコンの話題ついでにもうひとつ。冒頭で、マイコン少年の媒体がカセットテープであったことに触れましたが、もう一度あの "ピーガー" を体験してみたい方も多いことでしょう。

カセットインターフェースを持ったトレーニングボードは、もはや絶滅したかと思いきや、それがあるんです。英国の Flite 社 が販売している MPF-1B は、まさに往年のマイコン少年少女の夢を再現してくれる Z-80 ボード。

"この" 世界では、結構有名なボードです。私自身は購入していませんが、噂によると教材がかなり充実しているようです(ただし高価)。本体はアセンブリ済みであり、Micro-Master のように自分自身の手で理解しながら組み上げるタイプではありません。

Flite社は Z-80 の他にも、68000, 8086, 8032, 68EC020 など多種類のトレーニングボードを開発・販売しています。68K のトレーニングボード(FLT-68K)は、世界的に見ても大変珍しいものでしょう。


2006-06-13 (Tue)

[Thoughts][Books] 知っておきたいミニコン/パソコン変遷史

トラ技コンピュータ 1995年7月号

トラ技コンピュータ 1995年7月号 いつも海外の優れた出版物を紹介するというのも、一日本人としては "癪に障る" 話なので、今日は PDP-11 に関連する超一級品の国産資料 "知っておきたいミニコン/パソコン変遷史" をご紹介しておきましょう。

この資料は、1995年7月に出版された CQ出版 トラ技コンピュータ の特集記事です。トランジスタ技術は、トラ技コンピュータの他にも、トランジスタ技術SPECIALや TRY!PC など、極めて資料性の高い別冊シリーズを出版していますが、残念なことにそのほとんどが絶版となっています。

"知っておきたいミニコン/パソコン変遷史" は、数あるトラ技コンピュータ・バックナンバーの中でも、ミニコンピュータを対象とした異色の存在であり、発刊にあたっては編集部内でも企画段階から相当の論議があったのではないかと思われます。

PDPからIBM PC, PC98へと受け継がれる技術を理解しよう

私は、この企画を実現させた編集担当者の "志" をそのサブタイトルから感じ取りました。曰く、「PDPからIBM PC, PC98へと受け継がれる技術を理解しよう」。

IBM PC を頂点とするパーソナルコンピュータの系譜は、すべて DEC 社 PDP シリーズの血筋を引いており、その歴史と技術系譜を明らかにする事が本特集のテーマです。1995年当時は NEC 98MATE X シリーズが販売されていた頃であり、特集の章立ては

  • 第1章 パソコンの礎となったDEC社のコンピュータの変遷
  • 第2章 世界標準パソコン IBM PC と MS-DOS の変遷
  • 第3章 日本の国民機 PC98 シリーズの変遷

となっています("国民機" という言葉に、思わず目頭を押さえてしまう方も多いのでは)。章毎に、ことなる著者が執筆を担当していますが、圧巻は何と言っても、勝男 厚氏による第1章です。

Digital Equipment Corporation 社の名前の由来や(同社は起業当時 digital module と呼ばれる単純なフリップフロップや論理回路のボードを設計販売していた)、Programmed Data Processor (PDP) シリーズ誕生の歴史的経緯、PDP-8, PDP-11 の開発秘話とそのアーキテクチャなど、世界中の PDP マニアが本特集の存在を知れば、本気で日本語を勉強するのではないかと思われるほどの内容になっています。掲載されている写真資料も、ネット上の PDP サイトではお目にかかれないような資料価値が高いものばかりです。

この特集記事を堪能するためには、機械語とアーキテクチャに通じている必要がありますが、1980年代カセットテープを握りしめ、マイコンと共に熱き青春の日々を過ごした世代であれば、3章を通じてしばし恍惚感に浸れることでしょう。

ミニコンものがたり

残念ながら、トラ技コンピュータ 1995年7月号も絶版になって久しく、入手は極めて難しい状況にあります。ところが・・

日本HP社の粋な計らいで、同社のサイト上において当時の原稿が "ミニコンものがたり" として、ほぼ完全に再現されているのです(イラストや一部の原稿は割愛)。日本DEC は、1998年コンパックコンピュータに併合され、コンパックコンピュータは2002年現在の日本HPに併合されています。

オリジナルの文献は Google で検索しても、現在2件がヒットするだけですから、この貴重なオリジナル資料を目にすることができた幸運な人達は、わずかでしょう。本特集を一般公開してくださった、日本HP社、CQ出版社、そして著者である勝男 厚氏に感謝いたします。なお、どうしても全編が読みたい方は図書館で本書を探すか、CQ出版社のコピーサービスを活用しましょう。

本特集をはじめとして、1970-1990年代に発行された日本の技術書には、志に溢れた良質な記事や書籍が現在よりも遙かに多かったような気がします。"評価されるべきもの" にも書いた通り、過去に埋もれている日本の偉大な作品群を今一度見直すべきではないでしょうか。

PDP-8/E Simulator

PDP-8/E Simulator 勝男氏が本特集で紹介している、1965年に発表されたDEC社 最初のベストセラー、PDP-8 は1ワードあたり12ビットで構成され、そのうちOPcode (OPeration CODE)は3ビット。すなわち、命令はわずか8種類でした。また、4Kワード以上のアドレス空間を制御するために、セグメント方式が採用されています。悪名高き 8086 セグメント機構のご先祖様は、なんと PDP-8 だった訳です。

この超シンプルなミニコンを Mac OS X 上で再現するエミュレータ、"PDP-8/E Simulator" が Bernhard Baehr氏の手により公開されています。惚れ惚れするほど美しい画面デザインであり、ロードしたプログラムが LED 点滅と共に動く様子は、まさにエクスタシー(OS X ユーザで良かった)。ちなみに、DECpack イメージとして OS/8 System Disk も添付されています。

同氏のサイトは資料も充実していますので、マニュアル片手に40年前のハッカー気分を味わうのも良いでしょう。


2006-06-10 (Sat)

[Thoughts][Books] PC System Architecture Series by MindShare

今回は PDP-11 続編の予定だったのですが、原稿準備の都合上、急遽 MindShare 社にご登場願うことにしました。

Complicated PC architecure

Protected mode software architecture 30年前のミニコンピュータシステム、PDP-11 のアーキテクチャは、先日紹介した "MINICOMPUTER SYSTEMS" をはじめとして、DEC 社のリファレンスマニュアルなど数冊を読み込めば、おおよそ把握することができます。一個人が理解できる分量としては、最適なものでしょう。

ところが、現代の PC アーキテクチャに目を向けると、プロセッサ本体はもちろんのこと、バス・アーキテクチャや周辺デバイスも高度に進化しているため、システム全体を記述するためには軽く10冊を超える分量が必要となってしまいます。

これだけの規模になると、著者側も到底1人では対応できませんし、内容も極めて高度になるため、執筆能力を兼ね備えた優秀なエンジニア複数からなるチームを編成しなければなりません。「そのような Dream team が、この世に存在するのだろうか?」と夢みつつ、日々が過ぎ去ることしばし。

プロテクトモードに関する下調べをしていたある日、資料中に "Protected mode software architecture" という参考文献を見つけました。調べてみると、この本の出版社は Addison Wesley、執筆は MindShare の Tom Shanley 氏。これが私と MindShare 社の最初の出会いです。

Building-block approach

About x86 プロテクトモードプログラミングに関する資料が、世界的に不足している中で、本書は貴重な一冊と言えますが、中身は期待していたほどではありませんでした。解説書というよりは、データシート的な内容です。

しかし、内容以上に私を驚かせたのは、その序文でした。右図を拡大してよくご覧頂きたいのですが、MindShare 社は PC アーキテクチャを解説するため Building-block 方式を採用しており、その基礎に "ISA System Architecure" を置いているというのです。曰く、"The ISA System Architecture is the core book upon which the others build."。ISA とは Industrial Standard Architecture bus の略であり、1981年に発表された IBM-PC の8ビットバスを起源にしたバス規格です。

私は、21世紀のこの時代に、今なお ISA バスの重要性を説く書籍があろうとは、夢にさえ思っていませんでしたから、この序文を読んだ瞬間に相当のショックを受けました。

"全ての応用は基本の延長線上にある" と頭の中では考えていたものの、書籍という形で既に実現している会社が存在したことに、悔しさ半分、嬉しさ半分。直ちに、同社のサイトに飛びました。

We write the books!

驚かされたことに、MindShare 社の本業は Technical writing ではありませんでした。彼らは、世界中のエンジニアを対象に、PC アーキテクチャの超専門教育コースを提供する、インストラクター集団だったのです。執筆書籍は、ISA のような古典から、最新の技術まで多岐にわたっており、ここ数年の間にもタイトル数は増え続けています。教育コースは、書籍よりもさらに充実しており、AMD, Storage, Linux/Windows, 果ては Surface Mount Technology にまで及びます。

書籍の中身がデータシート的であった理由は、恐らくこれらがコースの講義中で補助テキストとして使われるためであり、"おいしいポイント" は講師から生徒へ、直接伝授されるのでしょう。

「この人達の頭の中はどうなっているのだろう?」と訝しく思いながらサイトをチェックしていると、ふと Home の page title が視界に。"MindShare - We Write the Books!" というメッセージを読んだ時、私はすべてを了解できたような気がしました(以前、このメッセージは "Yes, we write the books!" だったように記憶しています)。

彼らは、世界でも数少ない "書ける" エンジニアであり、教育者であったのです。基本を積み重ねることの重要性を書籍と教育で証明してきた彼らの姿に、私は心底尊敬の念を覚えると同時に、自分の考えが決して間違ってはいないことを確信することができました。さらには、具体的な方法論まで授かることができたのです。

それからというもの、日々私なりの Building-blocks を思い描いてきました。MindShare 社のような最新技術へのアプローチは難しいでしょうが、もっと下層のレベルからなら、私にも可能かもしれません。

近日中に、基礎ブロックのひとつをご紹介できると思います。


2006-06-05 (Mon)

[Thoughts] PDP-11 に学び、救われる

Real programmers and PDP-11

Ken and Dennis in front of a PDP-11 記念すべき再開第一回は、一片のモノクロ写真からはじめましょう。右の写真は、往年の名機 PDP-11 の前で端末に向かう、若き日の Ritchie 博士と Thompson 氏の姿をおさめた貴重なショットです(Yet Another PDP-11 Page より)。

"Computer science 界の母" ともいえる PDP-11 は、ふたりの手により、C言語と UNIX を産み出した訳ですが、現代の若い人達がこの写真を目にすれば「これほど巨大なコンピュータを手足のように操った彼らは、天才に違いない!」と思うことでしょう。

確かに、このふたりは類い希なる資質を持ち合わせていましたが、PDP-11 自身は現代のパソコンに比べれば、遙かに単純な仕組みから成り立っていたのです。PDP-11 の全盛期から30年以上を経た今、残念ながら、その設計哲学と優れた構造は忘れ去られつつありますが、幸い PDP-11 のありし日の姿を封じ込めた一冊の名著が残されています。

MINICOMPUTER SYSTEMS

MINICOMPUTER SYSTEMSその本の名は、MINICOMPUTER SYSTEMS。副題として ORGANIZATION, PROGRAMMING, AND APPLICATIONS (PDP-11) と名付けられ、1970年代のハッカーやエンジニアから愛読された往年のベストセラーです。共著者の1人である Eckhouse 元教授は、執筆当時 Digital Equipment Corporation (DEC)社に勤務しており、PDP-11 アーキテクチャに関する詳細な解説を行っています。その内容は他書の追随を許さず、行間からは DEC エンジニアの同製品に対する誇りと惜しみない愛情が伝わってくるようです。

ちなみに、この本は一昨年米国の Amazon.com から取り寄せた古本ですが、値段は確か1ドル未満でした。ベストセラーだけあり、中古市場には未だに相当数の同書が流れていることが低価格の原因のようですが、これだけの歴史的名著が "バナナの叩き売り状態" とは、得した一方で何やら物悲しい、複雑な感慨を持ったものです。

なお、従来米国の中古書籍販売店のほとんどは海外発送不可であったため、私は別途転送業者を経由して古本やマニュアルを入手していたのですが、最近は日本のアマゾンからも手軽に注文できるようになりました。ただし到着まで2週間前後はかかりますし、米国 amazon.com に比較すると amazon.co.jp を経由した場合、高値が付けられた古書のみ選択的にリストアップされている点に注意する必要があります。例えば、本日の時点で MINICOMPUTER SYSTEMS は米国では $0.73 スタートの古本22冊が登録されていますが、日本では 5000円以上の値段がついた2冊のみです。転送料や配送料を考慮すると、結局同じぐらいの値段になるのですが、米国 amazon.com が誇る豊富な古書群は、何者にも代え難い魅力と言えるでしょう。

PDP-11 System Block

PDP-11 System Block 同書より引用した右の図は、PDP-11 システム全体像をブロック図で概念化したものです。CPU 内部は ALU とステータスレジスター, 8個の汎用レジスターから構成され、UNIBUS を介してメモリ, TTY, ラインプリンター, ディスクなどのI/O装置と連結しています。巨大なラック群から構成されるミニコンピュータの内部とは思えないほどの単純さです。

俄には信じがたいのですが、次のブートストラップコードを見れば、当時の PDP-11 がいかにシンプルであったかが、お分かり頂けるのではないかと思います。

Bootstrap code

14 words bootstrap code

このショートプログラムはわずか 14 words から構成される PDP-11 用のブートストラップコードです。オペレータはこの 14 words をコンソールパネルの Deposit switches から打ち込み、目的のプログラムコードがパンチされた紙テープを本体 RAM にロードしていました。

機械語の知識がある方であれば、容易に解読できる通り、このプログラムは紙テープリーダのステータスレジスターの最下位ビットをONにし、リーダーを enable にした後、Data ready を確認しては、読み込まれたデータをメモリーに転送するという、単純作業を繰り返しています。

もちろん、記述言語は最も原始的なアセンブリ言語であり、オペレータはいわゆる "Hand assemble" により、即興で機械語をバチバチと入力していたのです。

PDP-11 の解説書は、本書以外にも数多く出版されていますが、MINICOMPUTER SYSTEMS が圧倒的に優れている点は、"Input/Output Programming" と題する第7章に、この Bootstrap をはじめとする、キーボード読み込みや、DECtape 制御, ディスク読み込み, 割り込み処理など、PDP-11 を構成する I/O 装置群の制御コード例が豊富に掲載されていることにあります。しかも、それらのコードリストは、すべて1ページ以下に収まっているのです。

私は、これまで PDP-11 に関する書籍や資料を出来る限り入手してきましたが、この中で MINICOMPUTER SYSTEMS は文句なしの最高傑作、その右に出るものなしと断言できます。

PDP-11/34 processor handbook

pdp11/34 processor PDP-11 プロセッサのレジスタ構成、アドレッシングモード、命令コードなどは、DEC 社のプロセッサマニュアルに記載されていますが、これらの多くは世界中の熱烈な PDP-11 ファンが管理している各地のサイトからダウンロードすることができます(右は amazon.com から入手した古本、値札が付いています)。

ところが、MINICOMPUTER SYSTEMS を熟読して分かったのですが、純正のプロセッサマニュアルにはユーザーの誤解を招くような記載や、不適切な命名が使われている点がいくつかあるのです。恐らく Eckhouse 氏はこの問題に気づいていたようで、プロセッサマニュアル中で誤解を招きやすい部分や分かりづらい点について、補足を加えてくれています。このおかげで、読者は本書を通じて、極めて明快に PDP-11 アーキテクチャを理解することができるのです。

Google pages の限界

昨今は、検索サービスの台頭からか、ネット上の情報さえあれば事足りるという考えが支配的になっているようですが、大変残念なことだと私は思います。PDP-11 アーキテクチャひとつをとっても、Google 上に登録された全てのページをかき集めたところで、一冊のMINICOMPUTER SYSTEMS には遠く及ばないのです。優れた書籍は30年の時を経ても、最新のネットワークデータベースを軽く打ち負かすだけのパワーを秘めているという事実は、一種痛快でもあり、また自分自身、襟を正されるような思いがします。

アーキテクチャを理解する

一昔前に "Lions' commentary on UNIX 6th Edition" が日本でも和訳され話題になりましたが、残念ながらカーネルソースを読むだけでは C言語と UNIX を真に理解することはできないでしょう。システムプログラミングはハードウェアとソフトウェアの両輪の上に成り立つものですから、アーキテクチャの理解は必須です。

しかしながら、現代は UNIX ハッカーが謳歌した時代とはことなり、すべての環境が肥大化の一途を辿った結果、カーネルはもとよりコンピュータアーキテクチャを一個人が理解することは、事実上不可能な事態に陥っています。私達がハードウェアとソフトウェアの "両輪" を理解することは、所詮かなわぬ夢なのでしょうか?

この1年半の間、私は絶えずこの疑問を自分に投げかけてきました。途中、何度も絶望しかかったこともありましたが、MINICOMPUTER SYSTEMS のような優れた書籍や設計哲学に裏付けされたハードウェアに出会うことで、ようやく救われたように思います。

次回は、こうして私が心底惚れ込んだ PDP-11 の "継承者" を紹介する予定です。


2006-06-04 (Sun)

[Misc] Wataru's memo 再開

突然ですが、昨年1月以来、約1年半ぶりにWataru's memoを再開致します。長期間にわたる休筆にもかかわらず、毎日多くの方々がこのページを訪れてくださっていたようです。この場を借りて、お礼申し上げます。

今回の再開を機会に、tDiaryのカテゴリー機能を活用して、過去のメモを全てcategorizeしてみました。現時点で10個に満たないカテゴリーですが、全体の見通しが随分良くなったように思います。古いメモを参照する際に、ご活用頂けましたら幸いです。また、最近流行りのRSSにも対応させてみました。

2004年までの商業誌上での執筆記事や出版書籍については、profile にまとめています。2001年から約3年にわたる執筆作業で、頭の中はほとんど放電状態となってしまったのですが、この1年半でふたたび充電できたように思います。

新しいテーマに関する下調べも進んでいますし、書籍の準備も進行中です。何より嬉しいことに、新しい出会いも数多くありました。このメモを通じて、ご紹介できればと考えています。

皆様から、引き続きご支援頂けましたら幸いです。

西田 亙 (NISHIDA Wataru)


2006-06-03 (Sat)

[プロフィール] 自己紹介

氏名

西田 亙 (Wataru Nishida)

URL

[Writing] 執筆書籍

Oversea Publishing GNU開発ツール (2006/9/1 初版発行)

SOFTBANK Pub Linuxから目覚めるぼくらのゲームボーイ! (2003/12/18 初版発行)

[Writing] 執筆記事一覧

UNIX USER Linuxカーネル2.4におけるNATとパケットフィルタ (商業誌初出)

掲載号ページタイトル
2001' 760-66Linuxカーネル2.4におけるNATとパケットフィルタ

UNIX USER ブートシェルGRUBをマスターしよう! (特別企画)

掲載号ページタイトル
2002' 164-73ブートシェルGRUBをマスターしよう!

Interface Linux徹底詳解 -ブート&ルートファイルシステム- (特集)

Linux徹底詳解

掲載号ページタイトル
2002' 7序章52-54なぜLinuxは難しいのか?
第1章55-65標準Cライブラリを使わないプログラミング
第2章66-77BIOS版 Hello, world! プログラムの作成
第3章78-97Linuxオリジナルブートローダの作成
第4章98-113オリジナルルートファイルシステムの構築
補遺114-120x86エミュレータBochsの使い方

UNIX USER GCCプログラミング工房 (連載)

掲載号ページタイトル
2001' 12131-138第1回 コードサイズの謎
2002' 1115-124第2回 glibcからの独立
2002' 2113-122第3回 バイナリを操るリンカースクリプトの正体
2002' 3109-120第4回 セクション配置のからくり
2002' 4121-130第5回 モジュールによるカーネル圏内突入
2002' 5103-112第6回 Making of Linux
2002' 697-106第7回 make configとmake dependの意義
2002' 7117-126第8回 vmlinux集約までの技
2002' 8125-134第9回 make bzImage製作技術
2002' 9117-126第10回 仮想機械octopus登場
2002' 10121-132第11回 素手でスピーカーを操る
2002' 11117-128第12回 踊るキーボードLED
2002' 12129-140第13回 FreeDOSでキーボードを操る
2003' 1105-120第14回 指先からの旅
2003' 2121-130第15回 キー入力処理を究める
2003' 3121-130第16回 本格仮想機械への序章
2003' 4131-140第17回 Cハッカーへのスタック
2003' 5127-140第18回 スタック操作・免許皆伝
2003' 6127-140第19回 セグメントを操る
2003' 8131-140第20回 VRAM自由自在
2003' 10129-138第21回 CRTに届くまで
2003' 11131-142第22回 VGAレジスタプログラミングに挑戦!
2003' 12121-134第23回 独自コンソールフォントの描画
2004' 1141-152第24回 仮想コンソールへの挑戦
2004' 2133-148第25回 幻の90x60テキストモード
2004' 3143-150Linuxカーネル2.6実践編 第1回 makeターゲットをマスターせよ
2004' 4121-132Linuxカーネル2.6実践編 第2回 SYSLINUXの秘密
2004' 5139-148Linuxカーネル2.6実践編 第3回 /usr/includeディレクトリの謎
2004' 6139-148Linuxカーネル2.6実践編 第4回 GCCソースツリーから理解するインクルードの極意
2004' 7127-136Linuxカーネル2.6実践編 第5回 モジュール対決 2.4vs2.6
2004' 8127-138Linuxカーネル2.6実践編 第6回 insmodコマンドを自作しよう!
2004' 9107-118Linuxカーネル2.6実践編 第7回 2.6モジュールを自作しよう!

UNIX USER Linuxから目覚めるぼくらのゲームボーイ! (4号連続特別企画)

Linuxから目覚めるぼくらのゲームボーイ!

掲載号ページタイトル
2003' 677-96第1回 はじめてのGBAプログラミング
2003' 787-110第2回 タイルモードでRPG
2003' 885-100第3回 時を刻むタイマー
2003' 983-116第4回 歌うダイレクトサウンド、踊るスプライト

Interface Linux/UNIX上でのプログラム開発の主役 GNU開発ツール入門 (特設記事)

掲載号ページタイトル
2003' 9119-129Linux/UNIX上でのプログラム開発の主役 GNU開発ツール入門

Embedded UNIX Linuxシステム縮小化計画 (連載)

掲載号ページタイトル
Vol. 162-72第1回 BusyBoxによるLinux基本環境の構築
Vol. 2106-121第2回 Linuxカーネルイメージのしくみとルートファイルシステム
Vol. 3158-185第3回 LinuxブートプロトコルとRAMDISK対応起動ディスク
Vol. 5141-157第4回 uClibcによるライブラリ縮小化

Embedded UNIX ゼロから始める組み込みLinuxシステム構築 (特集)

掲載号ページタイトル
Vol. 6序章6-8真にLinuxを理解するために
第1章9-30GNU開発ツールを使いこなす
第2章31-47システムコールから捉えた実行可能ファイル
第3章48-62Linuxカーネルとルートファイルシステムの融合
第4章63-84USBメモリを活用した可搬型Linuxシステムの構築

2005-01-14 (Fri)

[MCU] Abstract of MSP430 and Texas Instruments

MSP430

彷徨の果て、現在市販されている16bit MCUの中から、私のハートを射止めたのは、既に何度も紹介した Texas Instruments 社のMSP430である。

MSP430は、消費電流が1MIPSあたり250μA・待機時1μA未満という、超低消費電力が最大の謳い文句となっており、市中の電気・ガスメーター、血圧計やパルスオキシメーターなどの医療器具、テスター、果てはシューズなどに、幅広く採用されているMCUである。

アーキテクチャ野郎にとって気になるのは、まずレジスタ構成と、命令コードであるが、内部には16ビット汎用レジスターがR0からR15まで計16本、そして7つのアドレッシングモードが用意されており、C言語への最適化効率を強く意識した設計になっている。逆に、命令数は極限まで絞り込まれ、その数は驚くなかれたったの27である。基本的かつ必要最低限の命令セットのみが提供され、「足りない部分は、各自ソフトウェアで工夫して頂戴」という訳だ。

最近のMCUは、多機能化を計るあまり、ニーモニック(MOV, INC など命令の略称)を見ただけでは、一体何をする命令なのか、全く予想もつかないものが多い(中でも国内メーカーのニーモニックやポート名に対するネーミングセンスの悪さは際だっている)。結果として、プログラマーは新しい命令体系の習得および難解な略称名の暗記に、膨大な時間を費やされることになる。

これに対して、MSP430の場合は一瞥しただけで了解できる、汎用ニーモニックで構成されているため、過去にアセンブラーの経験があるプログラマーであれば、マニュアルをざっと斜め読みする程度で、命令セットの全体像を把握できてしまう。この分かりやすさが、初学者にも大きなメリットとなることは、言うまでもない。

優れたドキュメント群

よく練られたMCUはいくつか存在するが、MSP430が飛び抜けて際だっているのは、自身を支える豊富で良質のドキュメント環境にある。

先日、H8/300H に初めて興味を持ったユーザーが、Renesas Japan のサイトから、その製品概略を知ることは極めて難しいことを書いた。Texas Instruments ではどうだろうか?

まず、トップページでは、H8/300H と同じく10行前後の要約が示されているが、先頭に示された次の一行は、これ以上の名文は考えられないほど、完璧にMSP430の特徴を言い表している。

The MSP430 family of ultra-low-power 16-bit RISC mixed-signal processors from
Texas Instruments provides the ultimate solution for battery-powered measurement applications.

超低消費電力、16ビット RISC、Mixed signal processor、バッテリー駆動の計測機器に最適。この一文を読めば、経験あるエンジニアであれば、「ほー、アナログに強いTIのことやから、得意のADコンバーターやオペアンプ内蔵型の16bit RISC core MCU を出して来たんかいな。しかも、超低消費電力ねぇ、面白そうやんけ!」と来る訳だ。そして、読者の読みを予測したかのように、補足説明が下段で示されている(さりげなく値段も)。何気ない10行に見えるが、実は練りに練られた文章であることが分かる。

Paper work

この10行、実は学術論文で言うところの "Abstract" に相当する。ちなみに、論文は通常以下のパートから構成される。

  • Title (タイトル)
  • Abstract (要約)
  • Introduction (序論)
  • Materials and Methods (方法)
  • Results (結果)
  • Discussion (論考)
  • References (参考文献)

大学院生は、仕事が出来上がると、この枠組みに従い慣れない Paper work にいそしむことになる。誰もが最初は Results および Discussion にしか目が向かず、残りはケーキの飾りぐらいにしか捉えていないものだが、実は全てのパートが同じぐらいに重要なのである。一流誌になればなるほど、Reviewer (査読者)は Introduction から References に至るまで、実に細かく注文を付けてくる。また、経験を積むにつれ、Title と Abstract が持つ重みが分かるようになる。

The significance of Title and Abstract

PubMed (医学生物学文献データベース)ACM Digital Library などで文献検索を行った場合、適切なキーワード設定を行っていれば、そこそこの数の文献一覧が表示される。このあたりの感覚は、Google と同じであるが、文献検索における違いは、論文のタイトルおよび Abstract をチェックすることで、非常に高い確度で論文の "選別" を行える点にある。

参考までに、ACM Portal Digital Library 上で "Plan 9" の検索を行った際、トップに現れる文献を挙げておこう。この文献は、Rob Pike 氏による Plan 9 の Overview であるが、平易にして明快な Abstract はさすがだ。このように、文献検索においてはその場で論文の絞り込みが行えるよう、必ず Abstract が表示可能になっている。ちなみに、Digital library 中の全文PDFを入手するためには、会員登録(有料)が必要だが、このあたりに関しては Google Scholar と併せて、後日取り上げてみたい。

さて、情報がこれほど身の回りに溢れてくると、玉石混淆の中から、自分にとって価値ある情報をいかに効率的に拾い上げるかが、重要になってくる。ヒットした文献を片っ端から読みまくり、質の低い論文に「あ〜ぁ、またしょうもない論文を読んでしまったよトホホ・・」と、溜め息をついているようでは、限られた時間を有効に使うことは到底出来ないからだ。こうして、短い Abstract から真贋を読み取る "嗅覚のトレーニング" を積まざるを得なくなるのである。嗅覚が犬並みに研ぎ澄まされてくると、不思議なことに書き方のツボも分かってくる。

推察するに、MSP430 の Abstract を担当した著者も、その昔トホホを経験したに違いない。そして、自分の経験から、駄文がいかに読者の貴重な時間を損なうものであるかを学び、優れた文書がいかに読者の理解と成長を助けることができるかを悟ったことだろう。

MSP430 のサイト全体からは、このようなトレーニングを積んだ著者(達)が、持てる力のすべてを注ぎ込みながら、ユーザーのために尽力している姿が伺える。中でも私が度肝を抜かれたのは、Abstract 中の MSP430 Family でリンクされた、PDF ファイル。合計9ページから構成されるこのファイルは、エンドユーザーを対象にした MSP430 の Overview である。中でも、2ページという限られた空間の中で繰り広げられる、図入りの特徴紹介は神業的な完成度だ。見事という他ない。このような良質で魅力的なパンフレットを見せられれば、初めてのユーザーも「ちょっと検討してみようか」という気になるだろう。

このほか、Publications や Contributed articles という、堅気の人は到底使わない Academic な表現がサイト上で使われているところから見ても、著者もしくはサイト監修者は大学院において徹底的なトレーニングを受けた、Ph.D. (博士号取得者)ではないかと思われる。

上記のように書くと、「会社が売り上げを伸ばすために、ドキュメント整備に奔走するのは、当たり前なのではないか?」という指摘を受けることがある。確かに背景はその通りであろうが、「上司から命令されたので書きました」という "させられ体験" から、優れた文書は決して生まれない。著者自身が文書の価値と意義を知り、なおかつ担当したテーマに "惚れ込んでいる" 必要がある。

まさに心意気の世界であるが、MSP430 の最深部まで踏み込むと、TI エンジニアの凄さが垣間見えてくる。


2005-01-12 (Wed)

[MCU] 彷徨

遙かなるガンダーラ

さて、理想の16ビットアーキテクチャを求めて、旅が始まった訳だが、ガンダーラへの道のりは決して平坦なものではなかった。途中の道中をつぶさに記述していくと、立派な連載が出来上がってしまうので止めておくが、いくつか重要な点を記しておこう。

Renesas H8

私も日本人であるから、当初は国産MCUもいくつか下調べを行った。ところが、国内メーカーは、私のような一見客を最初から相手にする気がないのか、それとも彼らのプレゼンテーション能力自体に問題があるのか、Web 上に用意された資料のお粗末さは目を覆うばかりである。

具体的に、現在日本でベストセラーとなっているルネサスの H8/300H (旧日立)を見てみよう。トップページを開くと、H8/300H グループの一覧が家系図のようにズラズラと現れ、いきなり「どこ見ていいのか、訳分かんない」状態に陥る。

それでもめげずに、Overview を探すと "シリーズ概要" という項目の中に、H8/300H シリーズの特徴 というページが見つかる。「これこれ、このページを読めば概略が分かるのねぇ〜」と鼻歌交じりでクリックすると、一瞬我が目を疑うことになる。呪文にも等しいこの10行から、私達に一体何を読み取れというのか?H8/300H というプロセッサに興味をもって訪れたユーザーに対して、これほど冷たい仕打ちはないであろう。

「まぁまぁ、ここは日本だから」と、切れかかった自分をなんとか抑えつつ、「概略が用意されていないなら、ドキュメントを見れば良いのよ」と、次に "ドキュメント一覧" をクリック。と、そこに現れ出でたるは、153行にもおよぶ H8/300H ファミリーのデータテーブル。どうやら、「自分の目的にかなったMCUのPDFを、勝手にダウンロードして頂戴」ということらしい。新しいアーキテクチャを理解する上で、極めて重要な意味を持つアプリケーションノートも、閑古鳥が鳴くほど寂しい品揃えである。

悲しいことだが、これらのページを見る限り、エンドユーザーに自社製品を理解してもらおうという姿勢は、微塵も感じられない。大口の顧客に対しては、ルネサス社からの手厚い技術指導が用意されているのかもしれないが・・。

教材として考えた場合、プロセッサの単純さや、命令コードの対称性が重要になることはもちろんだが、それ以上に大切なことは、優れたドキュメント体系が整備され、良質のサンプルコードが用意されているか、否かである。こうして、H8 は教材候補の中から真っ先に消えていった。

蛇足ながら、同じ Renesas でも北米のページは、日本のものとは少々趣がことなる。試しに、同じ話題を扱った マイクロコンピュータ ラインアップMPU and MCU を見比べてみてほしい。また、H8S family のページ上に用意された Architecture というページからは、複雑なファミリー全体像を図から理解してもらおうという、工夫の跡が伺える。レジスター構成図もきちんと添えられており、感心(というか、本来これが当たり前なのだが)。日米のこの差は、一体何に由来しているのであろうか?

Atmel AVR

その後も遍路を重ね、購入した評価ボードは数知れず。途中、Atmel 社AVR 嬢に、しばし心を奪われることになった。Atmel 社のドキュメント群は質が高く、Application notes も充実していることに、いたく感心。すっかり Atmel 贔屓になってしまったのだが、命令体系、中でもアドレッシングモードがMCU向けにかなり特殊であったこと、プログラム書き込みに別途ライターが必要であること、下位機種には JTAG が装備されていないことなどから、最終的に断念した。

一方で、AVR は Olimex を始め、優れた学習キットが市場に出回っているので、手軽に楽しむことが出来る。また、binutils/GCC も AVR をサポートしており、GNU 開発ツールを活用してマイクロコントローラー・プログラミングに挑戦したいという方には、お勧めのプロセッサーと言えるだろう。


2005-01-11 (Tue)

[Thoughts] アーキテクチャを学ぶために

煮詰まる

しばらく連載執筆から遠ざかると、このようなちょっとした物書きさえも億劫になってしまう。日々の継続、そして大脳への刺激付けが大切というところだろうか。

GCCプログラミング工房を休載させて頂いた表向きの理由は、「書籍化に集中したいため」だったのだが、白状すると実はもうひとつあった。「今後、どの方向にテーマを展開していくべきなのか」、答えを見つけ出すことが出来ず、かなり煮詰まっていたのである。

登坂ルート

PC-UNIX の魅力に取り憑かれ、ご本尊であるカーネルの踏破に挑戦する人々は多いが、この山は大層険しい。よほど基礎体力と才能に恵まれた人間でない限り、まず間違いなく"遭難"が待ち受けている。かく言う私自身も、「あ〜れ〜〜!」と何度滑落したことか。

未だ踏破できた訳ではないが、ここ数年のトレーニングの甲斐あって、周りの見晴らしは随分良くなってきた。山の頂きも、くっきり見える。以前のように、息切れすることもない。これなら、無酸素登頂も夢ではないだろう。問題は、登坂ルートをどこに取るかだ。

3つの関所

最近は、Linux や FreeBSD のカーネル解説書などが豊富に出回るようになってきたが、これらの資料だけでカーネルを理解することは、極めて難しい。私自身の経験から考えると、カーネル・OSを理解するために、避けて通れぬ関門は以下のように3つある。

  • 開発ツール
  • カーネル内部のデータ構造
  • 標的アーキテクチャ

2番目については、これまで多くの解説が行われてきた訳だが、開発ツールとアーキテクチャについては、不思議なことにほとんど注目されることはなかった。むしろ、看過されてきたと言っても良いだろう。

カーネルの成り立ち、ひいては内部動作を正確に把握するためには、膨大なカーネルソースツリーから、一体どのようなコードとデータ構造が現れるのかを知る必要がある。PC-UNIX では、GNU 開発ツールが用いられるが、特に Linux では GNU ツール特有の、高度な使いこなしが随所で活用されている。このため、カーネルソースを読みこなすためには、GNU 開発ツールに通じることはもちろん、生成オブジェクトファイルおよび実行可能ファイルを規定する ELF 形式の内部構造も把握しておかねばならない。

GNU 開発ツールと ELF については、過去2年間の執筆を通じて、最低限知っておかねばならない勘所については、解説できたのではないかと思う。

残る関門はアーキテクチャであるが、IA-32 および PC-AT アーキテクチャは、あまりに壮大すぎて、どこから手を付けていいのやら皆目見当が付かない。手を付けるにしても、これまでアセンブラーや機械語に接した経験がない方々に対して、誰もが分かる明快な解説を書き下ろすことは至難の業、いや不可能に近い。

と、散々悩んだ挙げ句に、当時の私が出した結論が "仮想機械 Octopus" 編である。機能を絞り込んだ8ビットマシンのコーディングを通じて、レジスター・演算・スタックなど CPU の基本構造を理解し、FreeDOS 上での PS/2 キーボード・VGA カードプログラミング(いわゆるポート直叩き)を通じて、I/O 制御の意味とその素晴らしさを、読者の方々に体験して頂こうという、今にして思えばてんこ盛り状態の内容であった。

結果として中途半端なシリーズになってしまったことは否めなく、この頃からアーキテクチャ理解のための優れた素材探しが始まった。

アーキテクチャの師はいずこに

無論、最初から 486 もしくは Pentium プロセッサを理解できるに越したことはない。私自身、486 の解説書執筆に向けて、これまで数え切れないほど構想を練ってきた。しかし、現在に至るまで、納得できるアプローチを見つけ出すことは出来ていない。

その一方で、私の青春時代の経験が、「たとえ8ビット CPU でも良いから、ひとつの単純なアーキテクチャを徹底的に理解することが、何よりも大切ではないのか?」とも言っている。

確かにその通りだ。PC-8001 を20年以上昔のポンコツマシンと侮ることなかれ。この非力なマシンの中に、システムプログラミングの基本の多くは宿っていた。16ビットのアドレス空間は、今となっては鼠、いやノミの額ほどだが、それでも現在の初学者にとっては、十二分に広大な世界であり、壮大なロマンも秘めている。すっかり死語となってしまった Relocation という言葉も、16ビットの世界ではきらめきを取り戻す。

私を始めとする20年前のマイコン小僧達の周りには、このように理解する上で最適のサイズをもつアーキテクチャが、ゴロゴロしていた。かの Linus 氏も、おじいちゃんから譲り受けた Commodore VIC20 (CPU 6502)を、最初にしゃぶり尽くしたらしい。8ビットマシンは、基礎トレーニングを積むためのファームとして、最適のものであったと言えるだろう。

これに対して、現代のパソコン小僧達は受難続きだ。何と言っても、最初に目にするパソコンが、いきなり Athlon64 である。64ビットなど、もはやビットを数える気力すら萎えてしまう。これ以上にはないほど複雑化したマシンを前にして、ゲーマーもしくはインストール野郎と化す彼らを誰が責めることが出来よう。人間、自分が理解できない代物に対しては、条件反射的に思考停止するものである。

以上より、一個人が理解できるサイズを持つ、最適の16ビットアーキテクチャを探し求める旅が始まった。この20年の間に生じてしまった、アーキテクチャの Missing-link を紡ぎ直すためだ。ちなみに命令コードも16ビット長であることを条件に加えたのは、Octopus での苦い経験に基づいている。8ビット長命令コードは、確かに単純ではあるが、その後の発展性を著しく欠いてしまうからだ。


2004-11-17 (Wed)

[Books] 上がれ!空き缶衛星

Can Satellites

最近は、もっぱら「にわか天文学オジサン」と化して、本屋の天文学コーナーを熊のようにウロウロすることが多い。そんな折り、オレンジ色の "なっちゃん" の中に PIC マイコンの基盤が埋め込まれた摩訶不思議な表紙の本を見つけた。そのタイトルを「上がれ!空き缶衛星」という。副題は "CANSAT PROJECT" となっているから、これは Can Satellite Project に違いない。

うっそ〜〜〜!?カンカンを衛星にするんですか?マジですか?表帯に書かれたキャッチコピーが、また泣かせる。

熱い思いと、仲間がいれば、きっと上がる!
日本初の超小型衛星プロジェクトに青春をかけた工学部大学生たちの1年間の記録
熱血理科系ドキュメント!

と来たもんだ。裏帯のコピーは次のように続く。

机上でしか宇宙を知らなかった学生たちが突然、小さな人工衛星を作ることになった。
資金はほとんどない。
あるのは根気と体力と情熱、かき集めた皆の知恵だけ。
失敗の連続、思わぬ助っ人の登場、不夜城と化した研究室。
やがて、運命の打ち上げの日が来る・・・。

本書片手に、既に夢の世界へトリップしつつあった自分にふと気付く。「いかんいかん、この続きはお家で」

で、一気に読み切った訳だが、久々にムラムラとこみ上げてくる熱いものを感じた。読後感は、「なんで学生ばっかりこんな面白い事が出来て、税金払っとるオジサン連中には許されへんのや!」という、何とも自己中極まりないものである。正直、私はこの学生さんたちが、羨ましくて仕方がない。しばらくの間「オジサンはとっても悔しい、ウッキ〜〜!」と叫びつつ、Google 上でネタ元のチェックを行った。

残念ながら、本書の巻末には日本の NPO 法人である UNISEC (University Space Engineering Consortium) へのリンクが示されているだけであり、その後の感動的な CubeSat 打ち上げや、お世話になった AERO-PAC, ARLISS への URL が示されていない。"References" を欠いた学術論文は世の中に存在しないように、一般書と言えども出典は明示すべきであろう。

そこで、アメリカの偉大なるロケット野郎達と Twiggs 教授に心からの敬意を表しつつ、以下私なりに若干の補足をしておきたいと思う。

アメリカのロケット野郎達

ハードウェア野郎に国境はないが、こと "ロケット野郎" に関しては、日本は大きな遅れを取っている。川島レイ氏が書き上げる空き缶衛星物語は、アメリカはネバダ州、Black Rock 砂漠を本拠地に活動するロケット野郎達、AERO-PAC (Association of Experimental Rocketry of the Pacific) の粋な心意気からスタートした。AERO-PAC のサイトを覗くと、トップページでは2匹のアライグマがロケットに乗ってスッ飛んでいる。素敵すぎる・・。

本書を読むと分かるが、この CanSat Project は缶ジュースサイズの超小型衛星の打ち上げを目的として、スタンフォード大学の Robert Twiggs 教授が発案したものである。記念すべき第1回のプロジェクトには、日本から東大および東工大の学生チームが参加したが、肝心の打ち上げロケットを調達することが難しかったため、ロケット野郎達の胸を借りて、地上4000mの高度まで打ち上げることになった。約15〜20分間の滞空時間を利用して、地上局との遠隔測定データの通信、パラシュート制御による目標点への最短着陸(comeback competition)などが、各参加大学チーム間で争われる。昨年の様子はこちらから伺うことが出来る。お勧めは、砂漠から打ち上げられるロケット映像。オジサンも Black Rock 砂漠で「ヒュ〜ヒュ〜」って、言ってみたいのである(マジ)。

この他、現地での ARLISS 活動の様子は日本大学のページがお勧め。砂漠を背に歩く、8人の学生諸君のカッコ良いこと!

詳細については、ARLISS (A Rocket Launch for International Student Satellites)のサイトを参照して頂きたい。トップページ上では、Black Rock 砂漠を背にして、バズーカ砲を二回りほど大きくしたようなごっついロケットを担いだサングラスのオジサンが、妙な迫力を持って読者に迫る(ひょっとして、この人が Twiggs 教授であろうか?)。

それにしても、同写真の下に掲げられたメッセージが渋い。曰く、

The Rocket Launch for Can Do Students

この短い言葉は、ARLISS プロジェクトの神髄を実に良く物語っていると思う。翻って、我が日本において "Can Do Students" を育てることは、果たして可能なのであろうか?

CubeSat

その後、CanSat は CubeSat に進化し、遂に人工衛星として、地球周回を果たす。まさに「オネアミスの翼」そのものである。UNISEC 上には、CubeSat 物語と題して、東大チーム東工大チームの手記がそれぞれ掲載されているが、ひとつの読み物として私達の心を捉えるのは東工大チームであろう。

中でも、打ち上げのクライマックス「ピギャー」が聞こえた日のふたつは、読みながら思わず目頭がジンと来てしまった。第三者の言葉よりも何よりも、実際に製作に携わった学生達の生の言葉は、圧倒的な力を持っていることが分かる。それにしても、「体中の細胞という細胞から、涙が出たって感じでした」とは、なんて素敵な表現だろう。オジサン、完敗である。

最後に、エピローグに添えられた川島氏のメッセージを紹介しておきたい。

人生、先はわからない。
明日、生きているかどうかさえ、本当のところはわからない。
でも、あのとき、砂漠で彼らが強烈な一瞬を共有したのだということは、何があっても変わらない。
あの一瞬は、確かに彼らの十ヵ月の努力が凝縮したときだった。
・・中略・・
たくさんのひとたちの努力が、その一瞬、一つになる。
同じ夢を、その一瞬に、いっしょに見る。
そういう想いが凝縮した一瞬は、永遠なのだと思う。
永遠に残る「一瞬」を自らの手で作った学生たちに、心からの賛辞をささげたい。

本書をきっかけとして、学生さんから "元気" を分けてもらったような気がする。川島氏の次回作、CubeSat 編に期待しよう。


2004-11-16 (Tue)

[Time] 時間道、再び --UNIX timeの正体--

The identity of "UNIX epoch"

色々考えた末に、全く新しい観点から「UNIX 入門編」を書いている。世の中には当たり前とされていることが多いけれど、深く考えてみると実は納得がいかないことばかりだ。

UNIX time はそのひとつ。Linux 上の man page は、ctime に関して次のように言っている。

      The ctime(), gmtime() and localtime() functions all take an argument  of
      data type time_t which represents calendar time.  When interpreted as an
      absolute time value, it represents the number of seconds  elapsed  since
      00:00:00 on January 1, 1970, Coordinated Universal Time (UTC).

"UNIX time" が、西暦1970年1月1日の0時を起点(UNIX epoch)にした「経過時間(秒)」であることは、良く知られた事実である。タイムスタンプやオーナーID など、UNIX ファイルのメタデータは、inode 上に記録されるが、Linux の場合、UNIX time は atime/mtime/ctime (この3つの使い分けが、また曲者なのだが)共に、32ビット符号付き整数として取り扱われる。と言ってもこのままでは「絵に描いた餅」に過ぎないから、実際に自分の目で確認してみよう。

$ touch test
$ ls -l test 
-rw-r--r--  1 wataru wataru 0 Nov 17 01:44 test

簡単である。ただ単に、touch コマンドで空のファイルを作成し、そのタイムスタンプを確認しただけである。次に、coreutils パッケージに含まれる stat コマンドに登場願おう。

$ stat test 
  File: `test'
  Size: 0               Blocks: 0          IO Block: 131072 regular empty file
Device: 302h/770d       Inode: 29362       Links: 1
Access: (0644/-rw-r--r--)  Uid: ( 1000/  wataru)   Gid: ( 1000/  wataru)
Access: 2004-11-17 01:44:06.138216672 +0900
Modify: 2004-11-17 01:44:06.138216672 +0900
Change: 2004-11-17 01:44:06.138216672 +0900

察しの良い方は既にお分かりの通り、この情報は inode 上に格納された test ファイルのメタデータを人間様に読みやすい形式で出力したものである。これでは実感が湧かないので、-t (Terse) オプションを指定する。

$ stat -t test
test
0 0 81a4 1000 1000 302 29362 1 0 0 1100623446 1100623446 1100623446 131072

この訳の分からない数値の羅列が「inode」すなわち test の正体である。それぞれの値が一体何を意味するのか?誰しもそう思うだろうが、驚いたことに man 1 stat 中にその答えはない。「ええ加減にせんかい!」とモニターを叱り付けながら、stat.c のソースを紐解くことになる。

その答えは省略するが、数値を見れば容易に予測がつく通り、1100623446 が目指す UNIX time である。根性のある方は、このバイナリー値から時刻表記を導き出してみて欲しい。

UNIX time における2つのまやかし

さて、上記の英語表現の中には、実は2つの「まやかし」が隠されている。1つは、"second" の定義である。古来、数多くの天文学者達がより正確な「秒の定義」を目指して、骨身を削ってきたが、その努力は今も続いている。1秒の定義は時代の変遷と共に、大きく変化しておきており、「1970年の1秒」と「1972年の1秒」は厳密に言えば違うのである。これは 1961年から1971年までが「旧協定世界時 UTC」に基づいているのに対して、1972年以降は「(新)協定世界時 UTC」に基づいているためである。

国際原子時(TAI)という絶対的物差しで見た場合、1972年は1970年よりも約2秒遅れている。よって、UNIX epoch は本来 "TAI - 8 seconds" (国際原子時に遅れること8秒)と表記すべきだと私は思うのだが、世界中どこを探してもそのような記述はない。唯一、あの D. J. Bernstein 氏が次のように述べている

Arthur David Olson's popular time library uses an epoch of 1970-01-01 00:00:10 TAI.

Arthur David Olson 氏は、UNIX おける Time Zone に関して大きな貢献を果たしている一人だが、この "1970-01-01 00:00:10 TAI" という表記は私が下した結論に最も近い。D. J. Bernstein 氏が発表した clockspeed および libtai も含め、さらに深い調査が必要なようだ。「時間道は厳しい」が、時を巡る先人の苦労と知恵を知ることは、何よりも楽しい。

最後に、2つめのまやかしであるが、これは "UNIX time の時系" が明記されていない点にある。"since 00:00:00 on January 1, 1970, Coordinated Universal Time (UTC)" という表記を読めば、誰しも UNIX time は UTC に基づく時系だと考えることだろう。私も最初はそう考えた。

しかし、UNIX time と協定世界時 UTC は、全く違う。UTC は閏秒を含むが、UNIX time は閏秒を無視しているからである(POSIX も閏秒は無視すると明記している)。にもかかわらず、UNIX time と UTC が示す時刻は現時点で「一致しているように見える」。

本来両者が一致するはずはないのだが、この矛盾こそが UNIX における時間管理のアキレス腱となっている。Time Zone を適切に設定すれば、glibc は閏秒にも対応可能である。しかしながら、カーネルレベルでは、Linux はもちろん *BSD でさえ、その対応はおぼつかない。「時刻と時間」に関する正しい理解が、今こそ必要なのではないだろうか?


2004-10-23 (Sat)

[Writing] GCC プログラミング工房休載

休載

UNIX USER 10月号からの "GCC プログラミング工房" 休載は、「書籍執筆に集中する」ため、私から編集長にお願いしたものである。定期購読して頂いている読者の方々には誠に申し訳ないのだけれど、過去の連載を書籍にまとめるにあたり、自分自身の頭の中を、今一度整理し直す必要があると感じたからだ。

連載を続けることは、下調べの手間と時間さえ惜しまなければ、それほど難しいことではない。パズルに例えれば、毎回の連載はジグソーパズルのピースに相当すると言えるだろう。しかし、書籍の場合は違う。著者は読者に対して、ひとつの物語を提供しなければならない。過去36回に及ぶ連載は、個々にはそれなりの内容を備えているとは思うが、彩りは様々であり、すべてを組み合わせたところで、一幅の絵画が出来上がる訳ではない。

さて、毎月の原稿執筆作業から解放されること、実に3年ぶりであるが、この2ヵ月間、PDP-11 や OpenBSD、そして Plan 9 と格闘することで、これから書き上げる作品の全体像と、今後進むべき道がようやく見えてきたような気がする。がむしゃらに走り続けることも大切だが、時には思い切って "breathing space" を取ることも必要だろう。"breathing space"、日本語に訳すと「休息・一服」となってしまうが、本来の意味はちと違う。LONGMAN 曰く、

A short time when you stop doing something difficult, tiring etc.,
so that you have time to think more clearly about a situation or
time to solve a problem.

見事な英訳だ。ちなみに、手元にあるプログレッシブ英和中辞典では、次のようになっている。

(次の活動に備えての)息つく暇、考えたりする機会、休息、一服

思わず「なんじゃこりゃぁ?!」である。"To think more clearly"、"To solve problem" という、積極的な意味合いが日本語では吹き飛んでいる。この和訳から、仕事の合間に青い空を見上げながらタバコの煙を溜め息と共にくゆらす、疲れ切ったサラリーマンの姿を思い浮かべるのは、私だけであろうか?

英英辞典の言葉達が瑞々しく生きているのに対して、日本の英和辞典の言葉達はことごとく死んでいる・・。

そんな事を思いながら LONGMAN を眺めていると、素敵な表現を見つけた。"breath life/excitement/enthusiasm etc. into sth" というものだが、その英訳は次の通り。

To change a situation so that people feel more excited or interested.

将来、"This book will breathe excitement into readers." と評されるような書籍を書き上げたいと思う。

BGM: Kate Bush "Breathing"


2004-10-17 (Sun)

[Linux] Ultimate Code Viewer

Linux Kernel Graphing Project

サーバー移転も一段落したことであるし、"Goodbye, Linux" の続きを仕上げなければならないが、その前に小咄をひとつ。皆さんは Rusty Russell 氏が立ち上げた Linux Kernel Graphing Project をご存じだろうか?

これは文字通り、Linux kernel の構造をグラフ化し、人間の視覚を通してその全体像を捉えようという、何とも奇抜なプロジェクトである。その着眼点の素晴らしさと、頭の中のアイデアを直ちにコードで実現してしまう、Russell 氏の類い希なる行動力には、素直に感服してしまう。

氏のツールを介してポスター上に出力された Linux kernel の姿は、例えるならば、まさに「曼荼羅」だ。この曼荼羅は、4つのリングから構成されており、拡大していくとひとつひとつのソースファイルに含まれる関数達が現れる。思わず、「虫眼鏡を片手に、Linux 曼荼羅の世界を覗き込みたい」と考えた私は病気?

Free Code Graphing Project

この Linux Kernel Graphing Project は、多忙な Russell 氏に代わって、現在 Christian Reiniger, Michael Marineau の両氏が Free Code Graphing Project としてサポートを続けており、今年の8月下旬、待望のカーネル 2.6 対応版 が Sourceforge 上で公開されたばかりである。

「とりあえず、Linux 曼荼羅をこの目で確かめてみたい」という方は同サイト上に用意されている Linux 2.4.9 の PS ファイル(7.9MB) をチェックされると良いだろう。

自分で曼荼羅を作ってみたいという猛者は、lgp-2.6.0a をダウンロードされたい。ビルドの詳細は README に書かれているが、ポイントは以下の通り。

  • lgp パッケージ以外に、"bc" および "flex" が必要になるので、ホスト環境にこれらのツールが用意されていなければ、あらかじめインストールしておく(Debian であれば、"apt-get install bc flex")。
  • カーネルソースツリーを展開しておく。ちなみに、Linux-2.6.8.1 では 正常な PS ファイルを出力できなかったが、Linux-2.6.0 であれば問題なし。どうやら、新しいカーネルバージョンには対応できない模様。
  • make KERNEL_DIR=/usr/src/linux-2.6.0 を実行。Pentium 4 3GHz で約30分が経過。
  • 最後に ./posterize a0 1 でA0サイズのポスターイメージを出力。私の場合は、そのまま Mac OS X 上の Illustrator で曼荼羅を堪能。

じっくり曼荼羅を眺めてみると、その美しさとは裏腹に、得られる情報は意外に少ないことに気付く。それもそのはず、この曼荼羅は「関数」のみに注目し、肝心な「データ構造」が欠落しているからである。「関数とデータ構造」の依存関係をグラフ化するツールがあれば、Kernel hacking は一段、いや十段と楽しいものになるのではないだろうか?

ちなみにこのポスター、Linux 2.4 版が Linuxcare で販売されている模様。プロッター出力が利用できる方は、2.6.0 曼荼羅を壁に張り出しておけば、友人に自慢出来ること間違いなし、である。

A 3D animation of Linux source code development

私のような物足りなさを感じたのかどうか、フランスの Pascal Brisset 氏は、なんと Linux kernel source tree の3次元表示を実現してしまった。

3次元表示された Linux-2.4.5 の美しい姿 (9MB MPEG)は、思わず溜め息が漏れるほどだが、圧巻は Linux-1.2.0 から Linux-2.4.1 への成長の軌跡 (12MB MPEG)である。バージョンの進行途中で、新たに追加されたソースファイルは、赤色でフラッシング表示されるのだが、その姿はまるで広大な銀河の中で新星が誕生しているかのようだ。

こうしてみると、カーネルソースツリーというのは、まさしく人類が生み出した「小宇宙」という気がしてくる。贅沢を言わせてもらえば、同じ環境上で NetBSD と Linux の小宇宙を比較しながら、存分に俯瞰してみたいものである。


2004-10-12 (Tue)

[UNIX] From Debian/GNU Linux to OpenBSD

サーバー移転

いつの間にやら、3ヵ月が経過・・。この間に没頭していた、新しいサーバーへの移転作業が、ほぼ完了。従来レンタルしていた国内のサーバーから、北米のサーバーへ引っ越したため、日本からの応答性は今ひとつ。しかし、同じ値段で「Dual Xeon HP blade server, 2 x 80GB HDD, なおかつグローバル IP 9個」が利用できるのであれば、十分にお釣りが来るほどだ。HDD が RAID ではないため、合計 160GB を自在にマウント可能という点も気に入った。

もちろん、私のような「悪い子」には必須の、シリアルコンソールおよびリモート・リブート機能も最初からサポートされている。日本で同じスペックのレンタルサーバーを使用するためには、おそらく数倍を遙かに超える出費が必要だろう。サーバー天国、アメリカに多謝。面白かったのは契約時に、わざわざアメリカから確認の電話が掛かってきたこと。これも初めての経験である。

同サーバーにプリインストールされるOSは、Debian GNU/Linux もしくは FreeBSD 4.x だけであり、新たに挑戦したかった OpenBSD は用意されていなかった。このため、渋々 Debian を選択。が、悪い子の真の狙いは Secondary IDE drive に OpenBSD を「リモート作業で」インストールすることにあったのである。

経験豊かなインストール野郎の方々は、上の文章を読んで「そんなアホな!」と思われたことだろう。私も当初は無理だと考えていた。しかし、海外を丹念に探したところで、OpenBSD をサポートしているような奇特なレンタル会社はほとんど皆無と言って良い。たまにサポートされていても、料金や、上述したシリアルコンソールを始めとする様々な条件に適合するサービスを見つけることは出来なかった。

という事で、「自力でリモートマシンに OpenBSD をインストールするしか、オジサンに道はない」という結論に達した次第。「必要は発明の母」とは、まさにこの事である。

さて、「手元のマシン」に PC-UNIX をインストールすることは極めて簡単だ。最近はやりの ISO image を使えば、それこそ猿でも出来るかもしれない。しかし、今回のお相手さんは、遙か太平洋の向こう、アメリカ東海岸に位置するデータセンターにいらっしゃる。無論、私の手は届かない。これまで何度も夢見た「リモート・フロッピー挿入装置」も、この世にはまだ登場していない。

では、どうするか?一応、ネット上も調べてみたが、このような世迷い言を実行に移した人間を見つけることは出来なかった。世の中というのは、思いの外、常識人ばかりのようである。で、色々下調べを続けた結果、OpenBSD のインストーラーであれば、必ずや夢は実現できると確信。

OpenBSD のブートローダーに手を加えては、リモートリブートを繰り返すこと、幾たびか・・。数え切れないほどの失敗の後、遂に東海岸で OpenBSD が産声を上げたときの感動をオジサンは忘れない。久しぶりに、背筋がゾクゾクする感覚を味わった。

インストール途中、Debian server が接続不能になった事を察知した向こうの管理者から、すかさず「何か困ったことはないか?」とメール連絡があった。「実はリモート作業で OpenBSD をインストールしているため、しばらくサーバーは不通の状態が続く模様」と答えたものの、続く返事はなかった。恐らく、東海岸のサーバー管理室では「Japan の与太郎が訳の分からんことやっとるぞ。アホちゃうか!」位に思われていたのだろう(私が向こうの立場であれば、そう考える)。

で、目出度く OpenBSD current が Dual Xeon 上で起動した後に「作業は無事完了」とのメールを送ったところ、今度はすかさず以下のような返信が到着。

Wataru,

I must say I'm quite impressed!
If you have some time, I'd love to know how you managed to do that.
Glad it worked out for you.

短いメールではあるが、太平洋を越えて届いた祝福に感謝。今回、私が行った作業の「意味」を彼は十二分に理解してくれたらしい。このような「共感」は、何者にも代え難いものだ。

さて、今回の「変態技」を実現するために必要となる知識は以下のようなものである。

  • PC-BIOS におけるブートドライブの取り扱い
  • MS-DOS パーティションにおける MBR と PBR の違い
  • BSD スライスおよびパーティションの構造
  • OpenBSD ブートローダー(biosboot / boot) の内部構造

しかしながら、残念なことに、これらについて明快に記載した文書は、私が知る限り存在しない(だから苦労するのだが)。例えば、インストールの際には必ず登場する「基本パーティション・拡張パーティション」というお馴染みの言葉。このふたつの用語は、インストール解説書中で当たり前のように使われているが、実は両者を正確に定義した文書というものは見あたらない。誰もが曖昧なままに、パーティションにまつわる言葉を使っているのである。

ということで、いつの日か、今回の経験を元にした「パーティション・シリーズ」を執筆できればと思う。ちなみに現在は、Debian を BSD slice 上にインストールする変態フロッピーディスクを作成中。最新版である Debian unstable のブート・ルートフロッピーイメージの内部構造も、なかなか奥深く、面白い。


2004-07-13 (Tue)

[Linux] Goodbye, Linux 4.

man 再考

今日は man ファイルを通して、Linux と BSD の世界観を検討してみよう。

これまで、雑誌上や本メモ上で指摘してきた通り、Linux 環境における「man section 2」は、惨憺たる状況にある。Section 2 には、開発者にとっての生命線とも言うべき、全システムコールの解説が収められているが、Linux のそれは正式な管理者が存在せず、しかもカーネルハッカーが自由気ままに改編・削除・追加を行うために、最新カーネルとの整合性が全く取れていない箇所が多々存在する(私の経験からして、Linux の man section 2 は、読まない方が身のためである)。

今月号の GCC プログラミング工房でも紹介したが、2.6 カーネルのモジュール関連では、"Init module" system call の引数インターフェースが様変わりし(2つから3つへ)、"Query module" system call に至っては、驚くべきことに削除されている。

ライブラリー内における関数インターフェースの変更であれば、まだ話は分かるが(それでも全世界のユーザーへの影響は甚大である)、カーネル・システムコールの引数が変更されたり、システムコールそのものがあるバージョンから突如姿を消す・・という事態は、良い意味でも悪い意味でも Linux kernel ならではのものだと言えるだろう。

前回紹介した記事やプレゼンテーション資料を読めば、なぜ Russell 氏が周囲の反対を押し切ってまで、この改編を貫き通したのかは、理解できる。確かに、"bad code" に対しては、"patch" よりも "rewrite" が相応しい場合もあるだろう。

しかし・・Linux を巡る状況は、Torvalds 氏が恐る恐る 0.01 をネット上で公開した時とは、打って変わっている。今や Linux は、ひとつのカーネルインターフェースの変化が、全世界のユーザーに影響をもたらすまでに巨大化しているのである。

今回のモジュール・インターフェースの変化により、残業をよぎなくされた技術者達は、かなりの数に上ることだろう。しかも、この変化の技術的背景や、正しい問題回避方法について触れた資料は、カーネルソースツリーはもとより、ネット上にすら存在しない。一体どれだけの、無駄な時間が世界中で浪費されたことか?おそらく、この延べ時間は、Russell 氏自身が 2.6 モジュール開発のために費やした時間を、遙かに上回るのではないかと思われる。

頭の良い Russell 氏であるから、自分のコード改変が世界中のユーザーに与える影響は、事前に予想できていたに違いない。だからこそ、module-init-tools という 2.6 用のユーザースペースツールを自前で用意し、バージョンアップによる混乱を極力避けようとしていたのだろう。

ところが、蓋を開けてみれば、言葉足らずであったことは否めない。モジュール開発者にとっては、2.6 モジュールの正体が掴めないだけでも、大きな不安の種だろう。さらに悪いことには、新しく導入された kbuild による「不透明な 2.6 モジュール構築環境」が追い打ちをかける。こうなると、もはやユーザーは思考停止、ノックアウト状態である。開発者が「わしゃ、2.6 やーめた」と退散したとしても、誰が責められようか?(現実世界において、逃げることはかなわぬ夢だろうが)

このようにしてみると、どうにも不思議でならないのは、module-init-tools ソースツリーの中身である。この中には、新しい insmod, modprobe などのソース以外に、同コマンドの man ファイルが含まれている(insmod.8, modprobe.8 など)。しかし、新しいシステムコールに対応した man ファイルは用意されていない。

なぜか?この背景には、Russell 氏個人だけではなく、Linux 自身が抱える根元的な問題が存在するように思う。

Top pages

ここから先へは、Linux の世界にどっぷり浸かっているだけでは、進めない。思い切って、視点を変える必要がある。幸い現在は、ネットを通じて様々なドキュメントや、ソースツリーが手に入るのであるから、これらを活用しない手はない。

それでは、何気ないことではあるが、様々な PC-UNIX のトップページから man ファイルを探してみよう。

何はともあれ、Linux である。その Linux であるが、正式ホームページとなると、はて一体どこになるのであろうか?Linux と言えば、カーネルである。カーネルと来れば、"The Linux Kernel Archives" であるが、その実体はあまりに素っ気ない。カーネルソースツリーとパッチが無造作に置かれているだけである。

次に OSDN 主催の "LINUX.COM" を見てみよう。こちらは、いささか賑わっているようには見えるが、目指す man ファイルは見当たらない。

RedHat もしかり、SUSE もしかり、あの Debian でさえ御同様という有様である。Linux の世界では、「man ファイル問題はタブー化」しているのだろう。

さて、恐ろしいのは、Linux 界では上記のような事態が日常化してしまい、多くの人達が「これが普通なのだ」と受け止めてしまっている点にある。ここで、BSD を見てみよう。全体として BSD の世界は文書に重きを置いているが、中でも丁寧に整備されている OpenBSD を見て欲しい。

OpenBSD では左サイドバーの中程に存在する "OpenBSD Resources" の2段目に、そのものズバリの "Manual Pages" というリンクが用意されている。

"OpenBSD Manual Pages" には、システムコールのセクション2はもとより、各種システムツールからライブラリーに至るまで、あらゆる man ファイルが完備されている。

しかも、各セクション毎に introduction が設けられており、システムコールであれば、intro(2) を参照すれば、重要な errno のインデックスコードに始まり、各種引数タイプの解説などを一望することができる。

"Very Important For New Systems" と題された afterboot も秀逸である。これは、インストールを完了したシステム管理者向けに、最低限必要な勘所を解説したものだが、「ユーザーが出来るだけ無駄な時間を割くことがないように」という、大人の配慮が伝わってくる。

なぜ BSD では、このように全ての man ファイルを一同に会してユーザーに提供することが出来るのか?その秘密は、ソースツリーにあるが、続きはまた後日(ちなみに、Plan 9 の man ファイルは BSD のさらに上を行く)。


2004-07-06 (Tue)

[Linux] Goodbye, Linux 3.

Source be with you!

今日は視点を変えて、Paul Rusty Russell 氏の立場から、2.6 モジュール問題を捉えてみる。

既に述べている通り、Russell 氏自身は今回のモジュール改編に関して、ほとんど文書を作成していない。Linux 2.6 カーネルソースツリーはもとより、同氏が作成に関わっている module-init-tools パッケージ内部にも、説明と言えるような記述は見当たらない。よって、2.6 モジュールに新たに関わることになった世界中のエンジニア達は、まさに手探り状態で歩を進めることになる。

「2.6 カーネルの華々しい登場と共に、世界各地でその新機能を紹介する記事が配信されたではないか?」という意見もあるだろう。しかし、少し斜め読みすれば分かることなのだが、この手の紹介記事は表層をさらりと撫でるだけであり、読み終えたところで何ら残るものがない、もしくは書いている本人が内容を理解していないため、読者はチンプンカンプンというものが、ほとんどである。

「いや、そんなはずはない。世界のどこかに有効な資料があるはずだ!」と、辛抱強く検索を続けた猛者の目には、5/2 のメモで紹介した、"Porting device drivers to the 2.6 kernel""Migrating Device Drivers to the 2.6 Kernel" などが見つかるかもしれない。しかし、これらの資料とて本気で読み進めて行けば、「make modules の裏側で一体何が起こってんのよ?」、「そもそも 2.4 モジュールと 2.6 モジュールの物理的違いってどこにあるの?」という根元的問題には全く触れていないことが分かる。要するに、

誰も分かっていない、もしくは誰も問題点を明文化していないのである。

結果、「"Source be with you!" だよ、お兄さん、いやオジサン」と年老いた頭を鼓舞するしかない訳だが、最近の集中力低下は凄まじく、テストコードを組んで解析を終える頃には、それこそ息も絶え絶え・・の状態。しかも、これまで精魂込めて書き上げた解説が、新しいカーネルの登場によって、全面的に書き直さなければならないというのは、ライターですら萎える。新たなコードの導入によって、Replace される側のプログラマーは、もっと応えるだろう。

よって、ここに "Module War" の幕が開ける訳である。

The Great Module War

2.6 カーネルが持つ新機能の中でも、極めて重要な位置を占めるモジュール改編の背景については、次のふたつの資料が参考になる(というか、このふたつ以上に優れた資料を見つけることは出来なかった)。

前者は、日本で昨年10月に行われた Linux Kernel Conference 2003 での Russell 氏の講演資料を PDF 化したもの。後者は、硬派な記事で知られる KernelTrap.org で紹介された、Russell 氏へのインタビュー記事である。

残念ながら、前者はプレゼンテーション資料をまとめただけであり、講演内容については、各スライドから推測するしかない。しかし、極めて興味深い内容を含んでおり、私のように当日の講演を聞き逃した方の中で、モジュールに興味をお持ちの方には、是非とも一読をお勧めする。

ただし、本講演の内容を理解するためには、2.4 モジュールと 2.6 モジュール回りのカーネルソースおよびヘッダーファイルを熟読しておく必要があるだろう。あとは、ELF セクション構造と、GCC における __attribute__ 指定子使いこなしのノウハウも必要。

余談ではあるが、Linux カーネルを読みこなすためにまず必要なことは、binutils および GCC の基本操作知識を身につけると共に、ELF の構造、中でもセクション構造、さらには GNU tool によるセクション管理の方法をマスターすることにある。

私自身その昔「とっても痛い目」にあったので、よく分かるのだが、世の中の識者達は誰も「カーネルソースを読む前に、binutils/GCC/ELF を勉強せよ!」とはアドバイスしてくれないのである。このアドバイスに出会うことが出来なかったおかげで、まる2年近くは無駄な彷徨を続けてしまった・・。

「人生は短い。後輩の方々には、私と同じ轍は歩まずに、最短距離を進んで欲しい」という思いで、GCC プログラミング工房はスタートした。近々連載の方はお休みを頂いて、これまでに書いた記事をベースにしながら、GNU 開発ツールおよび ELF 解説書を「現在の視点」から新たに書き上げる予定。ということで、書籍の方は今しばらくお待ちくださいませ。

Russell 氏曰く

さて、肝心の講演資料だが、この内容を解説するためには、連載にして数回分以上の分量が必要になる。来月の GCCプログラミング工房を読み終えた上で、再度目を通して頂ければ、より深く味わうことができるかもしれない。手短にまとめれば、この講演の中では次のようなことが語られている。

  • Russell 氏がモジュールの改編に取り組むことになった、その理由と背景
  • 当時の 2.4 モジュールに存在した問題点
  • それまでユーザースペースで行われていたたリンク作業をカーネル内部に持ち込むアイデアとその実現
  • その結果、いかにコードが短縮かつ洗練されたか(insmod などのツールも含む)が述べられる。Russell 氏ここで一句
You'll Only Ever Know If You Write The Code
  • 御意。意訳すれば、「自分自身の手でコードを書いた時、初めて問題点とその解決方法を理解することができる」、これには諸手を上げて大賛成。
  • カーネルソースツリーへのマージを巡る戦いや他のハッカーから受けた影響の話(.gnu.linkonce.this_module セクション誕生の舞台裏が明らかにされている)が続く。そこで、氏曰く
Be Vicious With Code. Be Nice With People
  • これは、Linux community を理解する上で、非常に示唆に富む言葉であろう。以後、いくつかのテーマが続くが、この資料中で最も着目すべきは、次に示すふたつの Rusty's Lesson である。
No Feature Is Complete Until It Has Lots of Users.

そして、

Good Code Lowers The Barriers: More People Can Do More Cool Things.

氏が数々の軋轢をものともせず、積極果敢にコード改編に取り組む背景には、このような信念が存在するのである。

Where to go?

続いて、 KernelTrap.org で紹介された、Russell 氏へのインタビュー記事を見てみよう。この長文のインタビュー記事を作成した Jeremy Andrews 氏、ただ者ではない。

ちなみに、Russell 氏は先ほどの講演資料中で、自己紹介のために、同インタビュー記事を引用している。それはなぜかと言えば、Jeremy 氏が、2.6 モジュールの意義を十二分に理解できるほどの凄腕インタビュアーであると同時に、事実を冷静に分析し読者に伝える力を持っているからである。

一般的に、インタビュアーというのは「お前、分かっとんのかい!」という輩が多いものだが、本記事はその意味において、奇跡的ですらある。

「質問をする」ということは、実は極めて高等なテクニックであり、長期間にわたり地道な修練を要する。少しでも「アホな質問」を浴びせかけようものなら、実力を持った相手であればあるほど、瞬間的に見限られてしまうからだ(無論、聴衆からも)。見限られてしまっては、価値ある一言を引き出せるはずもない。こうして、多くのインタビューは不毛に終わるのである。

最後に、本インタビュー記事の中から印象的な言葉をふたつ紹介しておこう。ひとつは、Russell 氏がインタビュー中で引用している、Kernighan and Pike の言葉。

Don't patch bad code, rewrite it!

そして、もうひとつは「あなたがこれから取り組もうとしている、次のカーネルプロジェクトについて教えてください」という問いに対する答え。

... There's some "workload management" stuff which looks really useful, but maybe I'll
wonder into some of the more bohemian parts of kernel and be forced to rewrite that instead.

このふたつの言葉に氏の考え、そして Linux kernel の行く末がよく現れている。Linux user たるもの、カーネルの rewrite は今後も覚悟しておかねばならないだろう。

次回は少し視点を変え、BSD や Plan9 の世界から、今回の問題を考えてみたい。


2004-07-04 (Sun)

[Linux] Goodbye, Linux 2.

Single-bit decisions

先日のメモは、夜明け前に書き上げたせいか、感情が入りすぎてしまった・・反省。Rusty Russell 氏の Last name もミスタイプしてしまい、猛省。今日はまだお昼過ぎなので、少し冷静に話の続きを進めることにしよう。

Goodbye, Linux は、コメント欄を含め、各所で様々な評価を頂いているようだが、私達にとって大切なことは可能な限り多くの視点から問題を捉えることにある。本メモ上で提示しているものは、あくまでも私個人のひとつの視点に過ぎず、世の中にはそれこそ星の数ほどの考え方が存在する・・はずだ。

ところが日本というのはおかしな国で、「ハト派・鷹派」、「右翼・左翼」という言葉に代表されるように、物事を1ビットの粒度で捉える習慣を幼少時代から叩き込まれる。「戦争はいけません」、「日本国憲法第9条は偉大です」という具合に。当時の日本で一体何が起こり、世界はどう動いていたのか。その事実を教えた上で初めて、解釈と議論が成り立つはずだが、子供に考えるチャンスは与えられない。大人ですら、無署名の新聞記事やニュースに翻弄されるばかりである。

世界は1ビットで判断できるほど単純ではないし、地域、民族、宗教、時代により、物の見方は変わる。今やゲーム機ですら128ビットの表現力を持とうかという時代に、どうして「賛成か否か」、「善か悪か」というお粗末な物差ししか使えないのだろうか?

実は、かねてから「オープンソース」というテーマに関しても同じ危惧を抱いている。「ソース公開は善であり、無料であることもまた善」、ここまでは理解できなくもない。しかし、「ソース非公開は悪であり、有料であることもまた悪」という暗黙の風潮は、日本国憲法第9条と同種の問題を孕んでいるような気がするのは、私だけであろうか?

Linux には光り輝く一面と、人々を迷宮に誘い込む闇の一面が存在する。Linux を正しく理解し、効率的に運用するためには、少なくともこの二面を熟知しておく必要があるが、悲しいことに日本のマスメディアは、前者にしか興味がないようである。

現代の広大な情報砂漠を渡り切るためには、自らの足で歩き、自らの五感を信じ、自らの頭で考え抜くしかない。

Allied forces or multinational force?

真実を求めて旅する者の前には、古来「物の怪」があの手この手で、立ちはだかる。日本では、この物の怪が新聞やテレビであったりするから、大層怖い。その一例をご紹介しておこう。

最近、マスメディア上を賑わせている言葉のひとつに、「多国籍軍」がある。私がこの言葉を初めて知ったのは、今を遡ること14年前の湾岸戦争(Gulf War)。天の邪鬼の私は、その「もって回った表現」を訝しく思い、早速その原語を調べてみた。結果、驚愕した。

当時の日本人にすっかり浸透していた「多国籍軍」という言葉は(今もそうだが)、驚くなかれ "Allied forces" の翻訳だったのである(意訳、いや超訳と言うべきか)。海外では、プロのニュースライターは、記事中で同一用語の繰り返しを極力避けるため、 "Allied forces" を "Coalition forces" と言い換えることも多いが、両者の日本語訳は、どう考えても「連合軍」である。Allied から多国籍という言葉を捻り出すためには、かなりの労力とセンスを要する。

当時の日本において多国籍軍への「読み替え」が行われた背景には、「見えざる大きな意志」が働いていたと考えるのが、自然であろう。そして、日本中のマスメディアが一糸乱れることなく多国籍軍という表現で統一を図った事実は、かっての「大本営発表」を彷彿とさせるし、「民をして知らしむべからず」という精神も、この国では未だ健在であることが分かる。

以上、下手な夏の怪談よりも怖い話だが、これには後日談がある。つい最近の5月15日、イラクでは大々的な「連合軍の組織改編」が行われた。日本のマスコミ風に表現すれば、「連合軍改め多国籍軍」、正式には「CJTF7 (Combined Joint Task Force 7)改め Multinational Corps Iraq and Multinational Force Iraq」である。

改編前の連合軍ホームページについては、今なら CJTF7 で検索をかけるとキャッシュ中に見つけることが出来る。ちなみに、改編後はこちら。我が日本が最下段に位置しているのはご愛嬌として、Multi-National という上品な表現とは裏腹に、URL アドレスは "coalition-forces.htm" となっている。見かけは変わっても、中身は「連合軍」のままなのである。

14年前の時点で、既に「多国籍軍」と呼称していた(させられていた)私達日本人は、先見の明を持っていたというべきなのか、それとも自分たちにとって都合の良い蜃気楼を呑気に眺めていただけなのか。考えるほどに暗鬱な気分にさせられるが、これが現実の「一面」である。

さて、次回は Russell 氏自身の立場から、2.6 モジュール問題を捉えてみることにしよう。相手の側に立つことで、新たに見えてくるものも多いからだ。