TBD: WIkipedia Tracer

いい加減に、地震とまったくすすまない引っ越しと掃除とでうんざりしたので横道にそれて全く関係ないことを始める。



Wikipediaを見はじめると以下のような感じになってしまうことが多々ある.

その記事にある、気になる単語を片っ端から別タブで開くとタブ数が爆発してしまい
→ タブ数の増加に応じてOperaが不安定になる
→ おちる
→ オワタ
それもこれもWikipediaが悪い
とはいえ、戒めの為にもどれくらいWikipediaを巡ったのかを残したい.
単純に履歴を漁るという方法もある. だが単なる時系列でなく, どこからどのようにたどったかを記録できるとさらに面白そうだ。



以下の状態は「トップページ」の秀逸な記事にあった「神曲」から始まりいつの間にか「民主党」に至った光景。

後で読もうとおもって「新しいタブで開く」などを行うと、ページ間遷移の履歴はたどれないのでどうしてこうなったかがよくわからなくなる。
実際には以下のルートをたどって来たようだ。

ここでは一本道だが、実際にはこのようなリンクがたくさん連なり「神曲」をルートとする木ができているはず.
その木をたどれたり、可視化できるとおもしろそうだなーというのがモチベーション.
階層型タブブラウザにだいぶ近いし、だれかやってそうだけど、とりあえずやる.



現状ではChromeの拡張として作成. え、Opera? ^q^
Wikipediaの記事間のクリックでリンクを踏むことが多いはずだが、右クリック→「新しいタブで開く」などとする場合が個人的に最近多い。この手段だと、通常のリンクを開く時とは挙動が異なる。HTML+JavaScriptのイベントで感知できる範囲を超えてしまうのでChromeさんの手助けが必要だ。とはいえChromeの拡張でも、プロセスみたいにタブ間の親子関係を保持してくれわけではないようなのでそのあたりを頑張る必要がある. だいたいこのあたりをContent Script+Background Pageでダーティーに解決して、以下のようにリンク踏んでもタブで開いても変わらずに遷移がとれるようにしたところで今日は脱落。

見栄えが悪い。あんまり可視化になってないからグラフを吐けるようにする。そのうちOpera版も書く。

Plan 9 ソースコードでの例外機構 実装編

前に取り上げた「Plan 9 ソースコードでの例外機構 使い方編」の続きです.こんどは実装について.


Plan 9のプロセスでは例外をサポートするために, それを表すProc構造体に特殊なメンバを導入しています.以下に挙げるnerrlabとerrlabがそれにあたります. これらはそれぞれ, 登録されたラベルの個数, 配列です.

/sys/src/9/port/portdat.h : 長いので抜粋

struct Proc
{
...
	int	nerrlab;
	Label	errlab[NERR];
...
}

ここでいうラベルとはエラー時に戻るべき, waserror()関数が呼び出された場所を表します.具体的には以下のようなLabel構造体を用いてこれを保持しています.それぞれスタックポインタとリターンアドレスを格納するためのメンバがあります.

/sys/src/9/pc/dat.h

typedef struct Label Label;

struct Label
{
	ulong	sp;
	ulong	pc;
};

ここまでくればなんとなく, やっていることが分かるかと思います. waserror()呼び出し時にそのときのスタックポインタやリターンアドレスを登録しておき, error()を呼び出したらその値を読み出してレジスタorスタックに書き戻してやればerror()→waserror()へのジャンプが可能になります.

これはもちろん単発での例外を想定してProc構造体にいっこだけ保持するようにしてもできそうです.しかし, Plan 9では先の例で見たようなnexterror()関数を使った連続逆行を可能にしています.このために, errlabメンバでLabel構造体を配列として保持, 利用中の個数をnerrlabメンバで管理することで, ラベルのスタックのように使っています. なお, NERR=64と決まっています*1.

waserror()関数

waserror()関数から見ていきましょう. 実際にはこれは以下のようなマクロになっています. nerrlabメンバで個数を管理しつつ, errlabメンバにあたる配列中のLabel構造体を利用していきます

/sys/src/9/port/fns.h

#define waserror() (up->nerrlab++, setlabel(&up->errlab[up->nerrlab-1]))
setlabel()関数

