The Design and Implementation of
the Gracious Days

March 2020

February 2017

January 2017

2020.3.29 00:11 バッファサイズと shcat の本来の意図に関して文末に追記

まとまった文章を書く機会が減ってしまって、これではいかんと久しぶりに更新。

大学院に入った 19 年前。担当していた大学院生から研究室の計算機環境の管理を引き継いだ。動機は単純で、Unix 系 OS の管理に興味があったからだった。研究室では過去の管理者が構築したメールサーバが引き継がれていて、詳しいひとはすでにいなくなっていた。Unix 系 OS はデスクトップ用途で使われておらず、学生はほぼ全員、当時現役だった管理者が Windows NT で構築したファイルサーバと数台の Windows クライアントマシンを使っていた。

Windows の環境に問題があるわけではなかったが、クライアントマシンは台数が少なく、取り合いになっていた。ネットワークも共有フォルダがあるだけで、認証やホームディレクトリの管理がなく、ユーザのデータは基本的にローカルに保存されていた。つまり、同じマシンにログインしないと継続して作業ができない。こういうものなのかな、と思いつつ、マシンが混んでいて使えない状況はどうにかならないかを考えていた。

大学の研究室には、当時でさえもう廃棄されてもおかしくなかった古いワークステーションが多数放置されていた。Windows 環境になってから、誰も興味を持たなかったのだろうと思う。管理を引き継いでから、ワークステーション上でも研究に必要なソフトウェアは使えることが分かったため、それらをデスクトップ用途で使えるようにしようと考えた。

管理を引き継いだものの、ひとつ問題があった。自分自身は MS-DOS や Windows の経験はあったけれど、そもそも Unix 系 OS の管理も、ネットワーク管理の経験もほとんどない。どういうものなのかよく分からないまま、Unix 系 OS を触ってみたいという興味だけが動機だった。すでに構築されているメールサーバを調べつつ、放置されていたワークステーションに電源を入れて OS をインストールしながらの試行錯誤。FreeBSD を本格的に使い始めたのも、この時期だ。まず自宅の Windows 環境を FreeBSD をベースにした環境に置き換えて、Unix 系の OS でメール等の日常の作業をこなすことで、いろいろと学んだ。大学の学生用計算機室にもデスクトップ環境が整備された Unix 端末があったため、その管理手法を参考にしたりもした。

そのような管理の過程で、SunOS 4 マシンと Solaris 7 マシンと HP-UX マシンと OpenBSD/sparc マシンのすべてで、同じホームディレクトリを NFS マウントする環境を管理し始めた。こういう環境では、.cshrc.profile のような、各ユーザがログイン時に実行するシェルスクリプトを、特定の環境に依存しないように書かないといけない。管理者は条件分岐を駆使して、環境変数等が適切に設定されるように初期スクリプトを書くことが多かった。一方、これらのシェルスクリプトはユーザのホームディレクトリに置かれていて、ユーザ自身が変更できる。そのため、ユーザがカスタマイズしたことが原因でログインできなくなることも良くあった。

当時は管理の勉強をしたくて、いろいろと試行錯誤をしていた。各種デーモンを起動する rc スクリプトを書いたり、前述のようなログイン時の初期設定スクリプトを管理していると、ひとつ変更を加えるたびに、コマンドのパス名や挙動の違いといった環境の差が見えてくる。そして、なるべく移植性が高くて効率の良いシェルスクリプトはどうすれば書けるのか、という点が気になってきた。

read コマンド

Korn Shell や Bourne Shell には read という組み込み (built-in) コマンドがある。標準入力から 1 行読み込み、内容を字句解析して変数をセットする。たとえば次のようにすれば、ファイル foo の内容を表示することができる。昔は $L-a とか入ってたらどうなるの、みたいな話を気にしなければならなかった環境もあったけれど。

cat foo | while read L; do
    echo "$L"
done

そして当時、あるソフトウェアのスクリプトに次のような関数が定義されていることに気がついた。

shcat() { while read L; do echo "$L"; done; }

shcat < foo

shcat 関数は、cat(1) のような動作をする。$@ を見て、ファイル名を取れるように書かれていた亜種もあったように思う。shcat の他に、basename(1) も変数のパラメータ展開パターンを使ってシェルスクリプト関数にしたものがあった。

最初に「なぜ cat(1) を使わないのだろう」と疑問に思って、たいして違いはないだろうとすぐに忘れてしまったのだが、SunOS 4 でシェルスクリプトを書いていた時にふと思い出して書き換えてみたところ、cat(1) よりも shcat を使ったほうがはるかに高速であることが分かった。cat(1) は fork(2) に加えてディスク I/O を伴うため、どうしても一定の遅延が避けられない。ファイルが大きければ遅延は相対的に小さくなるが、cat(1) で読み込むファイルは高々数キロバイト程度である。shcat はサブシェルを起動していても、十分に速かった。「ああ、これ書いた人は高速化が目的だったのか」と一人で納得した。ちなみに、SunOS 4 マシン(もっと具体的に言えば Sun IPC)はとても遅いマシンだったので速度の違いがはっきり分かったが、Solaris 7(こっちは Ultra 5)は体感できるほどの違いはなかった。

それからしばらくは、なるべく組み込みコマンドを使ってシェルスクリプトを書くようになった。バイナリの実行は遅い。多少効率が悪そうに見えても、組み込みコマンドだけで処理できるなら、そっちのほうが効率が良いだろう。そう考えたわけだ。

本当に速いのか?

それから 10 年以上経過して、シェルスクリプトだけである程度の大きな処理をするようなシステムを組む経験もしてから、ふとこの while read ... の構文の効率が気になってきた。結構長い間、なんとなく単純に組み込みコマンドのほうが速いよね、という印象を持っていた。ここまで読んで「ああそりゃ実は遅いよ」と思い至った人は、これ以降を読んでも面白くないと思う。

シェルスクリプトで組まれたシステムは、パイプで標準入出力を接続することで、バイトストリームになっているデータを複数のプログラムで処理するというのが基本になる。1入力・1出力の単純なデータフローモデルに基づいているため見通しがよくプロトタイプの構築にはとても便利だが、パイプを流れるバイトストリームのスループットと I/O 負荷が、システムの性能に大きく影響する。パイプで接続された複数のプログラムは一般的にそれぞれ処理の速度が異なり、入出力されるデータの量も対称ではない。つまり、接続されたパイプの中でもっとも遅いプログラムによってスループットが決まってしまう。また、パイプはユーザランド・カーネル間のデータコピーを伴うため、パイプをつなげばつなぐほど、I/O 処理によるコンテキストスイッチとデータのコピー量は増加する。たとえばパイプで単純に連結された 10 個の cat(1) プログラムに 1 MiB のデータを流すと、何もしなくとも合計で 10 MiB 分のデータがコピーされるわけだ。これが 100 GiB のデータだったら、1 つ処理(=プログラム)を追加するたびに、追加で 100 GiB のデータコピーが発生することを覚悟しなければならない。

