Solaris 64 ビット 開発ガイド

第 6 章 上級者向けトピック

この章では、64 ビット Solaris オペレーティングシステムについてさらに詳しく知りたいシステムプログラマ向けに、プログラミングに関するさまざまな情報を提供します。

64 ビット環境のほとんどの新機能は、一般の 32 ビットインタフェースを拡張したものですが、一部の新機能は 64 ビット環境に固有の機能です。

SPARC V9 ABI の特徴

64 ビットアプリケーションは、ELF64 実行可能およびリンク形式 (Executable and Linking Format) によって作成されます。この形式によって、大規模なアプリケーションおよびアドレス空間を完全に記述することができます。

SPARCV9。『SPARC Compliance Definition, Version 2.4』には、SPARC V9 ABI の詳細が含まれます。このマニュアルでは 32 ビットの SPARC V8 ABI と 64 ビット SPARC V9 ABI について説明しています。このマニュアルは、SPARC International の www.sparc.com から入手できます。

次に SPARC V9 ABI の特徴を示します。

スタックバイアス

SPARC V9。開発者にとって重要な SPARC V9 ABI の特徴の 1 つに、スタックバイアスがあります。64 ビット の SPARC プログラムでは、2047 バイトのスタックバイアスを、フレームポインタとスタックポインタの両方に追加して、スタックフレームの実際のデータを取得する必要があります。次の図を参照してください。

64 ビット の SPARC プログラムでの 2047 バイトのスタックバイアスの追加を示しています。

スタックバイアスについては、SPARC V9 ABI を参照してください。

SPARC V9 ABI のアドレス空間の配置

SPARC V9。64 ビットアプリケーションのアドレス空間の配置は、32 ビットアプリケーションのアドレス空間の配置に密接に関係しています。ただし、開始アドレスとアドレス指定の制限値は大きく変更されています。SPARC V8 と同様に、SPARC V9 のスタックはアドレス空間の上端から下方に広がり、ヒープは下端から上方にデータセグメントを拡張します。

以下の図は、64 ビットアプリケーションに与えられたデフォルトのアドレス空間を示します。「予約済み」となっているアドレス空間の領域は、アプリケーションからマップすることはできません。これらの制約は、将来のシステムで緩和される可能性があります。

代表的な SPARC V9 64 ビットアプリケーションのアドレス空間の配置を示しています。

上図の実際のアドレスは、ある特定のマシンの特定の実装を示しており、説明のためにだけ掲載してあります。

SPARC V9 ABI のテキストおよびデータの配置

デフォルトでは、64 ビットプログラムは開始アドレス 0x100000000 にリンクされます。プログラム全体は、テキスト、データ、ヒープ、スタック、および共有ライブラリを含めて 4G バイトを超えるアドレスに存在します。これは、64 ビットプログラムが正しいことを検証するのに役立ちます。たとえばプログラムが関連するポインタの上位 32 ビットを切り落としてしまうと、そのプログラムはアドレス空間の下方の 4G バイトの部分へアクセスしようとして失敗します。

64 ビットプログラムは 4G バイトを超える位置でリンクされますが、リンカーのマップファイルを使用し、コンパイラまたはリンカーに -M オプションを指定して、4G バイト未満の位置でリンクすることも可能です。4G バイト未満で 64 ビット SPARC プログラムをリンクするためのリンカーマップファイルは、/usr/lib/ld/sparcv9/map.below4G にあります。

詳細は、ld(1) のリンカーのマニュアルページを参照してください。

SPARC V9 ABI のコードモデル

SPARC V9。コンパイラには、性能の向上や、64 ビット SPARC プログラムでのコードサイズを小さくするなど、さまざまな目的に合わせた各種のコードモデルがあります。コードモデルは以下の要素で決定します。

次の表は、64 ビット SPARC プログラムで使用できる各種コードモデルを示したものです。

表 6–1 コードモデルの説明 SPARC V9

コードモデル 