setlabel関数はアセンブリ言語で書かれており, 以下のような実装になっています.引数としては先ほどみたようなLabel構造体が渡されます. ここではこの時点でのSP(=スタックポインタアドレス)とリターンアドレスを取得し, Label構造体の所定の位置にセットしています. なお, "waserror()関数は通常0が返値"となっているためここでは0を返しています.

/sys/src/9/pc/l.s

TEXT setlabel(SB), $0
	MOVL	label+0(FP), AX
	MOVL	SP, 0(AX)			/* store sp */
	MOVL	0(SP), BX			/* store return pc */
	MOVL	BX, 4(AX)
	MOVL	$0, AX				/* return 0 */
	RET

error()関数

error()関数は例外を発生させる関数でした. この関数ではエラーメッセージを現在のプロセス(up)のerrstrメンバにコピーしてnexterror()関数を呼び出しています. spllo()関数はちなみにsti命令を呼ぶような関数. これと途中のsetlabelは何の為にしてるんでしょうね?

void
error(char *err)
{
	spllo();
	assert(up->nerrlab < NERR);

	kstrcpy(up->errstr, err, ERRMAX);

	setlabel(&up->errlab[NERR-1]);

	nexterror();
}

nexterror()関数

nexterror()関数では. 実際に直前のwaserror()関数が呼び出された場所へのジャンプを可能にしています.先ほどのwaserror()関数ではsetlabel()関数を呼んでいました. これに対してここでは逆操作であるgotolabel()関数を呼んでいます.このとき引数にはerrlabメンバに登録されているモノのうち, 最後尾のLabel構造体へのアドレスを引数として渡しています. これには戻るべきwaserror()関数の場所の情報が入っているのでした.

void
nexterror(void)
{
	gotolabel(&up->errlab[--up->nerrlab]);
}
gotolabel()関数

gotolabel()関数は, 与えられたLabel構造体からsp, pcメンバの値を取得し, それぞれスタックポインタのアドレスを表すSPとその最上部に位置するリターンアドレスにセットします. これにより, この関数は対応するsetlabel()関数が返った位置(waserror()関数)にもどる事になります. またsetlabel()関数では0を返していましたが, 例外で戻ってきたことを表すためにここでは1を返します.

TEXT gotolabel(SB), $0
	MOVL	label+0(FP), AX
	MOVL	0(AX), SP			/* restore sp */
	MOVL	4(AX), AX			/* put return pc on the stack */
	MOVL	AX, 0(SP)
	MOVL	$1, AX				/* return 1 */
	RET

poperror()関数

poperror()関数は直前のwaserror()関数によるエラー処理を取り消すものでした. 先ほどみたような形でLabel構造体を管理しているので, nerrlabをデクリメントするだけでできたりします.

#define poperror() up->nerrlab--

*1:が、waserror()関数ではどうにもこれをきちんと管理しているようには見えない・・・

ひらメソッド for Plan 9

修論オワタ\(^o^)/

人生にわずかながらゆとりができてきたので, ひらメソッドを適用してPlan 9ソースコードを読んでみる.

実はすでにPlan 9日記*1にてoracchaさんがやられていたりする*2.
とはいえ、若干興味の方向が異なっていたりするので(そして, 微妙な解説しか出来そうにない><ので), Pukiwikiつかって勝手に読み進めていこうと思う.

とりあえずはじめてみた
さていつまで続くのやら。

Acme SACで日本語入力 on MacOSX

Plan 9でエディタといえばacmeがあります。3つボタンマウス必須というとてもファンシーな操作体系をお持ちですが, 使っているとなかなかクセになるという不思議なエディタです.
これをPlan 9以外の環境でつかうために, Plan9PortAcme SACを用いる方法があります.
Plan9Portでは日本語表示・入力ともに可能ですが, 微妙に入力が変になります.
「ぐれんだだいすき」と打つと以下のようになってしまいました。(もしかして私の環境だけかもしれませんが・・・)

一方Acme SACでは, 同じような入力が以下のようになってしまいます. 表示が?なことを除けば, 文字数はあってそうです.