モノリシックなプログラムと比較すると、データ量の増加や処理の複雑化に対して効率の低下が著しい。各プログラムのスループットを最適化することでピーク速度は改善できる可能性があるが、莫大な量のCPU時間をデータコピーに費やす問題は本質的で、避けることが難しい。したがって単純に考えると、シェルスクリプトに速度的な利点はなさそうに見える。しかし、パイプの I/O は並列処理が可能であることと、最近のマシンはマルチプロセッサが主流であることを考慮すると、シェルスクリプトは典型的なマルチプロセスモデルで並列動作するプログラムになる。シングルスレッドで手続き的にデータ処理するように書かれた Perl や Python スクリプトよりも、シェルスクリプトのほうが高速に動作することがあるのは、この特徴に起因する。たとえ CPU 時間を莫大なデータコピーに費やしていても、少しでも並列に動作するほうが勝ってしまう。パイプで連結されたプログラムは、それぞれが独立した実行コンテキストを持つので、並列動作しながらも副作用がない関数を組み合わせるのと同じような見通しのよさがあるのは利点だ。ただし、もちろんまともに並列処理するように書かれたプログラムには、データコピーが原因で効率の面ではほぼ勝てないけれど。

話を戻そう。昔のワークステーションの経験から、while read ... は cat(1) を呼ぶより速いと思っていた。「マシンが速くなって差が見えなくなっても、新しくプロセスを作る処理は必ず入るのだから効率は良いだろう」と信じていたが、「いた」と書いたように、結論から言うとそう単純な話ではない。鍵になるのは、このスクリプトで呼ばれるシステムコールの回数である。

read の仕様

read コマンドは単体で標準入力から 1 行読むという動作をするコマンドだ。while と組み合わせて使うことが多いが、組み合わせなければいけないわけではない。標準入力から読むのだから、基本的には read(2) を呼ぶ。では、どれくらいの単位で読むのだろう?

1 MiB のファイルを作成して、トレースコマンドでシステムコールの回数を調べてみる。*BSD 環境ならこれでカウントできるはずだ。手元の FreeBSD 環境で実行した結果である。今なら DTrace を使おうと言いたいところだが、今回の用途なら truss(1) で十分だ。

% truncate -s 1m /tmp/z.txt
% truss -f sh -c 'cat /tmp/z.txt > /dev/null' 2>&1 | grep read | wc -l
267

Linux は環境によって truncate(1) がなかったり、truss(1) の代わりに strace(1) があるかも知れない。これは CentOS の環境で実行してみた結果である。

% dd if=/dev/zero of=/tmp/z.txt count=1 bs=1024x1024
% strace -f sh -c 'cat /tmp/z.txt > /dev/null' 2>&1 | grep read | wc -l
263

256 よりちょっと大きめの数値が出る場合が多いと思う。これは多くのシステムの cat(1) は read(2) システムコールを 1 ページのバッファで呼ぶことと、1 ページが 4 KiB のシステムが多いことが理由だ。1 MiB は 256 ページ分になる。そして、cat(1) の実行前には locale データ等のファイルもいくつか読むため、若干多めに出る。

じゃあ肝心の、read コマンドを使ったシェルスクリプトの結果を見てみよう。

% truss -f sh -c 'cat /tmp/z.txt | while read L; do echo "$L"; done > /dev/null' 2>&1 | grep read | wc -l
1049612

% strace -f sh -c "cat /tmp/z.txt | while read L; do echo "$L"; done > /dev/null" 2>&1 | grep read | wc -l
1048844

この数値が正しく予測できたひとは、その理由も理解していると思う。しかしシェルスクリプトに慣れていないひとの多くにとって、この結果は予想外ではないだろうか。

原因は、read コマンドの仕様にある。read は、「1行」を読まなければならない。逆に言うと、1 行を越えてデータを読むことは許されていない。Unix系OSにおける1行とは、改行文字が区切りになる。ということは「/tmp/z.txt の内容を 1 文字ずつ読み、改行文字かどうか調べる」という処理が必要になる。改行文字の先の文字を読んではいけないからだ。パイプで接続されたバイトストリームは、一度読んだら戻ることができない。もし read コマンドが改行文字を越えてデータを読んでしまったら、その次に続くコマンドが読むべきデータが失われてしまう。したがって、read(2) システムコールを 1 バイトのバッファで呼ぶ以外に、方法がないのである。

read(2) システムコールを 1 バイトのバッファで呼ぶのは、非常に無駄が大きい。1 MiB のファイルを読むのに、およそ 100 万回のシステムコールを呼ぶ。単に cat(1) だけを使った場合と実行時間を比較してみると定量的な違いが分かるが、膨大な量の CPU 時間を消費する。

このような理由から cat foo | while read ... の構文は、とても遅いのである。コマンドの仕様から考えると、これはどうしようもないように見える。

改善するには

ここまで読んで、「最初の shcat の例とコードが違うのでは」と気づいたひとがいるかも知れない。文頭では、次のような例として紹介した。

shcat() { while read L; do echo "$L"; done; }

shcat < foo

当たり前だが、こちらは cat(1) を使っていない。先ほどのベンチマークは cat(1) を使っていたので、入力をリダイレクトにしてみよう。

% truss -f sh -c 'while read L; do echo "$L"; done < /tmp/z.txt > /dev/null' 2>&1 | grep read | wc -l
1048583

% strace -f sh -c "while read L; do echo "$L"; done < /tmp/z.txt > /dev/null" 2>&1 | grep read | wc -l
8199

すると、FreeBSD はほとんど変わらないが、Linux (GNU bash) はシステムコールの回数に変化が現れる。256 回には及ばないが、かなり少なくなった。これはどうしてだろう?

実は、シェルの実装によってはシーク可能な記述子を read(2) が読む場合に限り、システムコールのバッファを増やすという最適化が入っている。前述したとおり、read 組み込みコマンドは改行文字を飛び越さないために、1 文字ずつ読む必要がある。しかし読む対象がファイルであれば、ランダムアクセスできるので読み出す位置は自由に設定できる。そのため、read(2) で大きめに読み込んでから改行文字を探し、その直後に読み出し位置を再設定すれば 1 文字ずつ read(2) を呼び出す必要はない。

最適化が可能な場合にどれくらいの単位で read(2) を呼んでいるのか、さまざまな環境で実験したところ、次のような結果になった。

Solaris 7,8,9 /bin/sh:          128 バイト
Solaris 7,8,9 /usr/bin/ksh:     1024 バイト
Solaris 7,8,9 /usr/xpg4/bin/sh: 1024 バイト
Solaris 8 bash 2.03:            1 バイト
Solaris 7,9 bash 2.05:          128 バイト
FreeBSD 12 bash 5.0.7:          128 バイト
OpenBSD 3.6 /bin/sh:            512 バイト
FreeBSD 12 zsh 5.7.1:           1 バイト

FreeBSD の /bin/sh は、実験で示されたとおり、どのような場合でも 1 バイトで read(2) を呼んでしまう。それに対して Solaris の sh や ksh は 128-1024 バイト程度の長さのバッファを使う。ページ長にしていないのは、典型的な 1 行の長さはもっと短いと想定しているからだろう。

ソースファイルを細かく追いかけていないけれど、GNU bash は 2.05 付近で 128 バイトとするように変更が入ったようだ。ksh はかなり昔から 1024 バイトになっていた。一方、zsh は新しいバージョンでも最適化が入っていない。

FreeBSD が遅いままなのは悲しいので、簡単な最適化を実装して追加した。今後公開される 11.4, 12.2 以降には含まれる予定。

追記

cat(1) のバッファサイズはページ長よりも大きいのでは、という指摘が曽田さんからあったので追記。

確かにページ長になるというのは決めつけすぎですね。FreeBSD は一般ファイルの場合は MAXPHYS (=128 KiB) の 8 倍を最大のバッファサイズにしていて、たまたま(というかある程度は意図的に)ページ長と一致します。メモリが少ないシステムでは自動的に小さくなるので、いつも一定ではありません。もっと大きいシステムも多数あるでしょう。

