いつの間にか季節は立冬をすぎ、朝晩はめっきり冷えこむようになってきました。夜の時間も長いし、PCの発熱を気にしなくてすむこの時期は、ソフトウェアとたわむれるのに絶好の時期でしょう。
前回、新しいCore i7なマシンを開発用に自作した話を紹介しました。このCPUにはIntel64と呼ばれる64ビット処理機能が搭載されており、32ビット環境との互換性を持ちつつ、64ビットの処理を実行させることが可能です。そこでさっそくこのCPU上でPlamo Linuxの64ビット化の作業に着手しました。
もっとも、64ビット化のためには、現在の32ビット版のパッケージを64ビット用に再コンパイルしていく必要があります。Plamoは小規模なディストリビューションとは言え、収録しているパッケージは1000を越えているので、それら全てを64ビット用に再コンパイルするのはかなり時間と根気のいる作業です。64ビット版Plamo Linuxを一般に公開できるようになるのはだいぶ先の話になりそうですが、この連載では適宜進行状況を紹介していきたいと思います。今回はその第1回目として、64ビット用の開発環境を構築した話題を取り上げてみましょう。
x86-64 CPU
今回導入したCore i7もそうですが、最近のIntel/AMD製のCPUではたいていx86-64 と呼ばれる64ビット化機能が組み込まれています。この機能はIntel CPUではIntel64 、AMD CPUではAMD64 と呼んでいますが、機能的には同一なので、両者を総称してx86-64と呼ぶ ことにします。
歴史的に見ると、Intelは伝統的なx86アーキテクチャと互換性のない新しい64ビットCPU(Itanium) を開発し、AMDなどのx86互換32ビットCPUを作っているメーカを振り切る戦略を取りました。一方、AMDはx86用に開発されてきた膨大なソフトウェアをそのまま活用できるように、32ビットと互換性を持つような64ビットCPU(AMD64) を開発しました。AMDのこの動きに対して、当初はIntelも32ビットと互換性を持った64ビットCPUを独自の設計で開発しようとしていましたが、32ビットと互換性を持ちながら機能が少しづつ異なる2種の64ビットCPUが生まれてしまうとソフトウェアの開発が大変になります。そう危惧したMicrosoftの圧力もあって、IntelはAMD64と互換の設計を採用し、当初はその機能をEM64T (Extended Memory 64bit Technology) 、 後にIntel64 と改名しました。
表1が、x86-64 CPUのもつ動作モードです。x86-64 CPUには、x86系CPUが持っていた従来からの3つの動作モードに加えて、64ビットモード と互換モード という2つのモードが追加されています。この追加された2つのモードはロングモード と総称され、それに合わせて従来からある3つの動作モードはレガシーモード と呼称されます。x86-64 CPUは、明示的にロングモードに移行しない限り、従来通りのレガシーモードで動作し、x86系CPUと互換で、32ビットバイナリだけでなく、BIOS等が利用する16ビットのリアルモードもそのまま動作します。
表1 x86-64 CPUの動作モード
動作モード 必要なOS アプリ側の対応
ロングモード 64ビットモード 64ビット用OS(x86-64用カーネル) 64ビット化が必要
互換モード 32ビットのまま動作可能
レガシーモード プロテクトモード 32ビット用OS(x86用カーネル) 32ビット用
仮想8086モード 16ビット用
リアルモード 16ビット用OS(MS-DOS等)
一方、ロングモードに入るとアドレス長が64ビットに拡張され、新たに追加された64ビットの汎用レジスタも使えるようになります。また、ロングモードの中には32ビットのバイナリを動かす互換モードも用意され、このモードを使えば既存の32ビットのバイナリもそのまま動かすことができるようになっています。
こう書けばいいことづくめのように聞こえますが、そもそもロングモードに入るためにはカーネル自体がx86-64用にコンパイルされて、x86-64のロングモードのことを知っていなければいけませんし、カーネルをx86-64用にコンパイルするためには、コンパイラやアセンブラがx86-64用に拡張されたコマンド(オペコード)を生成できる必要があります。すなわちx86-64 CPUの新機能を利用するには、まずx86-64用の開発環境を用意する 必要があるわけです。
x86-64用開発環境の選択
x86-64用の開発環境を作るにはいくつかの方法があります。一番簡単なのは既存のx86-64用のLinuxディストリビューションを導入することですが、それではあまり面白くないので、今回はx86環境上でx86-64用のバイナリを生成するクロスコンパイル の方法で開発環境を作りあげることにしました。
クロスコンパイルの場合、まず64ビット用のバイナリを生成できるコンパイラとアセンブラを32ビット環境上に用意し(クロスコンパイラ) 、そのコンパイラを使って64ビットネイティブな開発環境を作る、という流れになります。
この作業ではソフトウェアを構築していく順序が重要になるので、その手順について詳しく紹介している、CLFSプロジェクトのドキュメント を利用して作業することにしました。
CLFS(Cross Linux From Scratch) は、 Linux環境を全て手動でコンパイルして作りあげる方法を紹介しているLFSプロジェクト の一部で、 "Cross" の名前通り、x86環境上でMIPS用やPPC用のLinux環境をビルドする手順を詳細に紹介してくれています。
CLFSのx86-64用環境には、Pure64 と呼ばれる64ビット専用の環境とmultilib と呼ばれる32ビット/64ビット兼用の環境がありますが、今回は、既存の32ビットバイナリもそのまま動かせるmultilib環境を構築することにしました。
CLFSのPure64環境とmultilib環境ではライブラリの配置等が異なり、Pure64環境では/libや/usr/libに64ビット用のライブラリを置き、32ビット用のライブラリは/lib32や/usr/lib32というディレクトリに置きます。この場合、64ビット用のバイナリはデフォルトのままビルドできますが、32ビット用のバイナリは/lib32や/usr/lib32を見に行くように設定してビルドする必要があり、既存の32ビット用のバイナリは作り直さなければなりません。
一方、multilib環境の場合、32ビット用のライブラリは従来通り/libや/usr/libに配置し、新しい64ビット用のライブラリを /lib64 や /usr/lib64 というディレクトリに置くようになっています。この場合、32ビット用のバイナリは再ビルドしなくても使えますが、新しく64ビット用のバイナリを作る際には、参照するライブラリを/lib64や/usr/lib64に指定する設定が必要になります。
32ビット用の資産を使わずに64ビット用Linuxを作りあげる場合はPure64の設定の方が合理的ですが、Plamoの場合、32ビット用のパッケージがそれなりに充実しているので、それらはそのまま流用できた方が便利です。そこでmultilibの方法で64ビット環境をビルドすることにしました。
CLFS x86-64 multilib環境の構築
CLFSの手順は、CLFSのサイトやそこで配布されているドキュメントファイルで詳しく紹介されています。ただ、それらのドキュメントは誰でも「この手順に従えばビルドできる」ことを重視していて、全体の流れ(その作業の意味)が見えにくくなっているように感じました。そこで、以下では各手順の意味に重点を置きながらドキュメントを辿り、CLFSの環境構築の大きな流れを紹介してみます。今回参照したCLFSのドキュメントはCLFS-TRUNK-SVN-20100828 のx86_64-Multilib で、ドキュメントの各章をII-3やIII-5として参照することにします。
大きくまとめるとCLFSの環境構築の流れは、
32ビット環境上で64ビット用バイナリをコンパイルできる環境(クロスコンパイル環境)を構築
1)のクロスコンパイル環境を使って、64ビット用開発環境を構築
ブートするかchrootして 2)の環境をルートファイルシステムにした上で、64ビットなシステムを構築
という段階を踏みます。binutilsやGCCといった開発用のソフトウェアはこの各段階ごとにコンパイルし直さなければならないので、3回(GCCは最初にstatic link版を作るので計4回)コンパイルすることになります。
以下では、この各段階を CLFS のドキュメントを参照しながらもう少し詳しく見ていくことにします。
1) 32ビット環境上で64ビット用バイナリをコンパイルできる環境を構築
作業用パーティションと専用のアカウントの用意(II-2, II-4)
CLFSでは、既存のLinux環境上にCLFSの環境を構築しますが、元になるディストリビューションの影響を避けるために、専用のパーティションと必要最低限の環境変数のみを設定したユーザアカウント(clfs)を設け、構築作業はその環境で専用のユーザアカウントに su - clfs した上で実行します。以下ではこの専用パーティションを/clfsにマウントして作業を進めます。
環境構築に必要なソースコードやパッチファイルの入手先は一覧で整理されているので(II-3) 、それらをあらかじめダウンロードして用意しておきます。
32ビット用クロスコンパイル環境の構築(III-5)
まず、64ビットのバイナリを出力できる32ビット用のコンパイル環境(クロスコンパイル環境)を構築 します。そのためにはbinutilsやGCCをクロスコンパイル用にビルドすることになります。この際GCCのconfigureに与えるオプションは
--host=i586-cross-linux-gnu --build=i586-cross-linux-gnu --target=x86_64-unknown-linux-gnu
となります。この指定は、「 32ビット環境用に」( --host=i586-cross-linux-gnu) 、「 64ビット用のバイナリを出力する」( --target=x86_64-unknown-linux-gnu)バイナリを、「 32ビット環境上でビルドする」( --build=i586-cross-linux-gnu)という意味になります。
こうやって作ったバイナリは、作業用パーティションの/cross-tools/以下に集めて、次の段階はこのディレクトリにあるバイナリで作業することになります。/clfs ディレクトリにマウントされた/cross-tools/bin/ には下記のようなコマンドがインストールされ、それらは32ビット環境で動作する ようになっています。
% ls /clfs/cross-tools/bin
cloog* x86_64-unknown-linux-gnu-c++filt* x86_64-unknown-linux-gnu-nm*
file* x86_64-unknown-linux-gnu-cpp* x86_64-unknown-linux-gnu-objcopy*
ppl-config* x86_64-unknown-linux-gnu-g++* x86_64-unknown-linux-gnu-objdump*
..
% file /clfs/cross-tools/bin/x86_64-unknown-linux-gnu-gcc
/clfs/cross-tools/bin/x86_64-unknown-linux-gnu-gcc: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.0.0, not stripped
% ldd /clfs/cross-tools/bin/x86_64-unknown-linux-gnu-gcc
linux-gate.so.1 => (0xffffe000)
libc.so.6 => /lib/libc.so.6 (0xf7577000)
/lib/ld-linux.so.2 (0xf76ea000)
一方、このgccコマンドのターゲットはx86_64になっており、このコンパイラでソースコードをコンパイルすると64ビット用のバイナリが得られます。
% /clfs/cross-tools/bin/x86_64-unknown-linux-gnu-gcc -v
Using built-in specs.
COLLECT_GCC=/clfs/cross-tools/bin/x86_64-unknown-linux-gnu-gcc
COLLECT_LTO_WRAPPER=/clfs/cross-tools/bin/../libexec/gcc/x86_64-unknown-linux-gnu/4.5.1/lto-wrapper
Target: x86_64-unknown-linux-gnu
...
Thread model: posix
gcc version 4.5.1 (GCC)
この段階でビルドするソフトウェアは、カーネルのヘッダファイルとfileコマンド、ncursesのうちterminfo情報をコンパイルするためのticコマンド、後はアセンブラやリンカ等が含まれる binutils と C の標準ライブラリ(libc) 、CコンパイラであるGCCとGCCを作るために必要な数値演算用やループ最適化用ライブラリ群(GMP, MPFR, MPC, PPL, ClooG-PPL)だけです。
なお、この段階では64ビット用の共有ライブラリが使えないので、いったんstatic link版のGCCをビルドしておき、そのGCCで64ビット用のlibcライブラリをビルドした上で、再度dynamic link版のGCCをビルドする、という手順も入っています。
2) 1)の環境を使って、64ビットな開発専用環境を構築
64ビットな開発専用環境の構築(IV-6)
先に作ったクロスコンパイル用の環境(/clfs/cross-tools)を使って、/clfs/tools/ 以下に64ビット版の開発環境 を構築していきます。この際には /clfs/cross-tools/bin 以下をPATHの先頭に指定し、GCCやAS、LDなどは /clfs/cross-tools/以下のクロスコンパイル用ツールを明示してビルドしていきます。
この過程で再度binutilsやGCCを64ビット用にビルドしますが、その際のコンパイルオプションはこうなります。
--host=x86_64-unknown-linux-gnu --build=i586-cross-linux-gnu --target=x86_64-unknown-linux-gnu
先のクロスコンパイラ用の指定と比べると、--host の指定がx86-64用になっていて、この指定では「64ビット環境用に(--host=x86_64-unknown-linux-gnu)」 、「 64ビット用のバイナリを出力する(--target=x86_64-unknown-linux-gnu)」バイナリを、「 32ビット環境上でビルドする(--build=i586-cross-linux-gnu)」という意味になります。
CLFSの手順では、この段階でbashやbisonを始め、29種類のソフトウェアをビルド、インストールするようになっており、一つ前の手順でビルドした32ビット/64ビット用のlibcと含めて、30種類のソフトウェアで必要最小限の開発環境を構成しています。この段階でインストールされるソフトウェアは以下の通りになっています。
GMP-5.0.1, MPFR-3.0.0, MPC-0.8.2, PPL-0.10.2, CLooG-PPL-0.15.9
Zlib-1.2.5, Binutils-2.20.1, GCC-4.5.1, Ncurses-5.7, Bash-4.1
Bison-2.4.2, Bzip2-1.0.5, Coreutils-8.5, Diffutils-3.0, Findutils-4.4.2
File-5.04, Flex-2.5.35, Gawk-3.1.8, Gettext-0.18.1.1, Grep-2.6.3, Gzip-1.4
M4-1.4.14, Make-3.82, Patch-2.6, Sed-4.2.1, Tar-1.23, Texinfo-4.13a, Vim-7.2
XZ Utils-4.999.9beta
この段階で作成した/clfs/tools/binにインストールされるバイナリ群は64ビット用になっていて、32ビット環境からはライブラリのリンク状況等を確認できません。
% ls /clfs/tools/bin/
[* dd* infocmp* nl* s2p* uncompress*
a2p* ddate* infokey* nm* script* unexpand*
addr2line* df* infotocap@ nohup* scriptreplay* uniq*
...
% file /clfs/tools/bin/df
/clfs/tools/bin/df: ELF 64-bit LSB executable, x86-64, version 1 (GNU/Linux), dynamically linked (uses shared libs), for GNU/Linux 2.6.0, not stripped
% ldd /clfs/loop/tools/bin/df
not a dynamic executable
3) ブートするかchrootして 2)の環境をルートファイルシステムにした上で、64ビットなシステムを構築
次に、先ほど作った必要最小限の64ビット用開発環境で、実際に運用できる64ビットなLinuxシステムを構築していきます。その際には、既存の32ビット環境の影響を無くすために、新しく作った環境でブートするか、chrootでルートファイルシステムを切り替えることになります。CLFSでは両方の方法とも解説してあり、ブートする際はカーネルやsysvinitのビルド方法を(IV-7) 、chrootする際にはそのための設定方法などを詳述(IV-8)しています
カーネルを含めた必要なツールを用意してこのパーティションから開発用システムを起動すれば、既存のLinux環境から完全に独立した環境が用意できます。しかし、その環境ではコンソールしか利用できず、CLFSの手順をブラウザで表示しながら、指示されたコマンドをカット&ペーストして実行するような操作もできなくなるので、今回はchrootで環境を切り替える ことにしました。
当初はPlamo-4.73のデフォルトの環境からchrootで目的のディレクトリに移動しようとしましたが、bashが実行形式エラー になってしまいます。
% sudo chroot /clfs /tools/bin/bash
chroot: failed to run command `/tools/bin/bash': 実行形式エラー
「実行形式エラー」なんてのは見たことが無いなぁ、としばらく悩んだのですが、考えてみればこれは当然の結果で、現在動いている Plamo-4.73 のデフォルトカーネルはi586用になっているため、x86-64のバイナリは理解できません。64ビットなバイナリを実行できるロングモードに入るには、カーネル自体がx86-64用にビルドされていなければいけません。
それでは、と/usr/src/linuxにあるカーネルのソースコードに対して、ARCH=x86_64を明示してビルドしてみましたが、これもエラーになってしまいました。
# make ARCH=x86_64
scripts/kconfig/conf -s arch/x86/Kconfig
CHK include/linux/version.h
CHK include/linux/utsrelease.h
SYMLINK include/asm -> include/asm-x86
HOSTCC scripts/basic/docproc
CC kernel/bounds.s
kernel/bounds.c:1: error: code model 'kernel' not supported in the 32 bit mode
kernel/bounds.c:1: sorry, unimplemented: 64-bit mode not compiled in
make[1]: *** [kernel/bounds.s] エラー 1
make: *** [prepare0] エラー 2
このエラーメッセージも見たことが無いものだったのでしばらく悩みましたが、x86-64用にカーネルをビルドするためには、x86-64用のバイナリを出力できる環境が必要 になるわけで、カーネルソース一式を構築中のクロスコンパイル環境に移して、CLFSが指示しているようにCROSS_COMPILEオプションを指定してビルドして、無事x86-64用のカーネルが完成しました。
$ make ARCH=x86_64 CROSS_COMPILE=${CLFS_TARGET}-
CHK include/linux/version.h
CHK include/generated/utsrelease.h
CC kernel/bounds.s
GEN include/generated/bounds.h
CC arch/x86/kernel/asm-offsets.s
...
IHEX2FW firmware/keyspan_pda/xircom_pgs.fw
H16TOFW firmware/vicam/firmware.fw
$ file vmlinux
vmlinux: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, not stripped
このカーネルではIA32 Emulation 機能を有効 にしているので、ロングモードの互換モードを使って、まだ64ビット化していない Plamo-4.73 ベースの環境でも機能するはずです。そう考えて、新しくビルドしたカーネルとモジュールドライバを元の Plamo-4.73 環境に持っていって、新しくビルドしたカーネルから起動したところ、無事64ビット用カーネルで動作し、64ビット環境に chroot できるようになりました。
$ uname -a
Linux Corei7 2.6.35.7-plamo64 #1 SMP PREEMPT Tue Oct 26 00:35:06 JST 2010 x86_64 GNU/Linux
# chroot /clfs /tools/bin/bash
bash-4.1# ls /
bin cross-tools etc lib lost+found mnt proc sbin srv tmp usr
boot dev home lib64 media opt root sources sys tools var
bash-4.1# file /tools/bin/bash
/tools/bin/bash: ELF 64-bit LSB executable, x86-64, version 1 (GNU/Linux), dynamically linked (usesshared libs), for GNU/Linux 2.6.0, not stripped
bash-4.1# ldd /tools/bin/bash
linux-vdso.so.1 => (0x00007fffd416f000)
libncurses.so.5 => /tools/lib64/libncurses.so.5 (0x00007fb3c9616000)
libdl.so.2 => /tools/lib64/libdl.so.2 (0x00007fb3c9412000)
libgcc_s.so.1 => /tools/lib64/libgcc_s.so.1 (0x00007fb3c91fd000)
libc.so.6 => /tools/lib64/libc.so.6 (0x00007fb3c8e93000)
/tools/lib64/ld-linux-x86-64.so.2 (0x00007fb3c9865000)
元の32ビット環境では表示できなかったlddの出力も、新しくビルドした/tools/lib64以下のライブラリを指すと共に、それぞれのライブラリのエントリを示すアドレスが64ビット幅に拡張されていることが分かるでしょう。
次は、この64ビットの開発環境を使って、実際に運用可能な小規模Linux環境に必要なソフトウェアをコンパイルしていくことになりますが、だいぶ長くなったので今回はこのあたりまでにしておきましょう。