これでは論文執筆や, 日々の用途に使えないので大変困ります. ここでは, Acme SACで日本語表示を行えるように設定をしていきます.

日本語表示できない!

どうやら正確にはフォントがない, というのが原因のようです. 実はすでに9fansで, かの有澤先生がご質問なさっていたり・・・
Acme SACから参照可能なディレクトリとして, ~/acme-homeが存在します. これはAcme SAC起動時に自動的に作られる?ようです.
このディレクトリ以下に./lib/fontsディレクトリを作成し, ここに日本語用のフォントを置きいてacmeに認識させてやれば日本語がきちんと表示出来るようになります.

手順

フォントの入手

まず, acme用のフォントを引っ張ってくる必要があります.
これは本来的にはPlan 9のフォント(/lib/font/bin以下)を引っ張ってくる必要があります. しかし先ほど見たPlan9Portの例にあるように, Plan9Portのアーカイブにはこれが含まれています.
Plan 9環境を持っている人は 適当な方法でこのディレクトリの中身を引っ張ってきましょう.
ないひと, あるいは面倒な人はPlan9Portをダウンロードするとよいでしょう. なお, plan9port.tgzを解凍したディレクトリ中の./fontディレクトリの中身がフォントになります.

これらのフォントファイルを~/acme-home/lib/fontsディレクトリを作成し, ここにコピーします.
こんな感じになっていればOKっぽいです.

$ ls ~/acme-home/lib/fonts/
MAP		courier/	gb/		lucida/		lucm/		naga10/		pelm/		smiley/		times/
big5/		fixed/		jis/		lucidasans/	misc/		palatino/	shinonome/	special/
フォントの認識

フォントファイルを置いたら, 次はこれを使えるようにしましょう.
~/acme-home/lib/profileを編集し, 先頭のbindを行っている部分に以下の行を付け加えます.

bind -b $home/lib/fonts /fonts
日本語表示!

以上で日本語表示が出来るようになります.
ためしに「ぐれんだだいすき」と打ってみると以下のようにきちんと表示されます. 2行目に変換をかけてみたものを入力しましたが、カタカナ、漢字も出るようです.

これで日常生活での利用もバッチリ!

Kernel/VM Advent Calendar 52日目 : Brainf**kファイルサーバでもつくってみるか on Plan 9

はじめに

この記事はKernel/VM Advent Calendarのための記事です。

さてPlan 9です. そしてBrainf**kです.
ただ単純に二つを掛け合わせても, Plan 9でのプログラミングの解説にしかならないので,
Plan 9っぽさとして, Brainf**kのインタプリタサービスをファイルとしてアクセス出来るようにしてみました.

作ったモノ

今回は以下の二つをつくってみました.

皆さんご存知のように, Plan 9では全てのリソース, サービスをファイルとして扱うことができます.
逆に, サービスを提供する側のひとたちはファイルとして扱えるようにサービスを実装する必要があります.
ユーザ空間ではファイルサーバ, カーネル空間ではバイスとして, サービスを実装することでこれを行います.
ファイルサーバは, Linuxなどで言えばデーモンに相当するプロセスとして動作します. デバイスは, デバイスでいいんだろうか・・・


今回, インタプリタは実行プログラムとしてではなくファイルとしてユーザから扱えます.
このため, そもそもプログラムを実行させる方法や, brainfuckの命令である"."(画面出力)や","(入力)を受け取る方法を変える必要があります.
ここでは以下のようにしました.

  • インタプリタのファイルサーバはcmddataと名付けられたファイルを提供
  • cmdにプログラムを書き込むと, その結果がdataに溜まる
  • 画面出力の"."は, dataにデータを溜めるために用いる
  • 入力の","は, 後続する一文字をテープの所定の位置に突っ込む

このため, echoでcmdにプログラム文字列を突っ込んでから, catでdataを読み込むと結果が得られる, といった形になります.

bffileserver

bffileserverは, 9Pファイルサーバとして実装されています.
ファイルサーバは, ファイルをユーザに提供するデーモンのようなモノと言えます.
もともとただのプログラムなので起動は, bfとコマンドを入力するだけです.
起動すると, このプロセスは/srv/bfにファイルを作成します.
ユーザはこのファイルをmountコマンドで, 好きな位置にマウントすることで, インタプリタのサービスを扱うためのファイルを参照することができます.