また、shcat の本来の意図も曽田さんから。

これはそのとおり(cat(1) や basename(1) が使えない段階でも動くようにするため)だと思います。当時は理由が分からず、たぶん速度だろうと思ったわけです。指摘のとおり、SunOS は少なくとも 5.8 まではデフォルトインストールで //usr が分かれていて cat(1) は /usr/bin/cat にあるので、cat(1) が使えないこと気づくまでには、それほど時間はかかりませんでした。そしてその後 /bin/cat/ にあるシステムでも、たぶんこっちが効率的なのだろうと信じて shcat を使い続けていました。

先月書いた NFS の件、 10.12.4 Beta (16E144f) では修正されていることを確認した。 10.12.3 が出てからすぐに beta が出てしまったので、 10.12.3 で再現するかどうか確認しないままになってしまったけれど、 どちらにしても10.12.4の正式版には間違いなく修正が入るはず。めでたい。

Apple の中のひとに聞いたところ、社内では結構前に修正されていたものの、こういう修正はマイナーリリース(3桁目)のアップグレードにはなかなか入っていかないそうだ。カーネルの開発者がすばやく修正しても、その修正をリリースに回す前のQAは当然ながらセキュリティ上の修正が最も優先順位が高く、使われる可能性が少ないコンポーネントのバグ修正は後回しにされるとのこと。今回は 10.11 から 10.12 に切り替わるタイミングで入ったバグなので、10.13 が出るまでそのまま、という可能性もあったのかも知れない。

FreeBSD/pc98 の開発が終了へ

おつかれさまでした。

牧野武文著:ハッカーの系譜(9)オープンソースの巨人たち (10) 後からやってきて市場を奪うマイクロソフトより引用:

インターネットが普及するに従い、ソフトウェアは個人や企業が所有するCPUの上で動作するものよりも、ウェブサーバー上で動作するものが増えていった。ユーザーは、さまざまなサーバー上で動作するソフトウェアを遠隔地からアクセスして利用するようになっていった。

それにともない、知らず知らずのうちにソフトウェアはオープンソース化していった。たとえばウェブはオープンソースだ。ウェブは、HTMLというマークアップ言語で記述されている。ブラウザーの役割は、このHTMLのコマンド行を読みこみ、それを解釈し、対応した表現をおこなっていくことだ。HTMLの「ソースコード」は、多くのブラウザーで「ソースを表示」を選ぶと見ることができる。ウェブがオープンソースであることで、従来のソフトウェアや知的財産とはまったく違った文化が生まれた。それはウェブが、一種の共有財産とみなされるようになったのだ。

(中略)

Javaも実質オープンソースだ。Javaは他のプログラミング言語と同じように、ソースコードをコンパイルし、実行形式にしてウェブに組みこむ。ところが、この実行形式は完全なバイナリーではなく、中間言語のような形態をとっている。そのため、実行形式を逆コンパイルすれば、簡単にソースコードに戻すことができる。HTMLと同じように、ウェブデザイナーたちは面白い試みをしているJavaの実行形式を逆コンパイルし、ソースコードを表示させて、どのように実現しているのかを学ぶ。HTMLと同じように、Javaの表現も猛烈な速度で進化してきたし、今でも進化し続けている。

Javaは逆コンパイルできるから、知らず知らずのうちにオープンソースになるそうだ。

今まで一部しか読んでいなかったので連載を通読してみたけれど、やっぱり変なところが多いと思う。その12の文末に参考文献が載っていたので、これらの文献がおかしなことを書いているのかも知れない(しかし、「伽藍とバザール」はなぜ参考文献にないのだろうか?)

「オープンソース」という言葉がどこから発生したのかは、Eric S. Raymond が回顧録 を書いている。時期的には1998年、Netscape Navigator のソースコードを公開することが決まり、その方法を議論している場だ。当時はフリーウェア、フリーソフトウェアという呼び方が NetNews やパソコン通信などの電子掲示板ユーザの間で一般的だった。しかし商用ソフトウェアを扱う企業にとっては「フリー」という言葉に抵抗がある。そして「フリー」が何を指しているのかが曖昧だ。FSF の掲げる “free” とも紛らわしい。Raymond の回顧録には、そういった内容が「フリーウェア」を使わなかった理由として記されている。当時のMS-DOSやWindows用のフリーソフトウェアはバイナリ配布で、ソースファイルを含まないものも多かった。無料で配布するけれど、ソースファイルは公開しない場合のほうが多かったように思う。

1998年にO’Reillyが主催した Freeware Open Source Summit で、この用語が正式に発表された。Tim O’Reilly もオープンソースの重要性を啓蒙した人物のひとりだ。「オープンソースがそんなに素晴らしいなら、なぜオライリーの書籍はオープンソースにしないのか」という面白い問答がある。リンク先のO’Reillyの言い訳を読むと、オープンソースという言葉を聞いたばかりの人々の混乱がよくわかるだろう。「書籍を無料で公開・配布するのは、ソフトウェアと違って著者と読者の両方が得する構図にはならない」というのがO’Reillyの言い訳だった。

オープンソースという言葉の定義は、現在は OSI (Open Source Initiative) がOpen Source Definitionとして保守している。この定義によると、ソースコードが公開されているだけでなく、自由に再配布できなければオープンソースとは言えない。逆コンパイルすればソースが読めるからオープンソースだ、という主張はあまりに乱暴すぎる。

先のライセンス話のどこがおかしいのか聞かれたのでまとめておく。

  • 「BSDライセンスはコードを改変した人の権利がまったく守られない」と書いているが、改変した部分の著作権は改変したひとに帰属する。BSDライセンス表示の頭にある著作権者表示(”Copyright”)の行に、自分の名前を追加することが多い。なので、ひとつのファイルがたくさんのひとに変更されると著作権者が増えるので、すべての名前を残さないといけない。件の記事にあるように「原著作者のものになってしまう」ようなことはない。著作権者は改変部分だけを切り出して、別のライセンスで再配布することもできる。

  • 「GPLでは、原著作者という考え方がなくなり、パブリックドメインに近くなる」という記述。これもデタラメ。著作権はBSDライセンスと同様、改変したひとに帰属するので、”Copyright” の行を増やすことができる。ただしGPLの場合、再配布に関して「自分の変更部分だけ GPL でない条件を適用したい」という条件設定を許さない。「架空の存在に著作権を受け渡す」なんて大きな嘘なので信じちゃだめ。

BSDライセンスは、使用と再配布において著作権表示とライセンス表示の保存を配布者と受領者に要求している。GPLは条項が複雑なので一文でまとめるのは難しいが、誤解を恐れずにまとめるなら、使用と再配布において著作権表示、無保証であることの言明、ライセンスの表示を保存することに加えて、原本・改変物の両方の配布において「GPLの全条文を配布内容全体に適用すること」を配布者と受領者に要求していて、かつその条文の再配布条件には「ソースコードが配布されていること」が含まれている。

どちらも著作権に関しては何もコントロールしていないが、ライセンスを決めることができるのは著作権者である。たとえば著作権者は、同じコードをBSDライセンスとGPLという、それぞれ異なるライセンスで同時に配布することができる。また、一度 GPL で配布してしばらくたった後、GPL での配布を中止して、BSDライセンスでの配布に切り替えることもできる。ただしどちらの場合も、一度配布され、自分の手を離れてしまった配布物のライセンスは変更できない。できるのは自分が配布するのを中止することだけだ。BSDライセンスに切り替わったとしても、受領者は GPL で配布されていた時の配布物を GPL で配布し続けることが可能だ。

