2010年8月31日火曜日

初心者向けガイド: Linux カーネルのハック

( The newbie's guide to hacking the Linux kernelがとても素晴らしい記事だったので翻訳しました。 )

( 2010-09-28 LinuxCon Japan にて、口頭ですが、Greg K-H から翻訳・公開の許可をもらいました。Thanks! )

カーネルをハックするのに、コンピュータサイエンスの博士号や下積み期間は必要ないよ。もちろんそういうことは役には立つ。でもLinux開発の重要な点は、誰にでもオープンだってことだ。とにかく取り組むことが必要。あなたは毎日何らかの形でLinuxカーネルを使ってるでしょ? どんな小さいことでもいいから、カーネル開発を少しでも手伝えば、それを誇りに思えるはずだ。


多くの貢献をしていて (そして Linux Format の読者でもある) Greg Kroah-Hartman に聞いてみた。初心者が Linux カーネルのパッチを作るにあたって気をつけることは何かって。彼が語ったことは以下のとおりだ。

(PS: 以前の記事 How the Linux kernel works は、このチュートリアルの導入によいかもしれない)

あなたのPCでカーネルが問題なく動いていて、直したいと思える点がまるで無かったらどうしよう? がっかりすることはない。Linux カーネル開発者はどんなヘルプでも必要としてるし、クリーンアップが必要なコードはソースツリーに山ほど残ってる。例えば、drivers/staging/ ツリーのコードなんてどうだろう? これは通常の Linux カーネルコーディング規約を満たしていないコードたちだ。Linux カーネルツリーのメイン部分にマージするには、誰かがコードをきれいにしなくちゃいけない。


drivers/stagingにあるドライバには、コードをカーネルツリーの適切な場所に移動する前に必要な作業が、ToDoという形でまとめられている。ほとんどのドライバの TODO ファイルにはこんな記述がある。

- fix checkpatch.pl issues

これが何で、どうすれば良いのかを見てみよう。

コードベースが大きいプロジェクトには、コーディングスタイルがなくちゃいけない。コーディングスタイルがあることでたくさんの開発者が協力できて、プロジェクトが発展することに繋がる。Linuxカーネル開発者の目標は、他の開発者にコードの問題を見つけてもらうこと、そして、全てのコードのフォーマットを揃えることで、誰もがバグを見つけたり、修正したり、バグを報告しやすくしている。カーネルコードの一行一行が少なくとも二人の開発者にレビューされ、受理される。だから、共通のコーディングスタイル規約はとても重要だ。

Linuxカーネルのコーディングスタイルは、カーネルソースツリーの Documentation/CodingStyle にまとまっている。ただし重要なことは、このスタイルで統一されているということであって、これが他のスタイルより優れているってことじゃない。開発者がコーディングスタイルの問題をすぐに見つけられるように、scripts/checkpatch.pl というスクリプトが開発された。このスクリプトは問題を簡単に見つけてくれるし、開発者は自分が変更した部分に対してこのスクリプトを走らせないといけない。そうしないと、後々レビューワがスタイルの問題を指摘する、という無駄な作業が発生してしまう。


drivers/staging/ ディレクトリにあるドライバには、大抵コーディングスタイルの問題がある。というのは、Linuxカーネルガイドラインに詳しくない人たちによって書かれたからだ。まずやらなきゃいけないのは、正しいルールに沿ってコードを直すことだ。ここでカーネルハック初心者の出番だ。checkpatch.plを走らせれば修正すべき点が山ほどでてくる。



Gitを使ってコードを操る


Git の最高のチュートリアルは Git に付いてくる。Gitをインストールして、このコマンドを入力すればチュートリアルを読むことができる:

$ man gittutorial 

お気に入りのパッケージマネージャを使って Git をさくっとインストールして、Linuxカーネルのメインリポジトリをクローンしてみよう:

$ mkdir ~/linux
$ cd ~/linux
$ git clone git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux-2.6.git

このコマンドを入力すると、linux/ ディレクトリの中に linux-2.6 ディレクトリが作られる。ここからの作業はこの中で行うことになるので、まずはこの中に移動しよう:

$ cd ~/linux/linux-2.6

これで生のソースコードが手に入った。次はビルドとインストールだけど、これは大仕事だし、この記事の範疇を超える。ビルドとインストールについて一冊にまとまっている本がある。「Linux Kernel in a Nutshell」だ。ここで無料で読むこともできる: www.kroah.com/lkn.


Git を使って作業するときに気をつけないといけないことは、あなたの作業を Linus と同じブランチ ('master' と呼ばれている) で行わない、ということだろう。あなたはあなた自身のブランチを作って、そこで作業しなくちゃいけない。こうすることで、あなたによる変更が Linus のブランチに問題なくコミットできるようになる。'tutorial' という名前のブランチを作ってチェックアウトするにはこうすればいい:


$ git branch tutorial
$ git checkout tutorial

これだけだ。これであなたのカーネルリポジトリの 'tutorial' ブランチに移動したことになる。次のコマンドで確認してみよう:

$ git branch
   master
 * tutorial

'tutorial' の前についている * が、あなたが正しいブランチにいることを表している。これでカーネルコードを変更できるよ!

もし Git について詳しく知りたければ、このチュートリアル version control with Git を読んでみて。



特殊なルール

カーネルガイドラインの共通ルールを見てみよう。


スペース

みんなが守らないといけない最初のルールは、コードのインデントにスペースじゃなくてタブを使うことだ。それから、タブはスペース8個分じゃないといけない。スペース8個分のタブにした上で、一行が80文字より長くなってはいけない。(訳注: タブ1つを8文字文として数える。)


この80文字制限については、数えきれないほどの開発者が文句を言っているし、最近はこの制限を破ることが許されるところもいくつかある。もし 80文字ルールに従うためだけにおかしな行分割をしなきゃいけないと感じたら、そもそもそんなことが起こらないようにロジックを見なおしてリファクタリングしたほうが良い。


80文字という制約を強制することで、ロジックが小さく、理解しやすい大きさに分割されるようになる。そうするとレビューしたりコードを追ったりするのも簡単だ。そんなわけで、80文字ルールという気違いじみた制約にもそれなりの意味はあるんだ。



ブレース


カーネルにおけるブレースの使い方のルールはちょっと厄介だ。開きカッコは、以下に示す一つの例外を除いて、それが使われるステートメントと同じ行に置かなくてはいけない。閉じカッコは元々のインデントと同じ深さに戻さなくてはいけない。次の例を見てみよう:


if (error != -ENODEV) {
        foo();
        bar();
}

ifステートメントにelseステートメントを追加する場合は、次の例のように、elseを閉じカッコと同じ行に置くこと:


if (error != -ENODEV) {
        foo();
        bar();
} else {
        report_error();
        goto exit;
}

ステートメントが一つでブレースが必要ない場合は、不要なブレースはつけないこと:


if (error != -ENODEV)
        foo();
else
        goto exit;

開きカッコに関するただ一つの例外は、関数宣言 (訳注: 正しくは「関数定義」) に関する使い方だ。この場合は開きカッコは新しい行に置かれる。次の通り:


int function(int *baz)
{
        do_something(baz);
        return 0;
}


checkpatch.pl

スペースとブレースについての簡単なルールが分かったところで、checkpatch.pl を適当なスクリプトに対して走らせてみて、どうなるか見てみよう:

$ ./scripts/checkpatch.pl --help
Usage: checkpatch.pl [OPTION]... [FILE]...
Version: 0.30
Options:
-q, --quiet  quiet
--no-tree  run without a kernel tree
--no-signoff  do not check for 'Signed-off-by' line
--patch   treat FILE as patchfile (default)
--emacs   emacs compile window format
--terse   one line per report
-f, --file  treat FILE as regular source file
--subjective, --strict enable more subjective tests
--root=PATH  PATH to the kernel tree root
--no-summary  suppress the per-file summary
--mailback  only produce a report in case of warnings/errors
--summary-file  include the filename in summary
--debug KEY=[0|1] turn on/off debugging of KEY, where KEY is one of  'values', 'possible', 'type',
     and 'attr' (default is all off)
--test-only=WORD report only warnings/errors containing WORD literally
-h, --help, --version display this help and exit When FILE is - read standard input.

以下で使うことになる共通オプションは、--terse と --file だ。このオプションを設定すると、とても簡潔なレポートが生成してくれるし、パッチではなくて完全なファイルをチェックしてくれる。


ということで、カーネルからファイルを選んで、checkpatch.plが何を教えてくれるのか見てみよう。:


$ ./scripts/checkpatch.pl --file --terse drivers/staging/comedi/drivers/ni_labpc.c 
    drivers/staging/comedi/drivers/ni_labpc.c:4:  WARNING: line over 80 characters
...
drivers/staging/comedi/drivers/ni_labpc.c:486: WARNING: braces {} are not necessary for single
    statement blocks
...
drivers/staging/comedi/drivers/ni_labpc.c:489: WARNING: braces {} are not necessary for single
    statement blocks
...
drivers/staging/comedi/drivers/ni_labpc.c:587: WARNING: suspect code indent for conditional
    statements (8, 0)
...
drivers/staging/comedi/drivers/ni_labpc.c:743: WARNING: printk() should include KERN_ facility level
 
drivers/staging/comedi/drivers/ni_labpc.c:750: WARNING: kfree(NULL) is safe this check is probably
    not required
...
drivers/staging/comedi/drivers/ni_labpc.c:2028: WARNING: EXPORT_SYMBOL(foo); should immediately follow
    its function/variable
total: 0 errors, 76 warnings, 2028 lines checked

実際の出力から多くのワーニングを取り除いた結果を載せている。実際には76個ものワーニングがあったし、ほとんど同じものだからだ。


見ての通り、checkpatch.pl は、コードのどこが80文字制限をオーバーしているのか、どのブレースが不要なのか、そしてクリーンアップすべきその他のことを指摘してくれる。


どこを直せばいいのかわかったから、お気に入りのエディタを立ち上げて、早速ちょっと直してみよう。ブレースに関する警告なんてどうだろう。これを直すのは簡単なはずだ。オリジナルコードを見てみると、486 - 490 行はこんな感じだ:


if (irq) {
        printk(", irq %u", irq);
}
if (dma_chan) {
        printk(", dma %u", dma_chan);
}

余分なブレースを取り除くと次のようになる:


if (irq)
        printk(", irq %u", irq);
if (dma_chan)
        printk(", dma %u", dma_chan);

修正したファイルを保存してから、checkpatchをもう一度走らせてみて警告が消えたことを確認しよう:


$ ./scripts/checkpatch.pl --file --terse drivers/staging/comedi/drivers/ni_labpc.c | grep 486
$

もちろん、ファイルをビルドして、あなたが何も壊してないことも確認しないとだめだ:


$ make drivers/staging/comedi/drivers/ni_labpc.o
  CHK     include/linux/version.h
  CHK     include/generated/utsrelease.h
  CALL    scripts/checksyscalls.sh
  CC [M]  drivers/staging/comedi/drivers/ni_labpc.o

やった! これであなたの最初のカーネルコード修正は完了だ。でも、この変更をまとめて、カーネル開発者にわたして、そして彼らが修正を適用できるようにするにはどうすればいいんだろうか?



gitの更なる使い方

このファイルは Git リポジトリの中で編集されているので、あなたの変更を Git は追跡している。git statusを実行するとこれが確認できる:

$ git status
# On branch tutorial
# Changed but not updated:
#   (use "git add <file>..." to update what will be committed)
#   (use "git checkout -- <file>..." to discard changes in working directory)
#
# modified:   drivers/staging/comedi/drivers/ni_labpc.c
#
no changes added to commit (use git add and/or git commit -a).

この出力を見ると、私たちは 'tutorial' と呼ばれるブランチにいて、ni_labpc.c という一つのファイルに変更を加えたことがわかる。何を変更したのかを Git に聞けば、実際の行を確認できる:

$ git diff
diff --git a/drivers/staging/comedi/drivers/ni_labpc.c b/drivers/staging/comedi/drivers/ni_labpc.c
index dc3f398..a01e35d 100644
--- a/drivers/staging/comedi/drivers/ni_labpc.c
+++ b/drivers/staging/comedi/drivers/ni_labpc.c
@@ -483,12 +483,10 @@ int labpc_common_attach(struct comedi_device *dev, unsigned long iobase, 
        printk("comedi%d: ni_labpc: %s, io 0x%lx", dev->minor, thisboard->name,
               iobase);
-       if (irq) {
+       if (irq)
                printk(", irq %u", irq);
-       }
-       if (dma_chan) {
+       if (dma_chan)
                printk(", dma %u", dma_chan);
-       }
        printk("\n"); 
        if (iobase == 0) {

この出力は、patchツールがコード本体に適用できる形に整形されている。いくつかの行の先頭にある - や + は、どの行が削除されて、どの行が追加されたのかを示している。こういう diff 形式の出力は、すぐに自然に読めるようになる。カーネルメンテナにあなたの変更を受理してもらうためには、この形式で送らないといけない。



説明、説明、そして説明


diff 形式の出力自体は、コードがどのように変更されたのかを表している。しかしカーネルパッチを受理してもらうためには、もっと多くの情報を提供しないといけない。このメタデータは、コードの変更と同じくらい重要だ。誰がこの変更を行い、なぜこの変更が必要で、だれがこの変更をレビューしたかを示すからだ。


これが、数年前にLinuxカーネルツリーに受理されたサンプルだ:


USB: otg: Fix bug on remove path without transceiver
In the case where a gadget driver is removed while no transceiver was found at probe time, 
    a bug in otg_put_transceiver() will trigger.
Signed-off-by: Robert Jarzmik <robert.jarzmik@free.fr>
Acked-by: David Brownell <dbrownell@users.sourceforge.net>
Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
--- a/drivers/usb/otg/otg.c
+++ b/drivers/usb/otg/otg.c
@@ -43,7 +43,8 @@ EXPORT_SYMBOL(otg_get_transceiver);
  void otg_put_transceiver(struct otg_transceiver *x)
  {
-        put_device(x->dev);
+        if (x)
+                put_device(x->dev);
  }

最初の一行で、これがカーネルのどの部分の変更なのか、そしてこの変更が何なのかを非常に簡潔に説明している:


USB: otg: Fix bug on remove path without tranceiver

その次に、この変更がなぜ必要なのかをしっかり説明する文章が続く:


In the case where a gadget driver is removed while no transceiver was found at probe time,
    a bug in otg_put_transceiver() will trigger.

そのあとの何行かで、誰がこのパッチを作り、誰がレビューしたのかを表している:

Signed-off-by: Robert Jarzmik <robert.jarzmik@free.fr>
Acked-by: David Brownell <dbrownell@users.sourceforge.net>
Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>

'Signed-off-by:' という用語は、開発者に次のことを行う権利があることを示すために使われる。まず、この変更を行うことを許可されていること、そして、Linuxカーネルソースツリーに追加するために、この変更を適切なライセンスに基づいて提供することが許可されていることだ。この合意は、Developer's Certificate of Origin (開発者によるコード由来の証明) と呼ばれていて、Linuxカーネルソースツリーに含まれる Documentation/SubmittingPatches ファイルの中に詳しく書かれている。


簡単にいうと、Developer's Certificate of Origin は次のようなことを意味している:

  1. 私がこの変更を行ないました。または、
  2. 互換性のあるライセンスに基づいて提供されている既存のコードを元にしています。または、
  3. (1), (2), (3) に該当する人から私に提供され、私は変更加えていません。(訳注: 「(3)に該当する人から私に提供され」というのは、実際のコード修正者からコードを受け取った人から受け取った人から・・・、というような再帰的な構造を意味しています。)
  4. この変更は公開します。

これはとても簡単な合意によって、この変更が法的に受理可能なことを関係者全てが知ることができる。パッチが開発者やメンテナに流されていく間に、このパッチを確認した全ての開発者が 'Signed-off-by:' を追加し、最終的にLinuxカーネルソースツリーに受理される。この仕組みによって、Linuxカーネルの一行一行について、コードを書いた開発者やコードをレビューした開発者を追跡することができる。

これで、パッチの構成がわかったので、私たちのパッチを作ることができる。まず、私たちの変更をチェックインするように Git に伝えよう:


$ git commit drivers/staging/comedi/drivers/ni_labpc.c

Git はあなたのお気に入りエディタを起動し、以下の情報を表示し、あなたの入力を要求する:


# Please enter the commit message for your changes. Lines starting with '#' will be ignored, and an
    empty message aborts the commit.
# Explicit paths specified without -i nor -o; assuming --only paths...
# On branch tutorial
# Changes to be committed:
#   (use "git reset HEAD <file>..." to unstage)
#
#       modified:   drivers/staging/comedi/drivers/ni_labpc.c

パッチの概要を一行で記述しよう:


Staging: comedi: fix brace coding style issue in ni_labpc.c

次に、より詳細な説明を追加する:

This is a patch to the ni_labpc.c file that fixes up a brace warning found by the checkpatch.pl tool

そしてあなたの 'Signed-off-by:' 行を追加する:


Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>

このファイルを保存したら、Git が commit を行い、次の内容を表示する:

[tutorial 60de825] Staging: comedi: fix brace coding style issue in ni_labpc.c
1 files changed, 2 insertions(+), 4 deletions(-)

git show HEAD コマンドを使えば、直前の変更を確認することができる。今回は、あなたのコミット内容を全て表示してくれる:

$ git show HEAD
commit 60de825964d99dee56108ce4c985a7cfc984e402
Author: Greg Kroah-Hartman <gregkh@suse.de>
Date:   Sat Jan 9 12:07:40 2010 -0800
    Staging: comedi: fix brace coding style issue in ni_labpc.c
    This is a patch to the ni_labpc.c file that fixes up a brace warning found by the checkpatch.pl tool
Signed-off-by: My Name <my_name@my_email_domain>
diff --git a/drivers/staging/comedi/drivers/ni_labpc.c b/drivers/staging/comedi/drivers/ni_labpc.c
index dc3f398..a01e35d 100644
--- a/drivers/staging/comedi/drivers/ni_labpc.c
+++ b/drivers/staging/comedi/drivers/ni_labpc.c
@@ -483,12 +483,10 @@ int labpc_common_attach(struct comedi_device *dev, unsigned long iobase,
printk("comedi%d: ni_labpc: %s, io 0x%lx", dev->minor, thisboard->name,
       iobase);
-       if (irq) {
+       if (irq)
printk(", irq %u", irq);
-       }
-       if (dma_chan) {
+       if (dma_chan)
printk(", dma %u", dma_chan);
-       }
printk("\n");
if (iobase == 0) {

初めてのカーネルパッチ作成はこれで完了だ!



あなたの変更をカーネルツリーに取り込む

さて、パッチはできたが、カーネルツリーにはどうやって取り込めばいい? Linux カーネル開発は、いまだに主にメールベースで行われれている。パッチ提供もレビューもメールだ。


まず最初に、私たちのパッチを受理する担当のメンテナに送付できるように、パッチをエクスポートしよう。このために、またもや Git のコマンド format-patch を使うことができる:


$ git format-patch master..tutorial
0001-Staging-comedi-fix-brace-coding-style-issue-in-ni_la.patch

このコマンドを使うと、'master' ブランチ (というのは Linus のブランチのことだ。最初の方で説明したけど覚えてる?) と私たちの専用ブランチ 'tutorial' とのあいだの全ての差分のパッチを作ることができる。


今回の変更は、私たちのパッチひとつだけだ。それが 0001-Staging-comedi-fix-brace-coding-style-issue-in-ni_la.patch というファイル名で、そのまま送れるフォーマットで保存される。


このパッチを送る前に、パッチが正しいフォーマットになっていて、コーディングスタイルの問題を修正しただけで、カーネルツリーにエラーを追加していないことを確認するべきだ。ここでも checkpatch.pl をもう一度使うことができる:

$ ./scripts/checkpatch.pl 0001-Staging-comedi-fix-brace-coding-style-issue-in-ni_la.patch
total: 0 errors, 0 warnings, 14 lines checked
0001-Staging-comedi-fix-brace-coding-style-issue-in-ni_la.patch has no obvious style problems and is
    ready for submission.


すべて順調...だけど


でも誰に送ればいいんだろう? カーネル開発者たちは、この作業が簡単になるように、ここでもちょっとしたスクリプトを用意していて、あなたがこの変更を誰に知らせるべきかを教えてくれる。get_mainteiner.pl と呼ばれるこのスクリプトは、カーネルソースツリーの scripts/ ディレクトリに含まれている。これはあなたが変更したファイルをチェックして、カーネルソースツリーの MAINTAINERS ファイル (誰がどの部分を管理しているかをまとめたファイル) と照合してくれし、変更されたファイルの変更履歴もチェックしてくれる。こういった情報から、この変更を知らせるべき人たちのリストを魔法のように生成し、メールアドレスまで準備してくれる。


よし、あとはお気に入りのメールクライアントを起動して、get_maintainer.pl が教えてくれた人たちにパッチを送るだけ、だよね? ちょっとまった! よく使われている多くのメールクライアントはパッチファイルをぐちゃぐちゃにしちゃうんだ。改行すべきじゃないところで改行してくれたり、タブをスペースに変換してくれたり、除去すべきじゃないスペースを除去してくれたり、と、できる限りぐちゃぐちゃにしてしまう。


こういったよくある問題と、メールクライアントの適切な設定方法については、カーネルソースツリーの Documentation/email-clients.txt を見てみて。普段使ってるメールクライアントを使ってパッチを送りたいなら、このドキュメントが役立つかもしれない。でも他のやり方もあるんだ・・・。


Git には、git format-patch で作ったパッチを必要な開発者にメールで送る機能もある。git send-email コマンドがそれだ:


$ git send-email --to gregkh@suse.de --to wfp5p@virginia.edu \
   --cc devel@driverdev.osuosl.org \
   --cc linux-kernel@vger.kernel.org \
   0001-Staging-comedi-fix-brace-coding-style-issue-in-ni_la.patch 

・・・とすることで、私たちが作ったパッチを適切な開発者に送り、適切なメーリングリストに CC してくれる。



さて次は?


ようやくパッチを作って送ることができた。今度は、あなたのパッチを受け取った開発者が数日中にメールで返事をくれるだろう。それは「ありがとう。パッチあてたよ。」という素敵な内容かもしれないし、もしかしたら、そのパッチを受理するにはもう少しこういう変更が必要だよ、というコメントかもしれない。一週間たっても返事がなければ、もう一回送ってみよう。ウザがられてるかも、なんて心配はいらない。忙しいカーネルサブシステムメンテナの注意をひくには、粘り強くやることが重要なんだ。


というわけで、Linux カーネルパッチの作成、コミット、そして送付をざっと見てきた。この記事を読んだ人が、カーネルパッチを送ってくれることを願ってるよ。パッチを作ることが楽しくなったら、ぜひコンピュータ史上最大のソフトウェアプロジェクトに貢献しつづけてね。


補記


Greg がこの記事を書いたあと、私たちは、このパッチ手続きに関するたくさんの質問を Linux Format 読者からもらいました。下の二つは特によくきかれるので、みなさんがトラブらないようにここで答えます:

こういう小さいパッチを大量に送ると、カーネル開発者は逆に困るんじゃないの?
そんなことは全然ありません。確かに彼らは大きなプロジェクトで忙しいんですが、それはつまり、彼らにとってそんなに興味のない修正をあなたがやってくれることが重要だということなんです。こういった小さいちょっとしたことを解決することで、Linux カーネルがそのドライバを使えるようになりますし、あなたがどう思うとそれは素晴らしいことです。Greg はこうも言ってます。「大きなパッチを 1 個送ってもいいし、10個のパッチを送ってもいい。どっちでもいいと思う。でも、10 個のパッチを送るってことは、あなたの名前が changelog にたくさん載るってことだからね!」


80文字を超える行があるんですが、ここに改行を入れるのはおかしいと思うんです。どうしたらいいでしょう? (その他スタイルに関する質問)
Linux カーネルメーリングリストにメールしましょう。彼らはいい人たちだし誠実だから、あなたの質問に答えてくれると思いますよ。そしてメーリングリストで回答を得るというのが重要なんです。公開メーリングリストからはみんなが学ぶことができます。世界中に公開される形でメールを送るのが嫌なら、Greg に直接送ることもできますよ。




更新: さらにいくつかのコツ...


カーネルコントリビュータの Dan Carpenter が次のようなアドバイスをくれました:

  1. 開発者に送る前に、まず自分にメールを送ってみるというのは良い方法です。多くのメールは少し文字化けしてるです。メールを生テキストの状態で保存して、自分でパッチを適用してみてください:

    cd /patch/to/kernel/src/
    cat email.file | patch -p1
    
  2. 文字列リテラルを 2 行以上に分割しないこと。カーネルのエラーメッセージを見つけると、ユーザーはその文字列でカーネルソース全体を grep しようとします。文字列リテラルを分割してしまうと、こういったユーザーが困ります。
  3. 重要なクリーンアップでなければ、drivers/staging以外は触らないほうがいいだろう。開発者はバグを見つけると、"git blame" を使って、誰がなぜ変更したのかを調べる。空白の修正をしてしまうと、「XYZというハードウェアをサポートした」というコメントの代わりに「スペースをタブに変更した」と表示されてしまう。(訳注:だから変更履歴を調べるのが少し難しくなる)でも deivers/staging/ のコードはそもそもクソなので、だれも "git blame" を気にしてない。 (訳注: 2012-02-15 に翻訳を追加)
  4. LKML は Greg が言うほど良い奴らじゃないよ。kernel-janitors@vger.kernel.org はもっと初心者に優しいね。

追記 2012-02-15: @omasanoriからコメントを頂いたので、翻訳を追加しました。
追記 2012-02-16: @omasanoriから頂いたコメントをもとに、訳文を変更。

2010年5月29日土曜日

転職して二ヶ月の感想

感想をTwitterに投げたので、こちらに転載します。


imsut
そういえばこないだCTOが、「現場の作業員がラインを止められるTOYOTA Production Systemを見習おう」的なことを言っていた。TOYOTAってやっぱりすごいな。関係ないけど。



imsut
もちろん改善すべき点はたくさんあるんだけど、そういう改善案が現場から出てきて、それが非技術者の関与なしに実現することも、この規模の会社としてはすごいと思う。・・・いや他の会社をしらないんですけどね。



imsut
技術的にはcode review/release requestがworkしているのが意外(良い意味で)。「workしている」というのは、変に手続き重視主義じゃなく、daily/weeklyでbug fix/releaseしつつ、超巨大なシステムを安定運用させてるという意味で。



imsut
こういう研修をすることで、会社が優秀な人間を採用しやすく、彼らが働きやすくしているのは、怖いぐらいに合理的な思考ではある。



imsut
それは決して「差別カッコ悪い」みたいな安いヒューマニズムでは(たぶん)なくて、(例えば) 聞いたこともない宗教を信仰してる50才の黒人女性のレズビアンにも優秀な人はいるわけだから、



imsut
これだけ大きいと、様々な性別/年齢/文化/人種/宗教/sexual orientationの人がいるので、全く異なる文化・背景を持つ人が一緒に働くための研修が必須だったりする。



imsut
ボランティア活動を必須にしているのは、仕事と関係ない人と触れあうことで悪いエリート意識を持たないように注意してるのかな、とか、しかしボランティア活動を有給扱いにできるのはさすが大企業だな、とか思ったり。



imsut
ベンチャーは体制が整ってないから、いろいろ課題があるのはしってたけど、大企業には大企業なりの問題があって、そのための仕組みづくりを考えているんだよね。月に1, 2回はworldwide/regionalなミーティングがあって、自分の仕事と会社全体との関連を教えてくれたり。



imsut
転職して2ヶ月が経ちました。従業員3万人の上場企業は初体験でしたが、大変充実した毎日を過ごしてます。大企業→ベンチャーという転職組から話を聞いていたので、もっと堅苦しくて変なルールがたくさんあるかと想像してたけど。日本企業/外資系という違いが大きいのかもしれない。


2010年3月25日木曜日

日本特有のメール作法からメールフォーマットの改訂を希望する

たいして新しい話題でもないのですが、今朝Twitter経由で教えてもらった
マナーのあるメールの書き方 〜宛名は敬称に気を配ろう〜
に驚いたことと、その後1時間ほど歯の治療をしていて、この件について考えるより他にすることが無かったことがあり、ブログにしたためることにしました。

この「マナーお悩み速攻解決」の論点は2つあります。
  •  To, CCなどのアドレスのdisplay-nameには社名や敬称をつけよ
  •  メール本文は、社名・部署名・肩書き+名前で始めよ
ITのようなリベラルな業界にいると、取引先の担当者も「さん」づけだったりするので、後者については違和感があります。つまり、
    日経BP社        → 1行目に社名
    ●事業部        → 2行目に部署名
    編集長 木村太郎 様   → 3行目に肩書き+名前+様
    なんて書くことはなくて、
    木村さん
    で始めるわけです。(私は社名もつけません。面倒だし、間違えると大変だから。「キャノン」とか。)
    とはいえ、「会社に所属する」という意識が強い保守的な業界では、社名・部署名を書くのは納得できるところではあります。

    前者については(自分の中で)賛否があって、「自動的に挿入されたものをなぜわざわざ書き換えないといけないのか」と思う一方、「同僚に『様』がついている一方、取引先が呼び捨てのまま送るのはちょっと・・・」とも思うわけです。

    そもそも大抵のMUAでは、メールの新規作成時には、送信先を記入するコントロール(つまりToフィールド)にキャレットが表示され、返信時には、本文を記入するコントロールにキャレットが表示されます。(今確認したら、Gmailでもそうだった。)つまり、Toフィールドを書き換えるということは、マウスクリックするなり、Shift+Tabを押すなりしてキャレットを動かす必要があるわけで、これは非常に面倒な作業です。

    なぜそこまでして書き換えないといけない(と思われている)のかというと、人の呼び名がその人との関係によって異なるからです。木村太郎さんは「木村さま」だったり「木村さん」だったり「木村」だったり「木村編集長」だったり「パパ」だったり「あなた」だったりするわけです。
    これを、上下関係を重視する日本固有の事情と理解するのは違うと思われます。相手との関係によって呼び名が変わるというのは、多くの国の文化として根付いていると思われます。三國志(というか中国)を読んだときに、「相当親しい関係でないと名前で読んではダメで、普通の人は字(あざな)で呼ばなくてはいけない」と書いてあった気がいます(吉川三国志かな)。Wikipediaによれば、
    その人物が官職に就いた場合は官職名で呼ぶことが優先された(諸葛亮なら「諸葛丞相」。丞相が官職名である)。この場合、親しい間柄以外は、字で呼ぶことは、諱(引用者注:名前のこと)ほどではないにしても少々無礼なこととされていた。
    だそうです。

    "display-name" の成り立ちは「メールアドレスだけだと誰だかわからん」という問題だと推測されるのですが、だとすると、そして「関係によって呼び名は変わる」ことをあわせて考えると、「返信時に "display-name" も自動的にTo, CCフィールドに挿入される」という機能がバグではないかと。もっと言うと、To, CCに"display-name"が書けてしまうのは「仕様のバグ」ではないかと。

    "display-name" が、「t.kimura@example.comというアドレスを使っている私は木村太郎です」というように自分を明示するための機能であるとすれば、To, CCなどの「相手」を示すフィールドに "display-name" を記載する必要はないのです。

    というわけで、メールフォーマットの仕様を改訂して、To, CC, BCCには "addr-spec"(いわゆるメールアドレス)しか書けないようにして、"display-name" はFromとSenderにのみ許すのが良いと思いました。

    だれかRFC書いてくれ。

    2010年3月24日水曜日

    Popup Dictionary: 初めてのFirefox addon

    英語のウェブページを読むときに書かせないのが、goo辞書や英辞郎などのオンライン辞書です。ただ、「調べたい単語を選択」→「別のタブを開く」→「オンライン辞書で検索」→「結果を読む」→「元のタブに戻る」という手順は明らかに非効率です。

    ということで、選択した単語の周辺にポップアップで単語の意味を表示するaddonを作って見ました。Firefox addon初挑戦。

    参考にしたページは、Mozilla Developer CenterのXUL / JavaScript / DOMです。最初に "Setting up an extension development environment" を読んで、環境をセットアップしましょう。

    基本的には、ウェブページの作成と同じです。XUL(HTML)で画面を設計し、Stylesheetで細かくデザインし、JavaScriptで動きをつける、と。異なる点としては、
    • HTMLよりコントロールが豊富なXULが使える
    • ブラウザがFirefoxに限定されたため、ブラウザによるJavaScriptの挙動の違いに悩まされることがなくなった(気がしますが、Linuxでしかテストしてないのでわからない)
    • Cross site XMLHttpRequestが使える
    ぐらいでしょうか。

    困った点は、
    • 何を設定すれば、Firefoxの「ツール」→「アドオン」→「設定」から開くコンフィグダイアログを開けるようになるのか? (答:install.rdfにoptionsURLタグを書く)
    • XULのelementにaddEventListenerしても、event handlerに処理が渡ってこない。(答:いまだわからず。windowにaddEventListenerするという応急処置)
    • "about:config"画面に表示されるデータストア (?) に値を保存するにはどうするのか。(答:Components.classes['@mozilla.org/preferences;1'].getService(Components.interfaces.nsIPrefBranch)で取得できるオブジェクト経由でget/set可能。)
    があります。
    最後のComponents.classes[foo].getService(bar)を使いこなせれば、もっと便利なサービスが利用できそうです。が、ドキュメントが見つけられず。

    ソースコードはgithubに置きました。
    こちらから直接インストールできます。
    Popup Dictionary ( http://www.kwakaku.net/public/popupdictionary.xpi )

    2010年3月20日土曜日

    addr2line(相当)のshared library対応版

    メモリリークを検出するためにmalloc/freeの呼び出しをログに残すようにしました。あとはログを解析して、対応するfreeが無いmallocを見つけ、そのmallocのコールスタックを表示してやればOKです。

    ログには、コールスタック中の各関数のアドレスが記録されているので、これを関数名に変換したいです。「addr2lineでできるだろう」と思っていたら、addr2lineはshared libraryには対応していないことがわかりました。つまり、動的にロードされたライブラリ中の関数名は、addr2lineでは分からないのです。

    既存のツールがあると思うのですが、見つからなかったので自作しました。
    方針は以下のとおり。
    1. 解析したいプロセスのmapsファイル (/proc/[PID]/maps)をコピーしておく。
    2. 問題のアドレスとmapsファイルから、どのshared library(のtext region)に含まれているかを調べる
    3. 該当のshared libraryのsymbol tableを参照して、どの関数かを調べる
    4. 表示する

    これでうまくいかないケースとして、
    • dlopenを使ってdynamic linkしていて、取得したmapsファイルにmemory map情報が残っていない。
    • トランポリンコード
    が考えられますが、かなり稀な状況なので現実的には問題にならないでしょう。
    コードはgithubに置いてあるaddr2sym.rbです。

    「mallocがダメならnewを使えばいいじゃない」

    そこそこの大きさのソフトウェアを作った場合、メモリリークが問題になることがよくあります。(初期にメモリ管理についてしっかり設計し、エンジニアの合意を取れば、テストフェーズでメモリリークを気にすることはなくなるのかもしれませんが、残念ながらそのような素晴らしいプロジェクトを見たことがありません。)
    普通の環境 (i386, amd64, ppc32, ppc64) ではValgrindを使ってメモリリークを検出するのが定石ですが、Valgrindが動かない環境ではどうしよう、という悩みがあります。

    とりあえず、「glibcをdynamic linkして動作するアプリケーション」という仮定で、メモリリークの検出方法を考えました。この仮定はそんなに非現実的ではないはずです。
    方針としては、愚直ですが、
    malloc/freeをすべて記録し、対応するfreeが存在しないmallocを検出する
    となります。

    glibcには、__malloc_hook, __free_hookというフックが存在して、malloc/freeの呼び出しを横取りすることができます。がしかし、__[malloc|free]_hookはstatic変数でして、multi-threadedな環境では普通には使えません。

    "man __malloc_hook"からの抜粋ですが、
    static void
    my_init_hook(void)
    {
        old_malloc_hook = __malloc_hook;
        __malloc_hook = my_malloc_hook;
    }

    static void *
    my_malloc_hook(size_t size, const void *caller)
    {
        void *result;

        /* Restore all old hooks */
        __malloc_hook = old_malloc_hook;

        /* Call recursively */
        result = malloc(size);

        /* Save underlying hooks */
        old_malloc_hook = __malloc_hook;

        /* printf() might call malloc(), so protect it too. */
        printf("malloc(%u) called from %p returns %p\n",
                (unsigned int) size, caller, result);

        /* Restore our own hooks */
        __malloc_hook = my_malloc_hook;

        return result;
    }
    とやると、my_malloc_hookの"Restore all old hooks"の直後に別のスレッドがmallocを呼ぶと、hookされていない生のmallocを呼べてしまいます。
    (my_malloc_hookをrecursive lockで囲ってやれば大丈夫な気がしますが、試してません。)

    hookは何かと問題がありそうなので、hookを当てにせず、malloc/freeを乗っ取る方向で考えます。やりかたはBinary Hacks #61のとおり。

    注意点としては、mallocを乗っとると(当然ながら)glibcのmallocを普通には呼び出せなくので、複雑な事ができなくなります。今回の場合では、要求されたサイズやアロケートされた領域のアドレス、コールスタックなどを、例えばstd::setに記録し、freeが呼び出されたら対応するmallocの記録をstd::setから削除する、ということをやりたいのですが、STLなどは当然のごとく使えないです。
    しょうがないので、必要な情報はすべてファイルに書き出して、後からスクリプトで処理することにしました。ところが、fopen, fputs, fcloseなども使えない(内部でmallocを読んでいる?)ので、open, write, closeを使うことになりました。何という低レベルコーディング・・・。

    ともあれこれで、malloc/freeが呼ばれたときに、要求されたサイズ、アロケートされたアドレス、コールスタックをファイルに書き出すことができました。
    あとは、これを解析すればリーク箇所(対応するfreeが存在しないmalloc)を検出することができます。

    ソースコードは、githubにアップしてみました。

    なお、タイトルと本文とは(ほとんど)関係ありませんので、あしからず。

    2010年3月5日金曜日

    トレーディング収益1億ドル超の日数の比較

    たまには(というか初めてか)金融関連ネタを。Bloombergの記事から転載。
    米金融大手ゴールドマン・サックス・グループの1日当たりのトレーディング収入が1億ドル(約89億円)を超えた営業日数は、2009年に131日と過去最多だった。08年度の90日から増え、営業日のほぼ半分で1億ドル超の収入を出したことになる。
    (中略)
    米モルガン・スタンレーの先月の届け出によると、09年に1日のトレーディング収入が1億ドルを超えたのは69営業日だった。
    という記事を見ると、「Goldman Sachs (GS) はMorgan Stanley (MS) よりはるかに凄いなー」と思ってしますが、(この記事の内容からは)そうは言いきれない、という話。

    「1億円の利益をあげるのなんて簡単です。私に1000億円貸してくれれば、それを銀行に預けます。金利が0.1%として、年間1億円儲かります。重要なのはどれだけの資本を使って、どれだけの利益をあげるかということなんです」と言った人がいましたが、このGSとMSの話もそれと同じです。
    1億ドル超の日数が約2倍だとしても、使ったお金が2倍違ったら当然ですよね。

    EDGAR (Electronic Data-Gathering, Analysis, and Retrieval system) (有価証券報告書を閲覧できるシステム)で、GSのannual reportMSのannual reportを見てみました。
    まずは、話題のTrading Net Revenues Distribution(トレーディング収益の分布)のヒストグラムです。GSはこう。
    横軸の単位はmillion dollarです。$100 mil.以上の収益をあげた日が131日あったとわかります。ちなみに、括弧つきの数字はマイナスを意味しますので、$75 mil.以上$100 mil.未満の損失を出した日が2日あったということになります。$100 mil.以上が一括りにされてしまっているので、その先の分布はわかりません。

    MSのヒストグラムはこちら。
    各バーの高さが分からないので正確な値は分かりませんが、$100 mil.以上の日が確かに69日のように見えます。

    さて、この2社がトレーディングに使った資本にはどれくらいの差があるのでしょうか? ここでは資本自体の額ではなくて、どのくらいのリスクを取っていたかをVaR (Value at Risk) という値で比較してみます。

    VaRとは、確率Xでの最大損失を表す数値で、例えば「90%VaRが1億円」とは「10回のうち9回は損失額を1億円以下になるでしょう。でも残りの一回はどれだけ損失が出るかわからないよ」という意味です。

    GSの95%VaRの推移はこうだったそうです。
    縦軸の単位はmillion dollarです。2009年の平均daily VaRは$218 mil.だと書いてあったので、最大約200億円/日の損失を出すかもしれないトレーディングをしていたんですね。ところで、ここまで金額が大きいと感覚が麻痺して、何とも思わなくなりますね。

    MSの95%VaRは、推移が開示されていないので、ヒストグラムを引用します。
    平均$119 mil.だそうです。

    このVaRの比較から、GSはMSの約2倍のリスクを取っていた、約2倍の金額をリスクに晒していた、ということができそうです。
    だとすると、$100 mil.以上の利益を出した日が約2倍多いというのもうなずけます。だって約2倍のお金を使っているわけですから。

    とはいえ、この比較もおかしいですね。そもそも「$100 mil.以上の利益を出した日」って何か特別な意味があるわけじゃなくて、感覚的に「すげー」って思えるから使っているだけで、もっと単純に年間のトレーディングの収益合計で比較すべきです。

    この数値をannual reportから拾ってくると(GSの資料からトレーディング収益のみの値を見つけられなかったので、トレーディングと投資との合計で比較します)・・・。
    • GS: $34,373 mil.
    • MS: $6,393 mil.(ただし、投資で$1,054 mil.の損失をだしているので、トレーディングのみだと$7,447 mil.となります。)

    トレーディング+投資の比較だと約5倍の差があります。2倍のリスクを取って5倍の収益、これは凄いかもしれません。

    というわけでまとめとしては、
    1. 収益の額だけ見てても意味ないよ。
    2. とはいっても、収益/リスクの比で見たらGSの方が大きかった。
    3. でもトレーディングと投資の内訳が分からないから、いまいち釈然としない。
    ということになります。

    2010年2月28日日曜日

    "MAD MEN" 酒とタバコと女と仕事

    四六時中タバコをふかし、仕事中も酒を飲みまくり、女を見ればセクハラまがいの言動を繰り返す。今では信じられない風景ですが、これが50年前、1960年代のアメリカだそうです。

    "MAD MEN"とは、広告業界の人間が自分たち自身につけた呼び名。「MADison Squareの男たち」だから"MAD MEN"。「狂った男たち」という意味もあるのかもしれません。

    というわけで、アメリカドラマ"MAD MEN"を見始めましたが、感想としては「空気吸う回数よりタバコ吸う回数が多い」「この人たちはタバコ吸わないと死ぬのでは」「演じてる役者が肺がんになりそう」といったところ。とにかくタバコが凄いです。見てるだけで自分の服がタバコ臭くなる感じ。「50年前のアメリカを再現している」からこそ許されるのであって、これがもし今を描いたドラマなら、反タバコ団体に確実に訴えられるでしょう。

    オフィスについたらまず酒(ブランデー)、そしてタバコ。ミーティング中も欠かさずタバコ&酒。ランチタイムももちろんタバコ&酒。寝る前のベッドでも忘れずタバコ。火事に気をつけて。

    正直、ストーリー(広告代理店に努める主人公の仕事や家族を描いている)はどうでもいいです。最初に見たときは予備知識ゼロだったので、「50年前に作られたドラマなのか?」と思いました。50年前のアメリカに詳しいわけじゃないですが、服装とか髪型とか建物とかよくできてると思います。

    Haskellにおける型と"type class"

    Scalaとかいう汚い言語を勉強した反動で、美しい言語に触れたくなり、Haskellを触っていた週末でした。

    さて、Haskellには "class", "data", "instance", "type" という予約語がありますが、一般的な(オブジェクト指向言語における)意味と全然違うので注意が必要です。

    普通に考えると、
    • class: クラス定義?
    • data: インスタンス生成?
    • instance: dataと同じ・・・?
    • type: ???
    というように使えそうですが、CやJavaの言葉で説明すると、
    • class: インターフェイス定義用。Javaのinterfaceに近い。
    • data: 構造体定義用。Cのstructに近い。
    • instance: 構造体に属性を定義する。強いて言えばJavaのimplementsに近い。
    • type: 型の同名 (synonim) を作る。Cのtypedef。
    となります。

    例としてBookという型を考えます。
    data Book = Book {
      title :: String,
      authors :: [String],
      isbn :: Int
      }

    b = Book "Enjoy Haskell" ["Alice", "Bob"], 12345678
    Javaで書くとこう(動くことを確認していません。最近Java触って無いので間違ってるかも)。
    class Book {
      public String title;
      public ArrayList authors;
      public int isbn;
    }

    Book b = new Book;
    b.title = "Enjoy Haskell";
    b.authors = new ArrayList; // あと省略
    b.isbn = 12345678;
    ISBNがintで表現できるのかという問題は無視します。)
    これでBookクラスを定義できましたが、これだけだといろいろ不便です。例えば(非常に恣意的な例ですが・・・)大小判定ができません。ghciで試してみると・・・
    *Main> let b1 = Book "Enjoy Haskell" ["Alice", "Bob"] 1234
    *Main> let b2 = Book "Scala Sucks" ["Charlie", "Daniela"] 5678
    *Main> b1 < b2

    :1:0:
        No instance for (Ord Book)
          arising from a use of `<' at :1:0-6
        Possible fix: add an instance declaration for (Ord Book)
        In the expression: b1 < b2
        In the definition of `it': it = b1 < b2
    と、エラーになります。
    ご丁寧に"Possible fix: add an instance declaration for (Ord Book)"と、修正方法を教えてくれました。instance declarationを追加してみます。
    instance Eq Book where
      (==) a b = (isbn a) == (isbn b)

    instance Ord Book where
      compare a b = compare (isbn a) (isbn b)
    (今必要なのは、Ord Bookの宣言ですが、OrdはEqのサブクラス(のようなもの)なので、Eq Bookも定義しなくてはいけません。)
    これで、大小比較できるようになります。

    Javaで同じ事をやるには、Comparableというインターフェイス(で定義されているcomparaメソッド)を実装します。
    class Book implements Comparable {
      public int compare(Object o) { return isbn - (Book o).isbn; }
      public String title;
      // 省略
    }

    というように、Javaでimplementsを使うべきところ、つまりクラスにある性質を追加したいときに、Haskellではinstanceを使います。そして、EqやOrdを"type class"と呼びます。で、type classを定義するのに使う予約語が"class"です。例えばEqとOrdの定義はこうです。
    class Eq a where
      (==) :: a -> a -> Bool
      (/=) :: a -> a -> Bool

    class (Eq a) => Ord a where
      compare :: a -> a -> Ordering
      (<) :: a -> a -> Bool
      (>=) :: a -> a -> Bool
      (>) :: a -> a -> Bool
      (<=) :: a -> a -> Bool
      max :: a -> a -> a
      min :: a -> a -> a
    type classが持つべきメソッドの型を定義しています。Javaだとこうですね。
    interface Comparable extends Eq {
      // "extends Eq"はHaskellとの比較のためにつけただけで、実際には不要。

      int compare(Object o);
    }

    このように、"class", "data", "instance"をJavaと対応付けることで、理解しやすくなると思います。

    最後に残った"type"ですが、これはCのtypedefと同じです。
    type ErrorNo = Int
    Cではこうですね。
    typedef int ErrorNo;
    別名をつけただけなので、型チェックでは同じ型だとみなされるのもCと同じです。下のコードはtype checkをパスします。
    f :: ErrorNo -> String
    f e = "Hello World!"

    g :: Int -> String
    g i = f i

    ということで、Haskellにでてくる型関連の予約語を整理してみました。