ここではbfsrvディレクトリにマウントしてみました. cmdとdataという名前のファイルが見えているのが分かると思います.

さて, 実際にプログラムを流し込んでみましょう. brainfuckでのHelloWorldはこんな感じでした.

+++++++++[>++++++++>+++++++++++>+++++<<<-]>.>++.+++++++..+++.>-.------------.<++++++++.--------.+++.------.--------.>+.

ここではechoコマンドでこの文字列をcmdに流し込んでみます.

見事になにも起こりませんね. ファイルは叫んだりしないのであたりまえといえばあたりまえですが.
それでは, dataの中身をcatで呼んでみましょう.

するときちんとここに, 本来画面に出力すべきであったデータがとってあるのが見えます.
(なおここは, 単なるバッファなので一回読み出すとそれっきりです. 二回目にcatコマンドを読んでもなにも出ません. )

ファイルサーバの作り方

ファイルサーバの実装は以下の手順が必要です.

  1. Srv構造体を定義
  2. 仮想的なディレクトリツリーの定義とファイルの作成・追加
  3. ディレクトリツリーの登録&サービスの提供

以上がbfのmain関数で行うべき全てです.

void main(int argc, char** argv)
{
	initfs();  // 1, 2

	postmountsrv( &bffs, "bf", nil, MREPL|MCREATE ); // 3
}

Srv構造体(9p(2))は, そのファイルサーバの挙動と, 提供するツリーの情報を保持します.
前者は, ファイル操作に対応した関数テーブルと, ディレクトリツリーを表すTreeという構造体から成ります.
今回, このファイルサーバは1階層に二つのファイルしかない単純な構造です.
基本的にはread/write時に行うべき事を定義するだけでやりたいことができるので, 実装した関数は以下のように非常に少ないです.

Srv bffs = {
	.open	= fsopen,
	.read	= fsread,
	.write	= fswrite,
};

もっと複雑だったり, 動的に編成されるツリーを提供するようなファイルサーバを実装する場合には,
他にもいろいろと関数を追加する必要があります. が, まぁ手抜きなのでこんなもんでしょう...

このSrv構造体にツリーを割り当てて, ファイルを追加していきます.
これは, メモリ上の仮想的なツリーです. こういったものを形成するための関数がいくつか用意されています(9pfile(2)).

void initfs(void)
{
	bffs.tree = alloctree( nil, nil, DMDIR|077, fsdestroyfile );  // ディレクトリツリーの作成
	createfile( bffs.tree->root, "cmd", nil, 0777, nil );  // cmdファイルの追加
	createfile( bffs.tree->root, "data", nil, 0777, nil ); // dataファイルの追加

	return;
}

今回の場合, echoコマンドでfswrite()関数が, catコマンドでfsread()関数が呼び出されると予想できます.
それぞれ, 前者は受け取った文字列を解釈・実行しバッファにデータをため, 後者ではこのバッファからデータを読みます.

devbrainfuck

bffileserverはお手軽ですが, ユーザ空間のサービスの上あんまりカーネルVMっぽくないので, Advent Calendarのネタにするにはもう少しひねりが必要そうです.
ここでは, "とりあえずカーネルかデバドラ弄くれば許されるだろう"という安直な発想の元,
brainfuckバイスのようなものをPlan 9で書いてみました. 基本的な動作は先のファイルサーバ版と変わりません.



Plan 9では多くの機能がファイルサーバとして提供されています.
かのYUREXドライバ for Plan 9も, ファイルサーバの一つです.
これらはファイルとしてのインタフェイスを提供し, 容易に入れ替えたりすることができます.
一方で, Plan 9カーネルのコアの機能はカーネルに静的に埋め込まれています.
この機能を提供するモジュールをバイスと呼びます.
先ほどのYUREXドライバの土台となるUSBデバイスや, プロセスデバイス, ファイルシステムバイスなどがこれに含まれます.
ここでは, このデバイスの一つとして, Brainf**kのインタプリタサービスを追加してみます.


使い方

