Solaris 64 ビット 開発ガイド

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

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

アプリケーションに関する新しい情報

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

64 ビット: ABI の特徴

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

ABI の特徴: SPARC V9

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 ABI の特徴の 1 つに、スタックバイアスがあります。64 ビットの SPARC プログラムでは、2047 バイトのスタックバイアスを、フレームポインタとスタックポインタの両方に追加して、スタックフレームの実際のデータを取得する必要があります。以下の図を参照してください。

Graphic

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

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

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

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

Graphic

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

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

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

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

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

コードモデル

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

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

表 6-1 コードモデルの説明

コードモデル 

位置決め方法 

コードサイズ 

位置  

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

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 バイトよりも下方にリンクする必要があります。


プロセス間通信

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

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

この相違を取り扱う 1 つの方法は、両プロセス間で意味をなすようにデータが完全に同じサイズであることを保障することです。int32_tint64_t のような固定幅型を使ってデータ構造を構成してください。

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

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

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

ELF とシステム生成ツール

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

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

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

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 ビットのデバッガが必要となります。

libkvm/dev/ksyms

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

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

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

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

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

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

libkstat

多くのカーネル統計情報のサイズは、カーネルが 64 ビットあるいは 32 ビットプログラムのどちらであるかということとは関係ありません。名前付き kstat (kstat(3K) のマニュアルページを参照) がエクスポートするデータ型は自明で、符号付きまたは符号なしの 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(3S) のマニュアルページを参照してください。

パフォーマンスの問題

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() は、コピー量が多すぎるため、正しくない値を読み込むか、あるいはユーザースタック内の隣接する変数を破壊してしまいます。