Linux kernelには、FreeBSDのKSEや、NetBSDやSolarisのLWPのような、プロセスよりも細かいコンテキストスイッチの単位がない。あくまでプロセスがコンテキストスイッチの最小の単位で、スレッドはfork()のスーパーセットであるclone()というLinux独自のシステムコールで作成されたプロセスとして動作する。

要はメモリリソースを親プロセスと共有したまま(子プロセス用のメモリ空間をコピーせずに)作られたプロセスなのだが、実際のコンテキストスイッチの際には、メモリリソースの切り替えが発生しないので、通常のプロセスコンテキストの切り替えよりも理屈の上では速いはずだ。結果的にLWPと似たような位置付けになっている。

kernelのプロセスコンテキストに対して、どのようにスレッドを割り当てるかはglibc(libc,libthread)がやっていて、kernel2.4系の頃はLinuxThreadsという方式で行っていた。スレッドにはそれぞれ別のpid(プロセスid)が払い出され、それぞれのプロセス(スレッド)は別のマネージャスレッド(プロセス)が管理する。この方式だとスレッド間の(実際にはプロセス間の)シグナル配送などに問題が出ることがある(ややこしいなあ、プロセスとスレッドが一緒だと)。また、pidも大量に消費する傾向があり、大量のスレッドを作るプロセスが大量に起動したりするとpidを使い果たしてしまう可能性もある。一方で、kernel側に特に修正は必要なくて、kernelはプロセスとして見えているものだけをコンテキストスイッチの対象にすればよいので動作はシンプルだ。FreeBSDでもrfork()をclone()の代わりに使ったLinuxThreads互換の実装があったりなんかして、kseやuleスケジューラが安定する前にはよく使われたりした。

2.6系のkernelではLinuxThreadsに変わってNPTL(Native POSIX Thread Library)が実装されている。kernel、glibc両方に変更が加わっていて、LinuxThreadsよりもスレッドの生成がやたら速いらしい。スレッドの生成には相変わらずclone()を使うことや、kernelから見た際のコンテキストスイッチの単位はプロセスであることなどは変わらないが、見た目の大きな変化として、一つのプロセスには一つのpidがひも尽くこと(スレッド毎にpidが変わらない)、スレッドidが巨大な数字を扱えることなどの違いがある。またPOSIX Thread(標準的なスレッド実装)への準拠度合いも高い。逆に言うとLinuxThreadsはPOSIX Threadと非互換な部分が多いと言える。

不幸にもLinuxThreadsのPOSIX非互換部分に依存したプログラムがあった場合、2.4系では正常だったのに2.6系で予期しない動作になるという、悲しい結果になる場合がある。そんなときのために、2.6系kernelでもLinuxThreadsを実行すればよい(LinuxThreadsにはkernelに特殊な仕掛けが必要ないので、glibcさえ用意すれば問題ない)。

ライブラリの切り替えは一見、再コンパイルが必要なように感じるが、libpthreadとlibcが動的リンクされていれば、ldがLinuxThrads版か、標準のNPTL版を選択してリンクしてくれる仕組みなので、コンパイル済みバイナリでも起動時に切り替えることができる。

具体的には環境変数 LD_ASSUME_KERNEL にkernelバージョンを入れると良い。環境変数が定義されていなければデフォルトのNPTL版glibcがリンクされ、2.4.19などとすればLinuxThreads版のglibcがリンクされる。

この仕組み自体は、shared libraryが必要とするkernelバージョンに対して、現在の実kernelバージョンを上書きするためのものだ。環境変数 LD_ASSUME_KERNEL に対象のkernelバージョンを入れると、ldが実行時にそのkernelバージョンに応じたshared libraryをリンクするという寸法(そのバージョンより大きいバージョンを必要とするshared libraryをリンクしない)。NPTL版glibcはkernelバージョン2.4.20に依存しているため、LD_ASSUME_KERNELにそれ未満の数字が入っていると、そのバージョンでリンク可能なglibc(LinuxThreads版)がリンクされるためこんな挙動になる。

ちなみに動作はCentOS4系で確認した。CentOS5系の最小構成インストールだと、LinuxThreads互換のglibcはインストールされないようだ。

スレッド実装の確認のために以下のコマンドが用意されている。

% getconf GNU_LIBPTHREAD_VERSION

以下、実際の動作。動作確認のプログラムは小俣光之さんの所からいただきました。

% getconf GNU_LIBPTHREAD_VERSION
NPTL 2.3.4
% ldd ./mt
        libpthread.so.0 => /lib/tls/libpthread.so.0 (0x00504000)
        libc.so.6 => /lib/tls/libc.so.6 (0x00353000)
        /lib/ld-linux.so.2 (0x00339000)
% ./mt
[2758]start
[2758]thread_id1=-1208775776
[2758]thread_id2=-1219265632
[2758][-1208775776]0
[2758][-1219265632]0
[2758][-1208775776]1
[2758][-1219265632]1
[2758][-1208775776]2
[2758][-1219265632]2
[2758][-1208775776]3
[2758][-1219265632]3
[2758][-1208775776]4
[2758][-1219265632]4
[2758][-1208775776]5
[2758][-1219265632]5
[2758][-1208775776]6
[2758][-1219265632]6
[2758][-1208775776]7
[2758][-1219265632]7
[2758][-1208775776]8
[2758][-1219265632]8
[2758][-1208775776]9
[2758][-1219265632]9
[2758]thread_id1 = -1208775776 end
[2758]thread_id2 = -1219265632 end
[2758]end
zsh: exit 10    ./mt
%

これが通常のNPTLの動作結果。それぞれ別々のスレッドidが払い出されているが、pidはどちらも2758で共通。

一方LinuxThreadsだと、

% export LD_ASSUME_KERNEL=2.4.19
% getconf GNU_LIBPTHREAD_VERSION
linuxthreads-0.10
※LinuxThreadsになった
% ldd ./mt
        libpthread.so.0 => /lib/i686/libpthread.so.0 (0x0069d000)
        libc.so.6 => /lib/i686/libc.so.6 (0x00ae9000)
        /lib/ld-linux.so.2 (0x00339000)
※/lib/tls/以下のshared libraryが/lib/i686/以下のものに置き換わってリンクされる
% ./mt
[2854]start
[2854]thread_id1=16386
[2854]thread_id2=32771
[2856][16386]0
[2857][32771]0
[2856][16386]1
[2857][32771]1
[2856][16386]2
[2857][32771]2
[2856][16386]3
[2857][32771]3
[2856][16386]4
[2857][32771]4
[2856][16386]5
[2857][32771]5
[2856][16386]6
[2857][32771]6
[2856][16386]7
[2857][32771]7
[2856][16386]8
[2857][32771]8
[2856][16386]9
[2857][32771]9
[2854]thread_id1 = 16386 end
[2854]thread_id2 = 32771 end
[2854]end
zsh: exit 10    ./mt
%
※スレッドidも別ならば、それぞれのスレッドのpidも別。スレッドidの採番ルールも明らかに違う。

というわけで、2.6系kernel上でもLinuxThreadsを使うことはできた。2.6系kernelではスケジューラなども違うので、細かい挙動そのものが完全に2.4系と一緒になるわけではないが、LinuxThreadsに依存したバイナリを実行するには十分だろう。今回は性能のベンチマークは取っていないけれども、LinuxThreadsがNPTLより速い理由もないので、性能上の理由でLinuxThreaadsを使うこともないと思う。

詳しくはpthreads(7)を。