まずは, カーネルにこのソースコードを組み込む必要があります. ここでは, x86向けのpcfというカーネルの場合のコンパイル, 起動方法を挙げます

  • devbrainfuck.cソースファイルを/sys/src/9/port/以下にコピー
  • このカーネルのコンフィグファイルである/sys/src/9/pc/pcf中に, 組み込むデバイスとしてbrainfuckを追加

% mk 'CONF=pcf'
% ls 9pcf
9pcf

  • 9fat:コマンドで, 9fat領域を/n/9fat以下にマウント

% 9fat:
% ls /n/9fat
...

  • 先の9pcfカーネルをここにコピー(ここではbf9pcfという名前に変更)

% cp 9pcf /n/9fat/bf9pcf

  • /n/9fat/plan9.iniに以下の行を追加

bootfile=sdC0!9fat!bf9pcf

先ほどのファイルサーバの例では, 明示的にプログラムを立ち上げる必要がありましたが, 今回はカーネルが処理を行ってくれるので不要です.
ただし, サービスにアクセスするために, Brainf**kデバイスが提供するファイルツリーをbindする必要があります.
それぞれのデバイスは, 自身が提供するファイルツリーを, #と任意の一文字からなる名前空間に持っています.
たとえば, ルートファイルシステム#/, キーボードマッピング, マウスは#mと言った具合です
devbrainfuckデバイス#b名前空間を使っているので, これを適当な位置にマウントします.
ここでは/mnt/bfにマウントしてみました.

先ほどと同じく, 2つファイルがあります. 今回はbfcmdとbfdataという名前にしました.
これは通常, デバイスの提供するツリーは/dev以下にみんなまとめてbindされることが多いため, 今回作ったファイルを区別するためです.
ファイルサーバの例と同じように, echo/catでプログラムの流し込み, 結果の取り出しができます.

作り方

ファイルサーバではSrv構造体を用いましたが, デバイスの実装ではDev構造体というものがかわりに用意されています.
これもSrv構造体と同じような働きをしますが, ツリーの情報は持っていません.
一方で, デバイス固有の名前空間を定義するために任意の一文字を指定するメンバがあります.
ここではbを割り当てています.(Bはとられていた・・・)

Dev brainfuckdevtab = {
	'b',
	"brainfuck",

	bfreset,
	devinit,
	devshutdown,
	bfattach,
	bfwalk,
	bfstat,
	bfopen,
	devcreate,
	bfclose,
	bfread,
	devbread,
	bfwrite,
	devbwrite,
	devremove,
	devwstat,
};

ファイルサーバの時にも述べましたが, やるべき事はそんなに多くないので大抵の関数はdev~で始まるデフォルトの挙動を行う関数になります.
またその他の関数についても, bfread()とbfwrite()以外は大してお仕事をしていません. しかも, brainfuckの主要部はファイルサーバのモノと共通化できているという...

まとめ

というわけでBrainf**k interpreter as a Fileとして, ユーザ空間のファイルサーバ実装と, カーネル空間のデバイス実装について取り上げてみました.
でっちあげの構造のためいろいろと不備がありますが, 基本的な実装はRAMファイルシステム(/sys/src/lib9p/ramfs.c)やキーボードマップデバイス(devkbmap.c)などといった,
実際のファイルサーバやデバイスを参考にしたものです. Everything is a FileSystemをどのように実現しているのかうかがい知るのにはある程度, 役に立つでしょう.

もっとも, Plan 9の一番面白いところは, デバイスやファイルサーバとユーザの間に横たわるカーネル自体にあります.
これを期にPlan 9ソースコードなぞ探検してみてはいかがですか?

Plan 9 ソースコードでの例外機構 使い方編

Plan 9ソースコードを読んでいると以下のような関数が頻繁に出てきます.

  • error()
  • waserror()
  • poperror()
  • nexterror()

これらはC言語で例外をサポートするためにPlan 9で利用される関数です.
具体的には以下のように使います

  1 int func(){
  2         if( waserror() ){
  3                 recover_from_error();
  4                 return ERROR;
  5         }
  6 
  7         // below could err
  8         if( !do_something_has_error() ){
  9                 error("failed!");
 10 
 11         }
 12 
 13         poperror();                                                                                                                                                                             
 14         return SUCCESS;
 15 }  