改変者の著作権表示の追加や、異なるライセンスの共存の例

ライセンスを切り替えた例としては、SSH が有名だ。SSH はヘルシンキ工科大学に在籍していた Tatu Ylonen 氏が 1995 年に開発を開始し、次のライセンス条項でソースファイルを公開した。

Author: Tatu Ylonen <ylo@cs.hut.fi>
Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
                   All rights reserved

As far as I am concerned, the code I have written for this software
can be used freely for any purpose.  Any derived versions of this
software must be clearly marked as such, and if the derived work is
incompatible with the protocol description in the RFC file, it must be
called by a name other than "ssh" or "Secure Shell".

その後、彼は SSH Communications Security という会社を設立、SSH のソースコードの配布を止めてソフトウェア製品としての販売とサポート業務を開始した。その時点で著作権者は彼だけだったため、売り物にする目的でライセンスを変更したわけだ。しかし、ライセンスを変更してもそれまでに配布していたソースコードの配布を禁止することはできない。OpenBSD プロジェクトは、それ以前に配布されていたソースコードを集めて、そこから OpenSSH の開発を開始した。こちらはBSDライセンスになっているが、著作権表示とライセンス表示がどうなっているのか例をみてみよう。次は OpenBSDプロジェクトが配布しているssh.cの冒頭である。

Author: Tatu Ylonen <ylo@cs.hut.fi>
Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
                   All rights reserved
Ssh client program.  This program can be used to log into a remote machine.
The software supports strong authentication, encryption, and forwarding
of X11, TCP/IP, and authentication connections.

As far as I am concerned, the code I have written for this software
can be used freely for any purpose.  Any derived versions of this
software must be clearly marked as such, and if the derived work is
incompatible with the protocol description in the RFC file, it must be
called by a name other than "ssh" or "Secure Shell".

Copyright (c) 1999 Niels Provos.  All rights reserved.
Copyright (c) 2000, 2001, 2002, 2003 Markus Friedl.  All rights reserved.

Modified to work with SSL by Niels Provos <provos@citi.umich.edu>
in Canada (German citizen).

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
   notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
   notice, this list of conditions and the following disclaimer in the
   documentation and/or other materials provided with the distribution.

THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

もともとの著作権表示とライセンス表示に追加される形で、2条項BSDライセンスと著作権者が記載されている。これは著作権者が複数いて、両方のライセンスが適用されることを意味している。それぞれのライセンスは矛盾する項目がないので共存できる。改変者の著作権や権利は、こういう形で守られるのである。元の著作権者の権利が剥奪されたり、改変者の著作権が認められなかったりすることは発生しない。

GPLとBSDライセンスは相互に矛盾する条項があるため、同時に適用することができない。したがって、GPLのソフトウェアを改変した場合、改変した部分や全体はGPLにしなければならない。ただしこの場合も、改変者の著作権は守られる。どちらのライセンスも、著作権の保持について何も制限していないからだ。

複数の著作権者がいる場合、全員が同意しないとライセンスを変更することができない。このことは、プロジェクト側で不都合が生じることがある。著作権者のリストが増えてくると管理が大変になることと、ライセンスの軽微な変更であっても著作権者全員の同意をとらなければならないからだ。ライセンスを変更することは滅多に発生しないのだが、その時になって誰かと連絡がとれなくなったり、著作権者がひとりでも自分の権利を主張すると困ったことになる。そのため、プロジェクトによってはコードの貢献に関して開発者とプロジェクトの間で著作権を譲渡させる契約を結んだり、一般に CLA (contributor license agreement) と呼ばれる、著作権の行使をプロジェクトに許諾するライセンス契約を結ぶ。たとえば前者は FSF (Free Software Foundation) が行なっていて(説明)、後者は ASF (Apache Software Foundation) が行なっている(個人向けCLAの契約書)。GPL も BSD ライセンスも、こういった契約なしに著作権が勝手に設定されるような乱暴な内容ではない。

GPL が適用されるソフトウェアの例

GPLの場合を見てみよう。次のライセンス表示は GNU troff のソースファイルにあるものだ。

Copyright (C) 1989-2000, 2001, 2002, 2003, 2004
   Free Software Foundation, Inc.
     Written by James Clark (jjc@jclark.com)

This file is part of groff.

groff 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, or (at your option) any later
version.

groff 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 groff; see the file COPYING.  If not, write to the Free Software
Foundation, 51 Franklin St - Fifth Floor, Boston, MA 02110-1301, USA.

GNU troff は、コードの貢献者が著作権を FSF に譲渡しているため、”Copyright”行は FSF になる。その下にコードの著作者が書かれている。この個人名の表記は著作者人格権を尊重したもので、著作権者としての効力はない。その下には GPL が適用されることが書かれている。

次は a2ps というソフトウェアの例だ。

main.c -- main loop, and interface with user

Copyright (c) 1988-1993 Miguel Santana
Copyright (c) 1995-2000 Akim Demaille, Miguel Santana



This file is part of a2ps.

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, 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; see the file COPYING.  If not, write to
the Free Software Foundation, 59 Temple Place - Suite 330,
Boston, MA 02111-1307, USA.

GNU troff と非常に似ているが、copyright 行が FSF になっておらず、コードの改変者が列挙されている。さらに改変した場合は、新たな著作権者として追加して問題はない。ここに列挙されているひとは、全員がライセンスを GPL とすることに同意したことになる。GPL でないライセンスで配布したければ、全員が同意してライセンスを変更するという手続きが必要だ。

まとめ

元記事は一体なにを参考に書いているのだろう? 「パブリックドメインを可能にしようとしたGPL」なんていう表現は、初めて目にしたように思う。どこかにそういう主張があるなら知りたい。

牧野武文著:ハッカーの系譜(9)オープンソースの巨人たち (11) 「伽藍とバザール」とネットスケープの決断より引用:

このようなオープンソースのライセンスには、BSDライセンスとGNUライセンスなどがある。BSDライセンスは、無保証であることと著作権表示を明記してあれば自由に再配布、改変できるというものだ。しかし、このライセンスだと、コードを改変した人の権利がまったく守られない。たとえば、コミュニケーターのバグを発見した人が、その部分のソースコードを修正してくれたとしても、再配布をするときは、ネットスケープ社に著作権がある旨を明記しなければならない。バグフィックスに貢献してくれた人の功績は、形式的には、すべて原著作者であるネットスケープ社のものになってしまう。二次配布をするときは、あくまでもネットスケープの著作物としておこなう。

では、GNUライセンス(GPL=GNU一般公衆利用許諾)ではどうか。GPLでは、原著作者という考え方がなくなり、パブリックドメインに近くなる。ただし、GPLが違うのは、再配布をするときも自動的にGPLにライセンスされるということだ。つまり、著作権をGPLという架空の存在に帰属させ、なおかつGPLから外れることを禁止することで、心ない人が勝手に権利主張することができないようにしている。

いまだにライセンスと著作権を混同して考えているひとは多いのだろうか。歴史ものは読んでいて面白いのだけれど、この連載には他にもいくつか気になる点がみつかる。ひとつはこれ。反論を書こうとしたものの、記事の指摘をすることを露骨に嫌がる職業ライターの方もいるようなので記録だけしておく。

