負け組アーキテクトの憂鬱

メモしておきたいことや読書の記録を淡々と書く。

2009年07月

NPTLが標準の2.6系kernelでLinuxThreadsを使う

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)を。

VMWare仮想マシンのネットワークインターフェイスの選択方法

VMware仮想マシンでは今のところ3種類のNIC(Network Interface Card)のエミュレートができる。

vlance
俗に言うAMD PCnet32 10Mbps 遅い
e1000
俗に言うIntel PRO1000 1000Mbps 速い
vmxnet
VMware独自のNIC 1000Mbps相当 速い

vlanceとe1000は、ゲストOSに該当ハードウェア用のドライバが用意されていればそのドライバを利用することが出来る。

ただし、実際のハードウェアと完全な互換を保つ必要があるため、ホストOS(ESXならvmkernel)でのCPU負荷が大きい。ネットワーク負荷に伴って、CPUリソースがどんどん食われる傾向にある。

そこで利用されるのがvmxnetで、これはゲストOS上で専用のデバイスドライバが必要だが、ハードウェアと完全互換を保つ必要がなく、NICとしての必要十分な機能が実装されているもの。完全仮想化に対して、準仮想化(para-virtualization)などと言う。

vmxnetの他にもVGAドライバやマウスドライバに準仮想化ドライバが用意されていて、それらは各ゲストOS用のvmware-toolsに含まれている。

VGAやマウスドライバは、準仮想化ドライバをインストールしない限り、ゲストOSからは基本的な機能しか持たない完全仮想化デバイスとして動作し、準仮想化ドライバをインストールした時点で、準仮想化デバイスとして利用できるようになる。

NICの場合もデフォルトではflexibleと言って、通常はvlanceとして動作し、準仮想化ドライバをインストールするとvmxnet相当の準仮想化デバイスとして動作するようになる。

ただし、Linux等ではvmware-toolsに含まれるvmxnet.ko(準仮想化ドライバ)をinsmodしても、flexibleデバイスがvmxnetとして認識せず、vlanceとしてしか動作しなかったり、デバイスの認識順が変わってしまうなどの問題が出ることがある。

この場合には、NICを固定的に指定することができるが、VMware InfrastructureやVMware ServerではGUIが用意されていない(vSphereやWorkstationは未確認)。

大まかには該当仮想マシンの仮想マシン構成ファイル(拡張子が.vmxのファイル)を更新するのだが、ESXやVirtual Centerのインベントリに追加されている状態では、直接ファイルを編集することができない(ファイルだけ編集してもメモリ上の状態で上書きされてしまう)。

そのため、ESXやVirtual Center管理ならば、一端インベントリから削除して作業を行う必要がある。VMWare Serverならプロセスなりサービスなりを停止してから作業すると良い。

ESXやVirtual Centerのインベントリから削除する方法は二つ。

  • 仮想マシンを右クリックして「インベントリから削除」を選択する方法
  • 仮想マシンテンプレートに変換する方法

インベントリから削除する場合は誤ってディスクから削除しないこと。仮想マシンテンプレートに変換すると、仮想マシン定義ファイル(拡張子.vmx)は仮想マシンテンプレート定義ファイル(拡張子.vmtx)にリネームされるので注意。

仮想マシン(テンプレート)定義ファイルに追記する内容は以下のとおり。

ethernet0.virtualDev = "vmxnet"
ethernet0.virtualDev = "vlance"
ethernet0.virtualDev = "e1000"

このいずれかを指定すれば良い。2つめ以降のNICはethernet1,ethernet2…とする。

定義ファイルの編集方法は、ESXでVMFSデータストアならば、service consoleにsshなりコンソールなりでログインすると、/vmfs/volumes/[データストア名]/でデータストアにアクセスできるためそこで編集すれば良い(service consoleのないESXiの手段は不明・・・)。VMware ServerやWorkstationならホストOS上で直接エディタで開いてしまうのが良い。

ESXやVirtual Centerで、編集が終わった後のインベントリへの再追加方法は、そのデータストアを参照できるESXのデータストアアイコンを右クリックして「データストア内を参照」、該当のvmxファイルを探して右クリックし「インベントリに追加」する。元あったクラスタやリソースプールに追加できる。テンプレートから戻す場合には、「仮想マシンおよびテンプレート」のインベントリから、該当のテンプレートを右クリックし「仮想マシンに変換」で良い。同様に元あったクラスタやリソースプールに追加する。テンプレートがインベントリに見つからない場合は、上記vmxと同様にデータストアからインベントリに追加すると良い。

どのNICを利用すべきかは、性能の観点からは間違いなくvmxnetだろう。ただし、LinuxなどのOSの場合、vmxnetはvmware-toolsによってLKM(vmxnet.ko)として提供される。そのため、他のサードパーティ製LKMと同様に、kernelをupdateする度にドライバをそのkernel versionに合ったディレクトリの下に再配置する手間が発生する。

vmware-toolsではvmware-config-tools.plというスクリプトがこのへんの処理もまとめてやってくれるが、いかんせんkernelのupdate直後に(NICのドライバが入っていなくてネットが使えないので)コンソールでログインしてそのスクリプトを実行するというのが面倒な場面がある。台数が増えればかなりめんどくさい作業になるだろう。もし頻繁にkernelのupdateを予定しているのであればe1000ドライバを利用するのも良いアイデアかもしれない(性能もちょっと試して見た感じでは言うほどCPUリソースを食うわけでもなかったが、具体的な数字は失念)。

なお、64bit仮想マシンではNICはe1000固定になる。選択可能なのはあくまで32bit仮想マシンの場合だけだ。

SPARC機のALOM(sc)のパスワードを忘れたときの対処法

  1. とりあえずSolarisを立ち上げる
  2. ↓のコマンドで再設定
# /usr/platform/`uname -i`/sbin/scadm userpassword admin

※admin以外のユーザ名のときにはadminのところをそのユーザ名に

scsiバスのリスキャン方法メモ

※ベンダ提供の専用のコマンドが用意されてるときにはそっち使った方が確実

一般的なLinuxの一般的なscsiデバイスの場合
# echo "scsi add-single-device 0 0 1 0" > /proc/scsi/scsi
※rescanというか、targetの認識のためにやる。引数の数字はそれぞれ、host bus target(id) lun

Solarisの場合
# drvconfig

SPARC機のokプロンプトの場合
ok probe-scsi または
ok probe-scsi-all

VMware ESXのservice consoleの場合
# esxcfg-rescan [vmhbaのデバイス名]

引っ越しと再開

さくらインターネットのレンタルサーバで自前で構築したMovableTypeからlivedoor Blogに引っ越しました。

引っ越してて気づいたけど、2年も前の記事か…

引っ越さなくてもよかったかも。