recover_from_error()とdo_something_has_error()はコードを書く人が挿入した任意の処理だと考えてください. recover_from_error()はたとえば取得したリソースを解放したりと言ったことを行います. do_something_has_error()は仮に処理が上手くいかなかったら0を返す関数としましょう.


このときこのfunc()はどのようなフローになるでしょうか?waserror()は通常0を返す事になっています.このため最初のif節はスルーされることになります.

そうして8行目が実行されるわけですが, 仮にここでdo_something_has_error()が何らかの処理の結果, 0(失敗)を返したとします. 当然のことながら, if節が実行されその中でerror()が実行されますね.

この時, error()が呼び出されたらなんと次の瞬間には"直前の"waserror()の行まで戻る,という流れになっているのです. そしてこの時はwaserror()は1を返すようになります.このため, 先ほどはスルーされたif節が実行される事になるのです.

逆に以下のように書くこともできます.

  1 int func(){                                                                                   
  2         if( !waserror() ){
  3                 // below could err                                                            
  4                 if( !do_something_has_error() )
  5                         error("failed!");                                                                                                                                                       
  6                 poperror();   
  7                 return SUCCESS;
  8         }                     
  9                               
 10         recover_from_error(); 
 11                        
 12         return ERROR;  
 13 }

この場合, エラーが無ければ3-7行目が実行されますが, do_something_has_error()が0を返すと,
5行目の次に2行目に飛びます. このときwaserror()は1を返すので, if節の処理は実行されずに10行目のrecover_from_error()が実行されることになります.

共にRubyで類似コードを書くと以下のようになるでしょうか

  1 def func()                                                                                                                                                                                      
  2   if( !do_someghint_has_error() )
  3     raise "failed!"
  4   end
  5   return SUCCESS;
  6 rescue
  7   recover_from_error();
  8   return ERROR;
  9 end

これらの例外機構のもっともオソロシイ使い方はnexterror(), poperror()を併用したコード上での逆戻りです.nexterror()はerror()同様に"直前の"waserror()に飛ぶ関数であり, poperror()は例外を一つ消すものだと考えてください.

これらを用いると以下のような関数がかけます.

  1 int func(){                                                                                                                                                                                     
  2         a = alloc(sizeof(a));
  3         if( waserror() ){
  4                 free(a);
  5                 nexterror();
  6         }       
  7         
  8         // do something
  9         
 10         b = alloc(sizeof(b));
 11         if( waserror() ){
 12                 free(b);
 13                 nexterror();
 14         }       
 15         
 16         c = alloc(sizeof(c));
 17         if( waserror() ){
 18                 free(c);
 19                 nexterror();
 20         }       
 21         
 22         if( !do_something_has_error() ){
 23                 error("we go back");
 24         }       
 25         
 26         poperror();
 27         poperror();
 28         poperror();
 29         return SUCCESS;
 30 }       

正常に実行されていれば, 2→10→16→22→26行目というように処理が運びます.

一方で, 22行目で先ほどと同じように"エラー"とされerror()関数が呼び出された場合, 直前のwaserror()である17行目まで戻ります. その中でnexterror()が呼ばれていますが, これはerror()関数と同じく直前のwaserror()の行までジャンプします.

このため最終的な結果として, do_something_has_error()が"エラー"を起こすと, 18行目のfree(c), 12行目のfree(b), 4行目のfree(a)が順々に呼び出されることになります.
そして最後(コード上は最初)のwaserror()を含むif文ではさらにnexterror()を呼び出しています

  3         if( waserror() ){
  4                 free(a);
  5                 nexterror();
  6         }

これはさらにその外, func()を呼び出しているような関数に対して例外を投げています.
このためたとえばfunc()を呼び出す関数do_func()は以下のようになっている必要があります.

  1 int do_func(){
  2         if( !waserror() ){
  3                 func();
  4                 poperror();
  5                 return SUCCESS;
  6         }
  7 
  8         // tell something
  9         return ERROR;                                                                                                                                                                           
 10 }    

e1000ドライバ探検隊〜その他 ささいなこと〜

Linuxデバドラ本勉強会で突っ込まれてたけど調べてなかった部分についてちょろりと