位置決め方法 

コードサイズ 

位置 

外部オブジェクト参照モデル 

abs32

絶対 

2G バイト未満 

下部 (アドレス空間の下位 32 ビット) 

なし 

abs44

絶対 

2G バイト未満 

中央 (アドレス空間の下位 44 ビット) 

なし 

abs64

絶対 

2G バイト未満 

任意 

なし 

pic

位置に依存しないコード 

2G バイト未満 

任意 

スモール (1024 以下の外部オブジェクト) 

PIC

位置に依存しないコード 

2G バイト未満 

任意 

ラージ (2**29 以下の外部オブジェクト) 

スモールコードモデルを使用すると、命令シーケンスを短くできる場合があります。絶対コード内で静的データ参照を行うのに必要な命令の数は、abs32 コードモデルの場合が最も少なく、abs64 が最も多く、abs44 がその中間になります。同様に、pic コードモデルは、PIC コードモデルよりも少ない命令で静的データ参照を行います。その結果、コードモデルが小さいほどコードサイズも小さくなり、ラージコードモデルのような、より完全な機能性を必要としないプログラムの性能が向上します。

使用するコードモデルを指定するには、-xcode=<model> コンパイラオプションを使用する必要があります。現在、コンパイラは 64 ビットオブジェクトに対し、デフォルトで abs64 モデルを使用します。コードは、abs44 コードモデルの使用により最適化できます。より少ない命令を使用して、現在の UltraSPARC プラットフォームがサポートする 44 ビットのアドレス空間を利用できます。

コードモデルについては、SPARC V9 ABI およびコンパイラのマニュアルを参照してください。


注 –

abs32 コードモデルでコンパイルしたプログラムは、-M /usr/lib/ld/sparcv9/map.below4G オプションを使用して、4G バイトよりも下方にリンクする必要があります。


AMD64 ABI の特徴

64 ビットアプリケーションは、ELF64 実行可能およびリンク形式 (Executable and Linking Format) によって作成されます。この形式によって、大規模なアプリケーションおよびアドレス空間を完全に記述することができます。

次に AMD ABI の特徴を示します。

amd64 psABI 草案文書『System V Application Binary Interface, AMD64 Architecture Processor Supplement』(草案バージョン 0.92、2004 年 9 月 9 日) を参照してください。

amd64 アプリケーションのアドレス空間の配置

64 ビットアプリケーションのアドレス空間の配置は、32 ビットアプリケーションのアドレス空間の配置に密接に関係しています。ただし、開始アドレスとアドレス指定の制限値は大きく変更されています。SPARC V9 と同様に、amd64 のスタックはアドレス空間の上端から下方に広がり、ヒープは下端から上方にデータセグメントを拡張します。

以下の図は、64 ビットアプリケーションに与えられたデフォルトのアドレス空間を示します。「予約済み」となっているアドレス空間の領域は、アプリケーションからマップすることはできません。これらの制約は、将来のシステムで緩和される可能性があります。

代表的な amd 64 ビットアプリケーションのアドレス空間の配置を示しています。

上図の実際のアドレスは、ある特定のマシンの特定の実装を示しており、説明のためにだけ掲載してあります。

整列の問題

データ構造体の 32 ビット long long 要素の整列に関連してもう一つの問題があります。すなわち、i386 アプリケーションは 32 ビット境界で long long 要素を整列するだけですが、amd64 ABI は long long 要素を 64 ビット境界に配置するので、データ構造体に大きな隙間が生じる可能性があります。SPARC は 32 ビットと 64 ビットの両方で long long 項目が 64 ビット境界に整列されており、この点が異なります。

次の表は、設計アーキテクチャに対するデータ型整列を示します。

表 6–2 データ型整列

アーキテクチャ 

long long 

double 

long double 

i386 

amd64 

16 

sparcv8 

sparcv9 

16 