オープンソースプロジェクトに参加したいな、と思った時、まず最初に問題だと感じるのは英語だと思う。構成員が日本人だけで、日本人に向けてのみ出しているそソフトウェアでない限り、プロジェクトの共通語はふつう英語だ。植山さんの記事には英語で物事を進めることの利点が体験談とともに書かれている。他の記事にも、オープンソースプロジェクトで上手いことやっていくためのひとつとして英語の話が出てくる。一方、英語のせいで参加したくても二の足を踏んでしまう、というのもよく聞く話だ。結論から言ってしまうと、やっぱり読み書きだけでも習得しないと話に入っていくのは難しい。ソフトウェア開発者の多くは多様性に対して寛容なので、英語が不得意という理由で拒絶されることはないだろう。ただ、特別な配慮もしてくれない。

しかし英語の前に、プロジェクトとの距離のとりかたを学ぶべきだと思う。いままでわたしが見てきたり、自分自身がやって良くなかったなと思う例を思い出しながら書いてみることにする。

プロジェクトへ参加したいひとへ

まず、メーリングリストや掲示板など、プロジェクトの開発者がコミュニケーションに使っているところに行こう。いまどきのプロジェクトはメーリングリストを使っていないかも知れないが、便宜上、以降では情報交換の場の代表としてメーリングリストという言葉を使うことにする。あなたはプロジェクトがどういうソフトウェアを作っているのかは、すでに知っているのだと思う。最初に学ぶべきは、「誰が」「何を」やっているのかということだ。プロジェクトは「ひと」で動いているので、自分の興味ある内容を実際にやっている開発者が誰なのかを知らないと、先に進めない。メーリングリストに参加して一カ月もすれば、「ああこの名前は良く見るなあ」というひとが見つかるはずだ。逆に、一カ月経過してもメールが全然流れなかったら、そのプロジェクトは活発な活動がなくなっているか、あなたが間違ったところを見ている。

誰が何をやっているのかが何となくわかったら、自分が何をしたいのかをもう一度考えよう。利用者として情報が欲しいだけなら、参加しているメーリングリストなどで時おり自分の質問を投稿したり、他人の質問にアドバイスしたりする活動が良い。「こう操作すると動作がおかしいんだけど」「こういう機能があると便利だと思うんだけど、何とかならないかな」というような投稿も、プロジェクトにとってプラスであることが多い。開発者が興味がないことは返事がないだろうし、興味があれば反応が来るだろう。返事が来るかどうか気になると思うけれど、あまり過度な期待はしないこと。あなたがプロジェクトに責任を持たなくて良いように、開発者もあなたの発言にいちいち反応しなければならない理由はないからだ。

利用者の立場からプロジェクトに参加しているひとの多くは、そういったコミュニケーションを続けている。コードも何も書いてないけれど、これは立派に参加していると言って良い。「何かを貢献しなきゃ」という意識は、あまり強く持たないことをお勧めする。気合いを入れれば入れるほど、それを受け入れてくれなかった時に落ち込むことになるからだ。自分がつくり出すものは、まず自分が有用だと思えるものでないと続かない。

利用者と開発者の区別は、コードを書くかどうかではなく、「自身の成果をプロジェクトに提供するひとかどうか」ということである。プロジェクトに参加する自らの姿勢の問題であり、実際に提供したものが受け入れられるかどうかは関係ない。プロジェクト側が区別したり、誰かが認めたりするものではないので注意していただきたい。

利用者として参加するときに求められること

端的には、コミュニケーション(メーリングリストを読むとか、掲示板を読むということ)に時間がかけられるかどうかが問われる。もちろん全部読む必要はなく、自分の興味のある部分だけで良い。そのような場所でのやりとりはプロジェクトにおける一次情報なので、まとまっていないしノイズも多い。リリースのときに情報が得られれば十分で、日常的にメールを読むなんて面倒だな、と思うひとは、参加しないほうが良いだろう。ここでひとつの判断基準になるのは、あなたがプロジェクトに興味があるのか、プロジェクトの生み出すソフトウェアに興味があるのか、という点だ。あなたが食事をしたいと思ってレストランに入った時、厨房でどうやって料理しているのか知りたいだろうか? プロジェクトのメーリングリストを読むというのは、そういう行為だ。最終的にできあがるソフトウェアだけに興味があるなら、出来上がるまでの過程を眺めてもしょうがない。

「料理をしているところを眺める気はないけど、食事はしたいからレストランは残ってて欲しい」という立場なら、プロジェクトに対して金銭的な支援ができないか調べよう。寄付を受け付けているところは多いし、もしそういう案内がなければ、開発者のいるところで率直に「寄付をしたいけどどうすれば良いの?」と聞いてみるのもひとつの方法だ。お金の他に、PCなどのハードウェアを寄贈するという方法もある。開発者の輪の中に入らず、もっと遠くからプロジェクトに貢献することは十分可能だ。

一方、「厨房で行なわれる料理を眺めていたい」というタイプのひとは、プロジェクトに興味があるひとだ。利用者の立場でプロジェクトを眺めていれば、説明文書やウェブページの整備という作業がプロジェクトの中ではなかなか進んでいないことに気が付くはずだ。ソフトウェア開発が中心のプロジェクトは、そういった部分に時間を多く割くことができない。「ここ情報が古いので、僕が更新しますよ」と言えば、歓迎してくれるだろう。同じ線上にある作業は、文書の翻訳だ。ただし、この手の作業は終りがなく、モチベーションを維持するのは難しいことをあらかじめ覚悟しておこう。

あなたは自分の作ったものを提出したり、自分の意見を表明することを躊躇するかも知れない。「こんな内容で良いのだろうか」「くだらない、品質が低いと言われたらショックだし、恥ずかしい」、そう思うかも知れない。こういう感情は、あなた以外もみんな持っている。自分のつくったものに対する意見を受け取るのは、誰だってある種の不安を感じることだ。自分で生み出したものには愛着があったり責任を感じるので、それを否定されることは自分や自分の能力が否定されるようで辛い。意識的に考えていなくても、共同社会で「何か独自のものを生み出して外に出す」という作業は辛いのだ。小説家、画家、音楽家、スポーツ選手や芸人といった職業は、常にこの種のストレスに晒されている。程度の差はあっても、人間であれば誰もが感じるストレスだ。どんなに優秀で経験を積んだプログラマであっても変わらない。

この辛さを乗り越えるには、価値基準を自分に置くことと、ひとつのものに拘らないことの 2 点が重要になる。自分のつくったものは、まず自分でその価値を認めてあげなければならない。誰に何と言われても、自分で役に立つと思うものをつくったなら、批判があっても深刻に受け入れる必要はない。また、「どうしてもこれが受け入れられないと嫌だ」というような拘りは、ある程度の範囲までにとどめよう。自分が貢献できる部分は貢献し、受け入れてもらえなかった部分は自分の中にとどめておけば良い。

自分自身が興味のあることをやって、その成果を提供しよう。「これをやればみんなの役に立てる」という発想で何かに取り組むのは、相当の覚悟がない限りは止めよう。続けるのが辛くなり、やがて取り組みに対する批判に耐えられなくなる時が来るからだ。仕事でもないのに、あなたが楽しめないことを続ける理由はない。逆に、あなたが極端に苦しむことなく文書などを成果として継続的に提供できるのであれば、開発者として貢献できる可能性が高い。