187 static struct pci_driver e1000_driver = {
188         .name     = e1000_driver_name,
189         .id_table = e1000_pci_tbl,
190         .probe    = e1000_probe,
191         .remove   = __devexit_p(e1000_remove),
192 #ifdef CONFIG_PM
193         /* Power Managment Hooks */
194         .suspend  = e1000_suspend,
195         .resume   = e1000_resume,
196 #endif
197         .shutdown = e1000_shutdown,
198         .err_handler = &e1000_err_handler
199 };

上記の構造体で"__devexit_p"って何ぞというお話があったのでここでまとめておく。
やってることは単純でNULLか、そのままかをCONFIG_HOTPLUGに応じて変えるだけ. e1000_remove()はホットプラグが有効になっている場合に、デバイスが取り除かれたことを検知した時呼び出される関数なのでなんとなくこうしている理由は分かる.

313 /* Functions marked as __devexit may be discarded at kernel link time, depending
314    on config options.  Newer versions of binutils detect references from
315    retained sections to discarded sections and flag an error.  Pointers to
316    __devexit functions must use __devexit_p(function_name), the wrapper will
317    insert either the function_name or NULL, depending on the config options.
318  */
319 #if defined(MODULE) || defined(CONFIG_HOTPLUG)
320 #define __devexit_p(x) x
321 #else
322 #define __devexit_p(x) NULL
323 #endif

ただし"__devexit"とついている関数だけらしい
e1000_removeは以下のように宣言されている.

110 static void __devexit e1000_remove(struct pci_dev *pdev);

この__devexitの定義をおっていくと

include/linux/init.h
 81 #define __devexit        __section(.devexit.text) __exitused __cold

__sectionマクロは".devexit.text"セクションに宣言した関数を置く.

include/linux/compiler.h
260 #ifndef __section
261 # define __section(S) __attribute__ ((__section__(#S)))
262 #endif

__exitusedマクロは, gcc拡張の"__used". 関数が宣言されてて使われない場合, つまりCONFFIG_HOTPLUGが有効にされなかった場合に警告を抑制するようにする

include/linux/init.h
 69 #ifdef MODULE
 70 #define __exitused
 71 #else
 72 #define __exitused  __used
 73 #endif

__coldマクロ

include/linux/compiler-gcc4.h
 23 #if __GNUC_MINOR__ >= 3
 24 /* Mark functions as cold. gcc will assume any path leading to a call
 25    to them will be unlikely.  This means a lot of manual unlikely()s
 26    are unnecessary now for any paths leading to the usual suspects
 27    like BUG(), printk(), panic() etc. [but let's keep them for now for
 28    older compilers]
 29 
 30    Early snapshots of gcc 4.3 don't support this and we can't detect this
 31    in the preprocessor, but we can live with this because they're unreleased.
 32    Maketime probing would be overkill here.
 33 
 34    gcc also has a __attribute__((__hot__)) to move hot functions into
 35    a special section, but I don't see any sense in this right now in
 36    the kernel context */
 37 #define __cold                  __attribute__((__cold__))
 38 
 39 #endif

これに関してGCCマニュアルだと以下のようにcoldについての説明がある. unlikelyみたいなものらしい.

cold
The cold attribute is used to inform the compiler that a function is unlikely executed. The function is optimized for size rather than speed and on many targets it is placed into special subsection of the text section so all cold functions appears close together improving code locality of non-cold parts of program. The paths leading to call of cold functions within code are marked as unlikely by the branch prediction mechanism. It is thus useful to mark functions used to handle unlikely conditions, such as perror, as cold to improve optimization of hot functions that do call marked functions in rare occasions.

When profile feedback is available, via -fprofile-use, hot functions are automatically detected and this attribute is ignored.

The cold attribute is not implemented in GCC versions earlier than 4.3.

これが有効になっているのはGCC 4.3以上らしいので、それ以外のバージョンについては単純に__coldを宣言するだけ.

include/linux/compiler.h
250 /*
251  * Tell gcc if a function is cold. The compiler will assume any path
252  * directly leading to the call is unlikely.
253  */
254 
255 #ifndef __cold
256 #define __cold
257 #endif