SPARC システムで LP64 に対し問題がないと思われるコードであっても、整列の違いのために、32 ビットと 64 ビットのプログラミング環境間でデータ構造体をコピーする際に問題が生じる可能性があります。そのようなプログラミング環境には、デバイスドライバ ioctl ルーチン、doors ルーチンや、その他の IPC メカニズムがあります。整列の問題は、これらのインタフェースを慎重にコーディングしたり、#pragma pack または _Pack 指示語を適切に使用することによって回避できます。

プロセス間通信

次に示すプロセス間通信 (IPC) プリミティブは、従来どおり 64 ビットプロセスと 32 ビットプロセスとの間で動作します。

これらのすべてのプリミティブは、32 ビットプロセスと 64 ビットプロセスとの間の通信を可能にしますが、プロセス間で交換されているデータがそれらすべてのプロセスで正しく解釈されることを、明確な手順によって確認する必要がある場合があります。たとえば、long 型の変数を含む C データ構造体で記述されるデータを 2 つのプロセスが実際に共有するには、32 ビットプロセスがこの変数を 4 バイト量とみなし、64 ビットプロセスはこの変数を 8 バイト量とみなすということを認識する必要があります。

この相違を取り扱う 1 つの方法は、両プロセス間で意味をなすようにデータが完全に同じサイズであることを保障することです。int32_tint64_t のような固定幅型を使ってデータ構造を構成してください。整列に関しても配慮が必要です。共有されるデータ構造体は、パディングを追加したり、#pragma pack_Pack などのコンパイラ指示語を使用して再パックする必要のある場合があります。「整列の問題」を参照してください。

システムで提供される派生型に対応する派生型の一群が <sys/types32.h> にあります。これらの派生型は、32 ビットシステムの基本型と同じ符号、同じサイズですが、ILP32 および LP64 のコンパイル環境でサイズが変わらないように定義されています。

32 ビットプロセスと 64 ビットプロセスとの間でポインタを共有するのは、さらに困難です。まずポインタのサイズが異なるということがあります。またそれ以上に重要なことは、既存の C の使用法に 64 ビット整数 (long long) はありますが、64 ビットポインタには 32 ビット環境に相当するものはない、ということです。64 ビットプロセスが 32 ビットプロセスとデータを共有できるようにするため、32 ビットプロセスは共有データのうち、4G バイトまでしか一度に「見る」ことはできません。

XDR ルーチンの xdr_long(3NSL) は問題と思われるかもしれません。しかし、これは既存のプロトコルとの互換性を持たせるために従来どおり 32 ビットとして取り扱われます。64 ビットバージョンのルーチンが 32 ビットに格納できない long 値をコード化するように要求された場合、そのコード化処理は失敗します。

ELF とシステム生成ツール

64 ビットバイナリは、ELF64 形式でファイルに格納されます。この ELF64 形式は、ほとんどのフィールドが完全 64 ビットアプリケーションを格納するために拡張されていることを除いて、ELF32 形式に類似しています。ELF64 ファイルは elf(3ELF) API、たとえば elf_getarhd(3ELF) を使って読むことができます。

ELF ライブラリ elf(3ELF) の 32 ビットおよび 64 ビットのバージョンは、それぞれ ELF32 および ELF64 形式と、対応する API をサポートします。これによりアプリケーションは、32 ビットシステムまたは 64 ビットシステム (64 ビットプログラムを実行するには 64 ビットシステムが必要) から、両ファイル形式を構築、読み込み、あるいは修正ができるようになります。

さらに、Solaris では GELF (Generic ELF) インタフェースを提供し、プログラマが 1 つの共通 API を使用して両方の ELF 形式を操作できるようにしています。詳細は、elf(3ELF) のマニュアルページを参照してください。

ar(1)nm(1)ld(1)、および dump(1) を含む、すべてのシステム ELF ユーティリティが両方の ELF 形式を使用できるように変更されています。

/proc インタフェース