簡単にまとめると次のようになる。プロジェクトを理解し、ゴールを共有して参加するのは、開発者でなくても可能だ。

  • プロジェクトの開発者がコミュニケーションをとっている場に参加すること。メーリングリスト、IRC、電子掲示板などオンラインの情報交換に加えて、カンファレンスなどのイベントもある。
  • プロジェクトとの距離を意識すること。アナウンス用のメーリングリストだけを購読するのか、開発者の意見が飛び交うメーリングリストを読むのか、さらに自分でそこに積極的に投稿するのか、段階はさまざまある。あなたが興味があるのは何だろうか?
  • 質問する、質問に答える、意見するというアクションは、利用者として貢献できる方法のひとつだ。メーリングリストを読むのは面倒だけど貢献したいなら、寄付する、宣伝する(たとえばそのソフトウェアのインストール記事をあなたの blogに書く)という手もある。
  • 貢献を義務だと思う必要はない。自分のペースでやれることをやれば良い。

プロジェクトが求める人材とは

当然ながら、プロジェクトを動かしているのは「ひと」だ。あなたが開発者として参加したいと思った時にプロジェクトがあなたを歓迎するかどうかは、必ずしも「何らかの能力が秀でているかどうか」という基準で量られるわけではない。IEEE Spectrum 1999年10月号に、“How to be a Star Engineer”(PDF, 英語)という記事が載っている。IEEE はアイ・トリプル・イー(これが現在の正式な日本語表記である)と呼ばれる、米国に本拠地を置く電気工学・電子工学を中心とする技術者の組織である。Spectrumはその機関誌であり、さまざまな話題が載っている。その記事はカーネギーメロン大学のR.E.Kelleyという経営管理論の教授による「優れた技術者になるために必要なことは何だろうか」ということを書いたエッセイだ。日本の電子情報通信学会が 2001 年に翻訳版を「優秀なエンジニアになる方法」(PDF, 日本語)という題名で学会誌に載せている。面白い文章なので、一読をお勧めする。長いな、と思うひとは第1節と第2節までを読めば十分だ。

プロジェクトが求める人材は、第2節に出てくるヘンリとライの両方だ。プロジェクトは技術的な能力が高いひとが欲しい。しかしそれ以上に、みんなと仲良く同じゴールを目指してくれるひとを欲している。たとえばあなたが積極的にメーリングリストで初心者の相手をしてくれることは、プロジェクトにとって大きなプラスになる。プロジェクトやプロジェクトを中心とする開発者のコミュニティに利用者として参加することは、決して価値の低いことではない。あなた自身も、活動を通じてプロジェクトやソフトウェアについて学び、将来的により多様な貢献ができるかも知れない。

利用者としての関わりから、その後に開発者になった人もたくさんいる。すでに書いたとおり利用者と開発者は姿勢の問題なので、利用者が下で開発者が上、というような序列があるわけではない(一方で、このように信じているひとは多いは事実だが)。あなたがソフトウェア開発に興味があるのであれば、プロジェクトに利用者として参加しながら勉強するのもひとつの手だ。わたしは著名なプログラマがどういうコードを書いているのか、共同作業でどう意見交換をして物事を進めているのかにとても興味があった(今もある)。なので、特定の何人かの開発者の変更には目をとおして、そこからいろいろなテクニックを学んだ。本業では莫大な報酬を約束されるような優秀な開発者が書いているコードを無料で読めるというのは、素晴らしいことだと思う。

開発者として参加するときの距離感

コードを書いて貢献したいと思うひとは、利用者としてメーリングリストなどの情報交換の場に参加した後に、すでにいる開発者とコミュニケーションをとろう。コードを書いたら、開発者に伝えれば良い。メールで送るのが良いのか、Git の pull request にするのが良いのか、bugzilla にパッチを入れて PR を投稿すれば良いのか、プロジェクトによって風土が異なる部分が大きい。開発ツールの使い方がわからなければ、まずその勉強が必要だ。これは文書の貢献でも同じである。

自分の成果を提出する時には、「誰に伝えるのか」を意識すること。プロジェクトが小さければ主要な開発者に直接伝えるだけで十分だが、プロジェクトが大きい場合は、自分の変更に興味を持ったり、内容を理解してくれる開発者を絞りこんだほうが良い。メーリングリストを読んでいれば、誰が何に取り組んでいるのか学べるはずだ。それで足りなければ、そのプロジェクトが使っているバージョン管理システムやメーリングリストのアーカイブを使って、過去の変更者を調べよう。どうしてもわからなければ、メールなどで誰が詳しいのか聞いてしまうのが早いかも知れない。

誰が担当なのかを勉強せずに巨大な成果物を送りつけても、無視されるか受け入れるのに長い時間がかかることが多い。プロジェクトにはあなたのコードを受け入れる義務はないし、役所に書類を提出するのと違って、提出されたものが必ず処理されると期待できるものでもない。

どうすれば良いのだろうか。オープンソースプロジェクトは、多くの人が思うよりもっと人間的なものだ。かなり大きな組織であっても、重要な意思決定が飲み会の場で数人が話をして決まるようなことはよくあるし、いつも万人に公平な態度をとるわけではない。同じ目的を達成できるコードが 2 つあったとして、ひとつはいつも貢献している昔からいる開発者が作った少々品質の劣るコード、もうひとつは、どこの誰とも分からないひとから送られてきた品質の良いコードだったとしたら、どっちが採用されるだろうか。品質が良いコードがあるならそっちなのでは、と思うかも知れないが、前者が採用されることも良くある。

ここで重要なのは、将来的にこのコードの面倒を誰が見るのかという点である。プロジェクトの典型的な思考は次のようなものだ。昔からいる開発者であれば、おそらくすぐにはいなくならないだろう。どういう性格なのかも良く分かっている。コードの品質の悪いところは改善可能だ。まずいところを本人に伝えて、改善してから採用すれば良い。

一方、品質の良い、どこかから送られてきたコードは、開発したひととコミュニケーションがとれるかどうかで将来が決まる。送った開発者とメールのやりとりができるなら、「開発者として参加しませんか」と誘われるかも知れない。しかし、開発者と連絡がとれなかったり、最初は連絡がとれてもそのうち消息が分からなくなってしまうような印象があったとしたら、採用を躊躇してしまう。もちろん、すでにいる開発者が、その送られてきたコードの面倒をみることが無理なく可能だと判断すれば、採用するかも知れない。

この背後にあるのは、信頼できるかどうかという評価尺度である。あたりまえのことだが、初対面のひとより、いつもいるひとのほうが仲間意識が強い。共同体においてこれはとても重要だ。いつもいてプロジェクトのゴールを共有しているという姿勢を見せ続けるひとは信頼される。たとえば、メーリングリストでの質問に定期的に的確な答えを返しているひとがいたら、開発者の多くは名前を覚えるだろう。そしてそのひとが「コードを書いたんだけど」と成果物を出してきたら、おそらくみんな協力的になる。いつも初心者の相手という大変な作業をし続けている姿を見ているので、どうにか協力してあげたいな、という気持ちになるからだ。この気持ちの源泉は同じコミュニティにいるという仲間意識であり、プロジェクトを良くしていこうというゴールの共有である。そのひとが書くコードは拙く、採用されないかも知れない。しかし、もっと能力の高い開発者からのアドバイスは確実にもらえる。コードを書いた本人のコーディング能力も、活動を続けていればいずれ成長するだろう。

もちろん、当然ながら品質の良いコードを書けるひとは最初から信頼される。しかし、それと同じくらい「こいつは目的を共有した仲間なんだろうか」という村社会的意識があるのだ。つまり、開発者として参加するときのプロジェクトとの距離感というのは、開発者間のつきあいの深さ、目的共有の強さである。初対面という印象を拭える程度のつきあいがなければ、ふつうはまず開発者として認めてもらえない。社の主要事業も知らずに就職面接を受ける学生のようなものである。そして、開発者として参加してたくさんコードを書いたとしても、開発者とのコミュニケーションが欠けていると距離は縮まらない。

