Solaris 7 64 ビット 開発ガイド

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

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

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

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

SPARC Compliance Definition, Version 2.4』には、SPARC V9 ABI の詳細が含まれます。32 ビットの SPARC V8 ABI と 64 ビット SPARC V9 ABI について説明しています。この文書は、www.sparc.com にアクセスすると、SPARC International から入手できます。

汎用 64 ビット ABI の特徴

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

SPARC V9 ABI の特徴

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

アドレス空間の配置

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

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

Graphic

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

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

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

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

プロセス間通信

次に示すプロセス間通信 (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(4) は、64 ビットのカーネルクラッシュダンプに対して kvm_open(3K) を使用しようとしたときに異常終了します。同様に、64 ビットバージョンの libkvm(4) は、32 ビットカーネルクラッシュダンプに対して kvm_open(3K) を使用しようとしたときに異常終了します。

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

kvm_read(3K) または kvm_write(3K) へのアドレス引数がカーネルアドレスであるかユーザーアドレスであるかが曖昧であることは、64 ビットアプリケーションおよび カーネルではさらに問題となります。現在でもまだ kvm_read(3K)kvm_write(3K) を使っている libkvm(4) を利用するアプリケーションはすべて、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(2) に関する注意

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

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

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


例 6-1

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