/proc インタフェースは、32 ビットアプリケーションおよび 64 ビットアプリケーションの両方で利用できます。32 ビットアプリケーションは、他の 32 ビットアプリケーションの状態を調べたり制御したりできます。したがって、既存の 32 ビットデバッガを 32 ビットアプリケーションのデバッグに使用できます。

64 ビットアプリケーションは、他の 32 ビットまたは 64 ビットアプリケーションの状態を調べたり制御したりできます。ただし 32 ビットアプリケーションでは、64 ビットアプリケーションを制御できません。これは、32 ビット API では 64 ビットプロセスの完全な状態を記述することができないからです。このため、64 ビットアプリケーションをデバッグするには、64 ビットのデバッガが必要となります。

sysinfo(2) の拡張

Solaris S10 オペレーティングシステムの新しい sysinfo(2) サブコードによって、アプリケーションは使用可能な命令セットアーキテクチャに関する詳細な情報を確認できます。

たとえば、SI_ARCHITECTURE_64 サブコードを使用することによって、システム上に 64 ビット ABI があればその名前がわかります。詳細は、sysinfo(2) を参照してください。

libkvm/dev/ksyms

64 ビットの Solaris システムは、64 ビットカーネルを使って実装されています。カーネルの内容を直接調べたり変更するアプリケーションは、64 ビットアプリケーションに変換し、64 ビットバージョンのライブラリとリンクしなければなりません。

このような変換と修正を行う前に、まずアプリケーションがカーネルのデータ構造を直接知る必要があるかどうかを検討した方がよいでしょう。プログラムが最初に移植されるかあるいは新規に作成された後に、システムコールを使って必要なデータを抽出するインタフェースが Solaris プラットフォームで利用可能になって追加された、という可能性があります。この場合は、最も一般的な代替 API として sysinfo(2)kstat(3KSTAT)sysconf(3C)proc(4) を参照してください。これらのインタフェースが kvm_open(3KVM) の代わりに使用できるのなら、移植性を最大限にするために、それらを使用してアプリケーションを 32 ビットのままにしてください。さらに利点として、これらの API のほとんどは処理が速く、カーネルメモリーにアクセスするときと同じセキュリティ特権を必要としないことがあります。

32 ビットバージョンの libkvm は、64 ビットのカーネルクラッシュダンプに対して kvm_open(3KVM) を使用しようとしたときに異常終了します。同様に、64 ビットバージョンの libkvm は、32 ビットのカーネルクラッシュダンプに対して kvm_open(3KVM) を使用しようとしたときに異常終了します。

カーネルは 64 ビットプログラムなので、カーネルのシンボルテーブルを直接調べるために /dev/ksyms を開くアプリケーションは、ELF64 形式を理解するように機能を拡張する必要があります。

kvm_read() または kvm_write() へのアドレス引数がカーネルアドレスであるかユーザーアドレスであるかが曖昧であることは、64 ビットアプリケーションおよびカーネルではさらに問題となります。現在でもまだ kvm_read()kvm_write() を使用している libkvm を利用するアプリケーションはすべて、kvm_read()kvm_write()kvm_uread()kvm_uwrite() のルーチンを使用するようにする必要があります。これらのルーチンは、Solaris 2.5 から利用できるようになっています。

/dev/kmem または /dev/mem を直接読むアプリケーションは、従来どおり実行できます。ただし、これらのデバイスから読み込んだデータを解釈しようとすると、問題が発生することがあります。これは、データ構造体のオフセットおよびサイズが 32 ビットと 64 ビットのカーネル間で確実に異なるためです。

libkstat カーネル統計情報

多くのカーネル統計情報のサイズは、カーネルが 64 ビットあるいは 32 ビットプログラムのどちらであるかということとは関係ありません。名前付き kstat (kstat(3KSTAT) のマニュアルページを参照) がエクスポートするデータ型は自己記述型で、符号付きまたは符号なしの 32 ビットまたは 64 ビットカウンタデータを、適切なタグを付けてエクスポートします。したがって、libkstat を使用するアプリケーションは、64 ビットカーネル上で正常に動作させるために 64 ビットアプリケーションに変換する必要はありません。