この距離のとり方は、自分の匙加減だ。わたしはプロジェクトとの距離を縮めて運営や意思決定というところにまで自分の責任範囲を広げたが、距離を保って、ある特定の部分のコードの開発を担当するという参加の方法もある。開発者としての参加の場合、利用者の場合と異なり「何を貢献するのか」がかなり明確なので、自分の負担にならない距離を保って活動を続けるのは難しくない。距離を縮めて損をすることはないので、コミュニケーションにはなるべく参加することをお勧めする。やりとりすることに嫌気がさしてきたら、たぶんあなたはもうそのプロジェクトへの興味を失っているのだと思う。無理して続ける必要はない。

あなたが、あなたにしかできない突出した技術を持つ優秀なプログラマなら話は別だ。そういった価値を持つ人材なら、プロジェクトは金を払ってでも、あなたをプロジェクトに留めようとするだろう。そうでないなら、開発者コミュニティに参加するということは、最終的に他の開発者とのつきあいを深めていくことだということを理解しよう。開発者コミュニティにはさまざまな形で参加しているひとがいて、コードだけで物事が決まるほど単純ではない。コードだけ提供してなるべく他の開発者に近付かずに関わり続けることも不可能ではないが、距離をとりたいならプロジェクトに参加する意味はないと思う。

「パッチを提供したいけど、後々の面倒はみたくないな」というひとは、面倒をみてくれる開発者を探して頼もう。バグ報告のシステムに登録しただけでは見てくれないかも知れないので、「ひと」とコミュニケーションすることが肝要だ。それさえ覚えておけば、自身が開発者としてプロジェクトに関わり続けなくても、コードで貢献することは可能だ。

プロジェクト運営の教訓

開発者コミュニティで開発者とのつきあいが長くなってきたら、プロジェクト運営の仕事をする機会が出てくる。リリースの作成作業であったり、セキュリティ問題への対応であったりと、特定領域で責任者として振舞う開発者である。形態はプロジェクトによってさまざまだ。FreeBSDは最初は複数の創設者が決定権を持っていて、次に意思決定機関であるコアチームをつくって合議制に移行した。コアチームのメンバは 9 名、任期は2年で、開発者の投票によって決まる。コアチームはプロジェクトの責任者にあたる開発者を指名し、個々の領域に責任を持つチームの編成を指示する。主なものにリリースエンジニアリングチーム、セキュリティオフィサ、ドキュメンテーションエンジニアリングチーム、portマネージャチームなどがある。

プロジェクトの意思決定の立場まで行くと、もうちょっとややこしい話が見えてくる。プロジェクト運営にトラブルはつきものだ。開発者間のケンカ、ルールを守らない開発者、メーリングリストなどで暴れる利用者などは分かりやすい例だが、高い能力を持つ開発者間でもプロジェクトの機能不全に陥るような問題が発生することがある。それらに対処しなければならない。

オープンソースプロジェクトの運営には、過去から蓄積された教訓がたくさんある。比較的最近の話をひとつとりあげると、2008年のGoogle I/O では、“How to Protect Your Open Source Project from Poisonous People”(動画)という有名な講演があった。これは Subversion プロジェクトの開発者であるBrian FitzpatrickとBen Collins-Sussman(もちろん両者とも googler だ)による体験談とオープンソースプロジェクト運営者への教訓話だ。動画は長く英語なので乱暴に要約すると、「プロジェクトに関わろうとするひとのうち、ゴールを理解・共有しないひと、自分で調べようとしないひと、現状の批判ばかりするひと、他人の意見を尊重しないひと、自分でできる内容を自分でやらないひとはプロジェクトに有害なひとなので、やんわり退場願おう」という話である。

この要素の中で一番重要なものをひとつあげるとしたら、すでに繰り返し強調しているように「プロジェクトの目的の共有」である。「BSDは亡びていいのに」という発言をするひとは、ゴールを共有していないので相手にされない(おそらく本人も相手をしてほしいとは考えていないと思うが)。OSSコミュニティの継続性で指摘した「利用者目線の意見に興味がない」という態度には、そうしないとプロジェクトにとって危険だということが背景にある。「BSDは存続すべきか」という議論はプロジェクトを徒に消耗させ、得られるものはほとんどない。

人間は、自分の興味のないものほど多様性の価値を低く見積もる傾向がある。わたしはアイドルグループにほとんど興味がないので、50人以上いるAKB48はもっと人数が少なくてもいいんじゃないかと思ったりする。近くの大型ショッピングセンターに行くと自転車売場があるが、たくさん種類が並んでいるのを見るとどうせ似たようなデザインなのだから数種類でいいのでは、とも思ったりする。一方、興味を持っているものにはこだわる。わたしは自分の使うマシンのキーボードに5576-A01を使っている。キーボードなんてどれも同じなんじゃないの、とは思わないし、一種類に統一されて欲しいとも思わない。Mac の市場シェアは Windows よりもだいぶ小さいけれど、わたしは Mac のほうが好みなので使い続けている。

化粧品、時計、服のブランド、なんでも良い。あなたの日常生活でどれでも良いと思っているものと、こだわっているものの違いは何だろうか。そしてあなたは、自分が「どうでも良い」と思っていることに独自の考えを持っているひとと議論ができるだろうか。あなたは他人からこだわりを力説されても、心変わりしたりはしないだろう。ボランティア開発者が行なっているソフトウェア開発プロジェクトは、そのひとが興味があるからやっているのである。興味を持たないひととの価値観の議論は、そもそも噛み合わない。

合わないひととは関わらない、という簡単な話である。「FreeBSDに関係しているひとはへんなひとばかりだ」「そんな考えだからFreeBSDはダメになったんだ」と思うのも、発言するのも自由だと思う。ただ、わたしも含めてプロジェクトの人間はそういう議論はしないし、発言者を説得することもない。価値観は多様性があるものだし、プロジェクトが求めているのは目的を共有してくれるひとだけだ。プロジェクトを守るためには、そういうひとたちの相手はしないのが正しい。プロジェクトに近付いてきて実害が出るようなことがあれば、露骨ではないにしろ何らかの形で排除しようとするだろう。FreeBSDプロジェクトが目指しているのは、みんなを説得してFreeBSDを使わせることではなく、FreeBSDの品質を向上させることだ。そして品質の向上という目的を共有する開発者・利用者の集団を醸成することである。

この講演では、他にも健全な開発者コミュニティを維持するためにプロジェクトが気をつけるべき内容が説明されている。次のようなものだ。

  • 完璧を求める開発者、自分の作業領域から他人を排除しようとする開発者は、プロジェクトの開発を麻痺させるので注意しよう。

  • 目標を明確に、議論は論点を明確にする。同じ議論は繰り返さず、すべてのメールに逐一返信するような会話は避ける。

  • 議論や意思決定の記録をとり、共有する。

  • 開発者間でコミュニケーションを密にとる。コードの変更は互いにレビューする。

  • 開発の手順を決め、文書化する

  • 投票は最後の手段

このような教訓を実践しなければならない立場になることは少ないと思うが、開発者として大きなプロジェクトに関わるときには、多かれ少なかれ開発者同士の意見の衝突を経験することになるだろう。まともなプロジェクト運営者であれば、上述のような開発者コミュニティの外から来る攻撃からプロジェクトを守る努力をしてくれるはずだ。しかし、内部の開発者同士の意見の衝突は自分で何とかしなければならない。そのときに相談できる相手は、やっぱり同じプロジェクトの開発者だ。実生活にいる友人と同じである。普段から仲良くしている相手なら、トラブルの相談も気軽にできるだろう。コードだけのやりとりでは貴重な人脈形成の機会を失ってしまうので、とてももったいない。

仲良きことは…

オープンソースプロジェクトのメーリングリストを眺めていると、「なんでみんなギスギスしてるんだろう」と思うひともいるかも知れない。拙い質問や品質の低いコードにはマサカリが飛びまくる。遠慮がなく口が悪い開発者がいるプロジェクトも、それなりにある。たとえば2013年にLKMLであった Sarah Sharp による Linus の罵詈雑言批判の一幕が江添さんのサイトにまとめられている。彼女は Linux カーネルの xHCI ドライバのメンテナだったが、その後Linuxカーネルの開発者コミュニティを去った。

ここ数年、さまざまなプロジェクトでコミュニティ内のハラスメントや感情的な衝突が問題になり、行動規範 (Code of Conduct) を定めたところが多い。FreeBSDのCoCもウェブサイトに載せてある。これだけで万事解決とは言わないが、コミュニティの構成員が不快な思いをしないですむような努力は継続的に行なわれている。感情的な衝突が発生したら、双方の意見を聞いたり電話で直接本人と話すこともよくある。技術的正当性をふりかざして正論をぶつけるだけでは、ひとのコミュニティはうまく回らない。プロジェクトの外からはメールでのやりとりしか見えないことが多いが、実際のところ運営側でやっていることはもうちょっと複雑だ。Linuxカーネル開発者のコミュニティは大きく、企業ユーザの政治力学が働くなどオープンソースプロジェクトの中では少々特殊な部類に入るので、残念ながら罵詈雑言が消えることはないのかも知れない。

オープンソースプロジェクトに興味のあるひとは、自分の居心地のよいプロジェクトを探してみよう。大学のサークル活動のようなものだと思っていて良い。そこでの活動は、他では得難い経験や知的な刺激が得られる貴重なものになるはずだが、何の集まりなのかを理解せずに入ってもうまく行かない。ソフトウェアそのものだけではなく、プロジェクトの目的を共有するよう心がけることを強くお勧めする。

昨年の11/29に勉強会で使ったZFSの性能測定とチューニングの資料のリンクを、Twitterに投げただけになっていたので、参照しやすいようにここにも貼っておく。しゃべり資料は後から読んでもわかりにくいので、後ほどもうちょっと整理して文書化する予定。

ZFSはスケールするファイルシステムだけれど、速いファイルシステムではないので性能の話を見聞きする時には疑ってかかるのが吉。

このあたりで質問した内容。修正がコミットされて解決した。

Userland DTrace のコンパイル時の処理

Userland DTrace では、USDT provider(ユーザレベル静的定義トレースプロバイダ)を定義する際に、プローブをC言語のヘッダファイルとして生成する機能がある。プローブ定義はD言語で次のような書式を使って記述する。

provider sample {
    probe first__read(int, char *);
    probe debuglog(char *);
};

これをfoo.dというファイルに置いてdtrace -h -s foo.dとすると、次のようなマクロ定義が含まれるfoo.hというファイルができる。

#define SAMPLE_FIRST_READ(arg0, arg1) \
    __dtrace_sample___first__read(arg0, arg1)
#define SAMPLE_DEBUGLOG(arg0) \
    __dtrace_sample___debuglog(arg0)

プログラムではこのfoo.hをインクルードして、プローブを置きたいところにマクロを記述する。関数呼出だが、DTraceを有効にしなければnop命令に書き換えられるので、性能への影響は小さい。

これらの関数は当然ながら対象プログラムに含まれていない。そのため、対象プログラムのオブジェクトファイル(ここではsample.oとする)ができた後に、次のように-Gオプションをつけてdtraceコマンドを実行することで生成する。

% dtrace -G -o foo.o -s foo.d sample.o

foo.oには、foo.dで定義されたシンボルの実体が入っている。したがって、最後にすべてをリンクすればUserland DTraceに対応したバイナリができあがる。

シンボル置換

foo.dで定義したfirst_readというプローブは名前に二重下線が含まれているが、実際のプローブ名はfirst-readのようにハイフンに置き換えられる。この処理は、dtrace -Gコマンドがオブジェクトファイルの中のシンボルテーブルを直接書き換えて実現している。

% nm sample.o
            U __dtrace_sample___first__read
            U __stack_chk_fail
            U __stack_chk_guard
0000000000000000    T main
            U read

% dtrace -C -x nolibs -G -o foo.o -s foo.d sample.o

% nm sample.o
            U __dtrace_sample___first-read
            U __stack_chk_fail
            U __stack_chk_guard
0000000000000000    T main
            U ead

プログラムのオブジェクトファイルにある最初の__dtrace_sample__first__readというシンボルが、dtrace -Gコマンドの後に置き換わっているのが分かる。

DTraceのプローブを定義しながら開発を進めていたところ、時々プログラムのリンク時にエラーが出るようになった。先ほどの実行例を良くみると、置き換え後の read のシンボルの頭が 1 文字欠けているのが分かると思う。プローブの名前にも read があるので、これは置き換えの処理がおかしいのでは、というところまであたりをつけたところで、冒頭の質問メールを出してみた。

するとIllumos-gate のバグ報告6653で報告されているものと同一の症状のようだ。原因は次のとおりである。

  • シンボルテーブルはテーブルのオフセットが記録されているが、個々のシンボルがテーブル内でユニークな領域を占める保証はない。つまり"first-read" というシンボルと "read" というシンボルは、同じ部分文字列を含む可能性がある。

ELFのシンボルテーブルを生成するツールによっては文字列の重複部分を最適化するようだ。__ を長さの違う - に置換するため、オフセットがずれてreadの頭一文字が欠けてしまう。レポートには binutils の gas 2.26 以降で発生するとある。dtrace -Gの前にobjcopyを実行してsample.oをコピーすると、症状が出なかった。

そもそもシンボルテーブルを書き換えるのではなくて、シンボルのルックアップ時に__-に置き換えるべきなのでは、というもっともな指摘もあり、FreeBSDにはこういう修正がcommitされた。FreeBSD 10 系はそもそも問題が発生するシンボルテーブルを生成しないので影響はない。

Userland DTraceは使っているひとが少ないのか、不具合がまだまだあるようだ。

シェルスクリプトの記事の動作チェックをしていた時に、BSDのuniq(1)の出力がPOSIXに準拠していないことに気がついた。

% jot -b 1 100 | uniq -c
 100 1

SUSv3のOUTPUT FILESの節には、次のように書いてある。

If the -c option is specified, the output file shall be empty or each line shall be of the form:

"%d %s", <number of duplicates>, <line>

BSDのコードは昔から"%4d %s"だ。この書式指定は少なくとも1980年まで遡ることができる。ずっと変わっていない。確認してみたところ Solarisの実装"%4d" だ。SCO UNIX はソースが確認できないが動作を調べると"%4d"っぽい。

古いUNIXのソースを遡ってみると4th Editionまで遡ることができた。1975年5月のソースだが、これも"%4d" だ。

一方GNU coreutils (確認したバージョンはuniq 8.4)は次のように表示する。

    100 1

ソースファイルを確認する限りでは"%7" PRIuMAXのようだ。Gitの履歴からたどれるもっとも古い1992年11月のバージョンでは"%7d"になっている。ということは、POSIXを信じてuniq -c | cut -f 1 -d " "とかすると痛い目をみる、ということだろうか。

uniq -cの出力が%dになっている環境を知っている方、情報求む。