注 –

名前付き kstats を作成および管理するデバイスドライバを修正するときは、エクスポートしようとする統計情報のサイズを、固定幅の統計データ型を使って 32 ビットおよび 64 ビットカーネル間で不変にすることをお薦めします。


stdio への変更

64 ビット環境では stdio 機能が拡張されて、256 を超える数のストリームを同時に開くことができるようになりました。32 ビットの stdio 機能では、従来どおり 256 を超える数のストリームを同時に開くことはできないという制限があります。

64 ビットアプリケーションが、FILE データ構造体のメンバーにアクセスできることに依存しないようにしてください。実装に固有な構造体メンバーに直接アクセスしようとすると、コンパイルエラーとなります。この変更で既存の 32 ビットアプリケーションが影響を受けることはありませんが、このように構造体のメンバーを直接使用する方法は、すべてのコードから取り除くべきです。

FILE 構造体には長い歴史があり、この構造体の中身を参照してストリームの状態に関する付加的な情報を収集するアプリケーションもあります。64 ビットのこの構造体は参照できないようになっているため、新しいルーチン群が 32 ビットの libc と 64 ビットの libc に加えられ、その結果、実装の内部に依存することなく同じ状態を調べることができるようになりました。たとえば __fbufsize(3C) のマニュアルページを参照してください。

パフォーマンスの問題

64 ビットのパフォーマンスの長所および短所について説明します。

64 ビットアプリケーションの長所

64 ビットアプリケーションの短所

システムコールの問題

システムコールの問題について説明します。

EOVERFLOW の意味

戻り値の EOVERFLOW は、カーネルからの情報を渡すために使うデータ構造体の 1 つまたは複数のフィールドが小さすぎて値を格納できない場合に、常にシステムコールから返されます。

現在、64 ビットカーネル上の大きなオブジェクトに遭遇したとき、多くの 32 ビットシステムコールは EOVERFLOW を返します。これまでも、大規模ファイルを扱う場合には同様でしたが、daddr_tdev_ttime_t、およびその派生型の struct timevaltimespec_t が現在では 64 ビットを格納するため、32 ビットアプリケーションにおいては、従来よりも EOVERFLOW が返される場合が増えます。

ioctl() に関する注意

一部の ioctl(2) 呼び出しは、これまでうまく指定されていませんでした。ioctl() はコンパイル時の型検査では検出されません。そのため、ioctl() は追跡が困難なバグの原因になる可能性があります。

2 つの ioctl() 呼び出しを考えてみてください。一方は 32 ビット量 (IOP32) へのポインタを操作し、もう一方は long (IOPLONG) へのポインタを操作します。

次のコード例は、32 ビットアプリケーションの一部として動作します。

	int a, d;
	long b;
	...
	if (ioctl(d, IOP32, &b) == -1)
			return (errno);
	if (ioctl(d, IOPLONG, &a) == -1)
			return (errno);

このコードが 32 ビットアプリケーションの一部としてコンパイルされ、実行されるとき、どちらの ioctl(2) 呼び出しも正しく動作します。

このコードが 64 ビットアプリケーションとしてコンパイルされ、実行されるとき、どちらの ioctl() 呼び出しも正常終了しますが、正しく動作しません。最初の ioctl() は、大きすぎるコンテナ (データを格納するメモリー領域) を渡します。その結果、ビッグエンディアン実装の場合は、カーネルは 64 ビットワードの誤った部分へ、あるいは誤った部分からコピーしようとします。リトルエンディアン実装の場合でも、コンテナには上位 32 ビットに意味のない値が含まれます。2 番目の ioctl() は、コピー量が多すぎるため、正しくない値を読み込むか、あるいはユーザースタック内の隣接する変数を破壊してしまいます。