[ 前のページ ] [ 次のページ ] [ 目次 ] [ 索引 ] [ DOC Home ]

8 OpenVMS Alpha 64ビットAPIガイドライン

本章では,OpenVMS Alpha 64ビット仮想アドレッシングをサポートする, 64ビット・インタフェースを開発する際のガイドラインについて説明します。 独自の64ビット・アプリケーション・プログラミング・インタフェースを開発しているアプリケーション・プログラマにとって, このガイドラインは大変有用です。

本章で推奨するガイドラインは,難しく厳密な規則ではありません。 適切なプログラミングの例を紹介しながら,ガイドラインを説明します。

Cポインタ・プラグマについての詳細は, 『DEC C User's Guide for OpenVMS Systems』を参照してください。


8.1 クォドワード/ロングワード引数ポインタのガイドライン

OpenVMS Alpha 64ビット・アドレッシングのサポートは,アプリケーション・プログラムが 64ビット・アドレス空間のデータへアクセスすることを認めています。 そのためアプリケーションの中では, 32ビット符合拡張値でないポインタ (64ビット・ポインタ)がより一般的になります。既存の32ビット APIも引き続きサポートされるため, プログラマは64ビット・ポインタの存在を特に注意する必要があります。

たとえば64ビット・アドレスを,誤って 32ビット・アドレスしか処理できないルーチンに渡す可能性が考えられます。また, 新しいAPIが64ビット・ポインタをデータ構造に埋め込む場合も考えられます。 このようなポインタは,新しいデータ構造の中では符号拡張の 32ビット値として存在しますが,最初は 32ビット・アドレス空間のポイントに制限されます。

どのルーチンも,32ビット・アドレスに代わって 64ビット・アドレスが渡される場所では, プログラミング・エラーに注意する必要があります。 このようなチェックは符号拡張チェックと呼ばれるもので,ビット31 の値に一致した状態で,アドレスの上位32ビットがすべて0,またはすべて 1であることを確認します。このチェックは, この制限を適用しているルーチン・インタフェースで実行できます。

ルーチン・インタフェースを新しく定義するときは, 32ビット・ソース・モジュールから, ルーチンの呼び出しを簡単にプログラムできるように考慮しなければなりません。 また,64ビット・アドレッシングのサポートをもともと意図している言語だけでなく, すべてのOpenVMSプログラミング言語で作成された呼び出しを考慮しなければなりません。 新規ルーチンの 32ビット呼び出し側に対して不慣れなプログラミングを強いることを防ぐために, 64ビット呼び出し側だけでなく 32ビット呼び出し側にも配慮しなければなりません。


32ビット・アドレス空間(P0/P1/S0/S1)での常駐が禁止されている参照渡しの引数は, その参照アドレスを符号拡張チェックする必要がある

OpenVMS呼び出し規則では,ルーチンに渡される32ビット値は, ルーチンが呼び出される前に,64ビットに符号拡張されることが要求されます。 このため,呼び出されるルーチンは,常に64ビット値を受け取ります。 32ビット・ルーチンは,引数に対する参照を符号拡張チェックしない限り, その呼び出し側が32ビット・アドレスで正しくルーチンを呼び出したかどうかを判断できません。

データがディスクリプタでルーチンに渡される場合には, ディスクリプタへの参照に対してもこの符号拡張チェックが適用されます。

符号拡張チェックが失敗した場合,呼び出されたルーチンは,エラー状態 SS$_ARG_GTR_32_BITSを返します。

あるいは,呼び出されるルーチンが,エラーがない状態で 64ビットの位置に渡されるデータを受け取ることが求められる場合, 符号拡張チェックが失敗したときは,データを 32ビット・アドレス空間にコピーすることができます。ルーチンがデータをコピーする 32ビット・アドレス空間は,ローカル・ルーチン記憶域(つまり, 現在のスタック)です。ローカル記憶域以外の 32ビット位置にデータがコピーされる場合, メモリ・リークおよびリエントラントについて考慮する必要があります。

新規ルーチンを開発する場合, コードへのポインタおよび新規ルーチンへ渡されるすべてのデータ・ポインタは, できるだけ64ビット・アドレス空間を利用することが望まれます。これは, データがルーチンの場合,またはプログラマやコンパイラ,リンカが通常は 64ビット・アドレス空間に配置しない静的データ と一般に考えられる場合であっても同様です。コードと静的データが 64ビット・アドレス空間の中でサポートされる場合, このルーチンを改めて変更する必要はありません。


32ビット・ディスクリプタ引数が 32ビット・ディスクリプタであることを確認する必要がある

ディスクリプタを受け取るルーチンは,32ビットおよび 62ビット・ディスクリプタ形式を区別するフィールドをチェックしなければなりません。 64ビット・ディスクリプタを受け取ると,ルーチンはエラーを返します。

既存の大半の32ビット・ルーチンは, 64ビット・ディスクリプタが間違って指定されると, 次の理由でエラー状態SS$_ACCVIOを返します。

図 8-1 32ビット・ディスクリプタ :(クリックで表示)


64ビット・ディスクリプタで渡される引数を受け取るルーチンは, 64ビット・ディスクリプタと同様に 32ビット・ディスクリプタに対処する必要がある

新規ルーチンは,同じルーチンの中で32ビット・ディスクリプタと 64ビット・ディスクリプタに対処します。同じ引数で32ビット・ディスクリプタまたは 64ビット・ディスクリプタを指すことができます。オフセット0での 64ビット・ディスクリプタMBOワードは,値が1であることをテストし,オフセット4 での64ビット・ディスクリプタ MBMOロングワードは -1であることをテストすることによって,32ビット・ディスクリプタと 64ビット・ディスクリプタを区別します。

32ビット・ディスクリプタに加えて 64ビット・ディスクリプタの処理を目的として変換されている,既存の 32ビット・ルーチンを考えてみます。入力ディスクリプタが 64ビット・ディスクリプタであると判断されると, 64ビット・ディスクリプタが指すデータを,まず 32ビットのメモリ位置にコピーした上で,32ビット・メモリの中で 32ビット・ディスクリプタが作成されます。この新しい 32ビット・ディスクリプタを既存の32ビット・コードに渡すことで, ルーチン内部での変更はこれ以上必要ありません。


32ビット項目リスト引数が 32ビット項目リスト引数であることを確認する必要がある

項目リストは, item_list_2およびitem_list_3という2種類の形式で定義されます。 item_list_2形式の項目リストは2つのロングワードで構成されており, 先頭のロングワードには長さと項目コードのフィールドが含まれ, 2番目のロングワードには通常バッファ・アドレスが含まれています。

これに対してitem_list_3形式の項目リストは3つのロングワードで構成されています。 先頭のロングワードには長さと項目コードのフィールドが含まれ,2番目と 3番目のロングワードには, 通常バッファ・アドレスおよび戻り長アドレス・フィールドが含まれています。

32ビット項目リストが2種類存在するため, 64ビット項目リストも2種類定義されています。新しく加えられたitem_list_64a およびitem_list_64bは,32ビットの項目リストにそれぞれ対応します。 どちらの形式の項目リストも,オフセット0および4の位置に,MBOおよび MBMOフィールドをそれぞれ含んでいます。また,両方の項目リストとも, ワード・サイズの項目コード・フィールド,クォドワード・サイズの長さフィールド, およびクォドワード・サイズのバッファ・アドレス・フィールドをそれぞれ含んでいます。 なお,item_list_64b形式の項目リストは, これとは別にクォドワード・サイズの戻り値長アドレス・フィールドも含んでいます。 戻り値長は64ビットです。

項目リストを受け取るルーチンは,フィールドをテストして,これが 32ビット項目リストと64ビット項目リストのどちらであるかを区別しなければなりません。 64ビット項目リストを受け取った場合,ルーチンはエラーを返します。

既存の大半の32ビット・ルーチンは, 64ビット項目リストが間違って渡されたことを次の理由で判別した場合,エラー状態 SS$_ACCVIOを返します。またはこれをシグナル通知します。

図 8-2 item_list_64a :(クリックで表示)

図 8-3 item_list_64b :(クリックで表示)


64ビット項目リスト引数で渡される引数を受け取るルーチンは, 64ビット項目リスト同様32ビット項目リストにも対処する必要がある

新規ルーチンは,同一ルーチン内で32ビット項目リストと 64ビット項目リストに対処しなければなりません。同じ引数が, 32ビット項目リストと64ビット項目リストのどちらも指すことができます。 64ビット項目リストのオフセット0にあるMBOワードは1でなければならず, 64ビット項目リストのオフセット4にあるMBMOロングワードは -1でなければなりません。そのため,これをテストすることによって, 64ビット項目リストと32ビット項目リストを区別します。


ポインタの参照渡しを避ける

特定のメモリ管理ルーチンなどで, ポインタを参照渡しする必要がある場合,ポインタは64ビットで定義します。

32ビット・ポインタと64ビット・ポインタが混在すると, 64ビット・ポインタが期待されている場所に,呼び出し側が間違って 32ビット・ポインタを参照渡しするなどの,プログラミング・エラーを引き起こします。

プログラマによってロングワードしか割り当てられていないのに, 呼び出されたルーチンが64ビット・ポインタを読み込むと, ルーチンの中で間違ったアドレスが使用されることになります。

また,呼び出されたルーチンが64ビット・ポインタを返すと, プログラマによって割り当てられているロングワードに対して 64ビット・アドレスが書き込まれるため,データの破損が発生します。

ポインタを参照渡しされる既存のルーチンには, 64ビット・サポート用の新規インタフェースが必要です。 旧ルーチン・インタフェースは, 32ビット・メモリ位置の中でポインタをこれまでのように渡し, これに対して新規ルーチン・インタフェースは, 64ビット・メモリ位置でポインタを渡します。同じインタフェースを保持した上で, 64ビット・ポインタを渡すと,既存のプログラムに異常が発生します。

例: SYS$CRETVA_64サービスで使用される戻り仮想アドレスは, 参照渡しでポインタを渡すことのできる例です。P0空間および P1空間の中で作成される仮想アドレスは,64ビットすべてが返されますが, 意味があるのは32ビットだけであることが保証されています。SYS$CRETVA_64は 64ビット空間にアドレス空間を作成し,64ビット・アドレスを返すこともできます。 64ビット・アドレスが返るため,返される値は常に64ビットであることが必要です。

メモリ割り当てルーチンは,可能な場合,値(つまりR0) で割り当てられるデータへのポインタを返します。C割り当てルーチン,malloc, calloc,reallocがこの例です。

メモリ管理ルーチンでないルーチンの新規インタフェースで, 出力引数を定義してアドレスを受け取ることは避けてください。 64ビット・サブシステムがメモリを割り当て, 32ビット呼び出し側に対してポインタを出力引数で戻すと,問題が発生します。 呼び出し側は,64ビット・ポインタをサポートまたは指定できない可能性があります。 あるデータへのポインタを返す代わりに, 呼び出し側はバッファへのポインタを用意して, 呼び出されたルーチンがこのユーザ・バッファにデータをコピーすることが望まれます。

参照渡しされる64ビット・ポインタは,ルーチンへの呼び出しが64ビット言語または 32ビット言語で作成できるような環境で定義する必要があります。 64ビット・ポインタはすべての呼び出し側によって渡される必要があることを, 明確に指示しなければなりません。


特に要求されない限りルーチンは64ビット・アドレスを返す必要はない

呼び出し側で 64ビット・アドレスを処理できることが確実でない限り,メモリを割り当て, その呼び出し側に対してアドレスを返すルーチンは,常に 32ビット・アドレス・メモリを割り当てることに,十分に注意してください。 これは,関数の戻り値と出力パラメータの両方に当てはまります。この規則は, 64ビット・アドレスを求めていないアプリケーションに,64ビット・アドレスが 侵入することを防ぐものです。この結果, 呼び出し可能なライブラリを開発しているプログラマは, 特に注意してこの規則に従う必要があります。

既存のルーチンが, 割り当てられているメモリのアドレスをルーチン値として返す場合を考えます。 呼び出し側が64ビットを処理できることがわかっていて, ルーチンが入力パラメータを受け取った場合, 64ビット・アドレスを返すことに何も問題はありません。これ以外の場合は, 引き続き32ビット符号拡張アドレスを返さなければなりません。後者の場合, 64ビットを処理できる呼び出し側が 64ビット・メモリの割り当てを優先するのであれば,既存のバージョンに代わって, ルーチンの新バージョンを提供できます。

例:文字列ディスクリプタを操作するLIBRTL内のルーチンは, 渡されたディスクリプタが新しい64ビット形式であれば,呼び出し側が 64ビットを処理できると判断します。この場合は,文字列データに 64ビット・メモリを割り当てても安全です。これ以外の場合は, 32ビット・アドレス・メモリだけを引き続き使用します。


パブリック・インタフェースのデータ構造では埋め込みポインタを避ける

新規インタフェースの新しい構造で埋め込みポインタが必要な場合, (クォドワード・アラインされている) 64ビット・ポインタ用の構造の中で記憶域を用意します。呼び出されたルーチンは, 場合によって構造からポインタを読み込む必要がありますが,単純に 64ビット全体を読み込みます。

ポインタが32ビット符号拡張アドレスの制約を受けている場合(たとえば, ポインタが32ビット・ルーチンに渡される場合),ルーチンの入り口で, 64ビット・ポインタについて符号拡張チェックが実行されます。 符号拡張チェックが失敗すると,エラー状態SS$_ARG_GTR_32_BITS が呼び出し側に返されます。または 64ビット・アドレス空間に常駐しているデータが検出されると,これが 32ビット・アドレス空間にコピーされます。

新しい構造は,64ビットの呼び出し側も32ビットの呼び出し側も, 余分なコードを使用しないで済むように定義する必要があります。構造では, 64ビットの呼び出し側を対象とするクォドワード・フィールドと, 32ビットの呼び出し側を対象とする 2つのロングワード・フィールドが互いに重なり合って提供されます。 先頭のロングワードは32ビット・ポインタ・フィールドで,次のロングワードは MBSE (must be sign-extension)フィールドです。32ビットの呼び出し側の大半では, MBSEフィールドは0になります。これは,ポインタが 32ビット・プロセス空間アドレスとなるためです。ここで重要なのは,ポインタを 64ビット値として定義し, 32ビットの呼び出し側に対してクォドワード全体を入力する必要があることを明確にすることです。

次の例では,64ビットと32ビットの両方の呼び出し側が,関数routine を呼び出すときに,block構造にポインタを渡し,同じ関数プロトタイプを使用します (dataは別のモジュールで定義され,その構造は不定であるものとします)。

     #pragma required_pointer_size save
     #pragma required_pointer_size 32

     typedef struct block {
         int blk_l_size;
         int blk_l_flags;
         union  {
     #pragma required_pointer_size 64
             struct data *blk_pq_pointer;
     #pragma required_pointer_size 32
             struct  {
                 struct data *blk_ps_pointer;
                 int blk_l_mbse;
                 } blk_r_long_struct;
             } blk_r_pointer_union;
         } BLOCK;
     #define blk_pq_pointer     blk_r_pointer_union.blk_pq_pointer
     #define blk_r_long_struct  blk_r_pointer_union.blk_r_long_struct
     #define blk_ps_pointer     blk_r_long_struct.blk_ps_pointer
     #define blk_l_mbse         blk_r_long_struct.blk_l_mbse
     
     /* Routine accepts 64-bit pointer to the "block" structure */
     #pragma required_pointer_size 64
     int routine(struct block*);

     #pragma required_pointer_size restore

入力引数を指定する既存の32ビット・ルーチンの場合, これがポインタを埋め込む構造のときは,別の方法で既存の 32ビット・インタフェースを保持できます。実行時に, 32ビット形式のデータ構造と区別できる,64ビット形式のデータ構造を開発できます。 32ビット形式の構造だけを受け取る既存のコードは, 64ビット形式の構造が指定されると,自動的に異常終了します。

新しい64ビット構造についての構造定義には, 32ビット形式の構造を含む必要があります。 32ビット形式の構造を含むことによって,呼び出されたルーチンは入力引数を, 64ビット形式の構造へのポインタとして宣言し, いずれの場合でも明確に処理することができます。

言語に対して,型のチェックを行う 2種類の関数プロトタイプを用意しなければなりません。 省略時の設定時の関数プロトタイプは,引数を 32ビット形式の構造へのポインタとして指定します。 64ビット形式の関数プロトタイプは,マニュアルに説明されているように, シンボルを定義することによって選択できます。

64ビット対32ビット・ディスクリプタは,これがどのように行われるかを示す例です。

例: 次の例では,シンボル FOODEF64 の状態が,正しい関数プロトタイプと共に, 64ビット形式の構造を選択します。シンボル FOODEF64 が未定義の場合,古い 32ビット構造が定義され,古い32ビット関数プロトタイプが使用されます。

関数 foo_print を実現するソース・モジュールはシンボル FOODEF64を定義し, 32ビットおよび64ビットの呼び出し側からの呼び出しを処理することができます。 64ビットの呼び出し側はフィールド foo64$l_mbm1 を-1に設定します。 foo_print はフィールドfoo64$l_mbm1が-1であるかどうかをテストすることによって, 呼び出し側が64ビット形式の構造を使用しているか,または 32ビット形式の構造を使用しているかを判断します。

     #pragma required_pointer_size save
     #pragma required_pointer_size 32

     typedef struct foo {
         short int     foo$w_flags;
         short int     foo$w_type;
         struct data * foo$ps_pointer;
         } FOO;

     #ifndef FOODEF64

     /* Routine accepts 32-bit pointer to "foo" structure */
     int foo_print(struct foo * foo_ptr);

     #endif

     #ifdef FOODEF64

     typedef struct foo64 {
         union  {
             struct  {
                 short int     foo64$w_flags;
                 short int     foo64$w_type;
                 int           foo64$l_mbmo;
     #pragma required_pointer_size 64
                 struct data * foo64$pq_pointer;
     #pragma required_pointer_size 32
                 } foo64$r_foo64_struct;
             FOO foo64$r_foo32;
             } foo64$r_foo_union;
         } FOO64;

     #define foo64$w_flags    foo64$r_foo_union.foo64$r_foo64_struct.foo64$w_flags
     #define foo64$w_type     foo64$r_foo_union.foo64$r_foo64_struct.foo64$w_type
     #define foo64$l_mbmo     foo64$r_foo_union.foo64$r_foo64_struct.foo64$l_mbmo
     #define foo64$pq_pointer foo64$r_foo_union.foo64$r_foo64_struct.foo64$pq_point
     er#define foo64$r_foo32    foo64$r_foo_union.foo64$r_foo32

     /* Routine accepts 64-bit pointer to "foo64" structure */
     #pragma required_pointer_size 64
     int foo_print(struct foo64 * foo64_ptr);

     #endif

     #pragma required_pointer_size restore

上の例で,構造 foo および foo64 が同じソース・モジュールの中で交換して使用される場合,シンボル FOODEF64を削除できます。この場合,ルーチンfoo_printは次のように定義されます。
     int foo_print (void * foo_ptr);
FOODEF64シンボルを削除することによって,32ビットの呼び出し側と 64ビットの呼び出し側が同じ関数プロトタイプを使用できます。ただし Cソースのコンパイルの際に,厳密な型のチェックが行われることはありません。


8.2 Alpha/VAXガイドライン


アドレス,サイズ,および長さの引数だけが, クォドワードとして値で渡されなければならない

値で渡される引数は, VAX上ではロングワードに制限されています。VAX APIとの互換性を実現するには, クォドワード引数を値渡しではなく参照渡しする必要があります。しかし, アドレス,サイズ,および長さは,アーキテクチャが原因で,理論的には OpenVMS VAX上でロングワード,かつOpenVMS Alpha上ではクォドワードになる引数の例です。

APIがOpenVMS VAX上で利用できない場合であっても,すべての API上での一貫性を実現するため,このガイドラインに従ってください。


ページ・サイズに依存する単位の使用を避ける

長さやオフセットなどの引数は, バイトなど,ページ・サイズに依存しない単位で指定します。

ページレットは不便な単位です。これはVAXとの互換性を目的として開発され, OpenVMS Alpha上で,OpenVMS VAX互換インタフェースの中で使用されています。 ページレットはそのサイズがVAXページに等しく, ページ・サイズに依存しない単位とは考えられません。これはAlpha上の CPU固有ページとしばしば混乱されるためです。

例: EXPREG_64内のLength_64引数は,クォドワード・バイト・カウントとして, 値で渡されます。


参照渡しされるすべてのデータをすべて自然にアラインする

呼び出されるルーチンは, コンパイラに対して引数がアラインされていることを指定し, これによってコンパイラは, より効率的なロードおよび保存のシーケンスを実行できます。 データがそのままアラインされていない場合,性能は低下します。

参照渡しのデータが自然にアラインされていないために呼び出されたルーチンが正しく実行できない場合, ルーチンは明示的にチェックを実行し, アラインされていない場合はエラーを返す必要があります。たとえば, ロック付きロード,条件付き保存がルーチンの内部でデータについて実行される場合, データがアラインされていないと,ロック付きロード, 条件付き保存は正しく処理されません。


8.3 32ビットAPIから64ビットAPIへの拡張

APIの拡張を, 32ビット設計の改善や新規機能の追加とは区別して考えると, 必要な作業を容易に行うことができます。新しい64ビットAPI の中でのルーチンの呼び出しは,簡単なプログラミング作業に過ぎません。


64ビット・ルーチンは,64ビット形式の構造に加えて32ビット形式の構造を受け取る

APIへの呼び出しを簡単に修正できるように,インタフェースは, 64ビット形式に加えて,32ビット形式の構造も受け取ることができなければなりません。

例: 32ビットAPIが情報をディスクリプタで渡していた場合,新規イ ンタフェースは同じ情報をディスクリプタで渡さなければなりません。


64ビット・ルーチンは32ビット・ルーチンと同じ機能を提供する

新しい64ビットAPIが古いAPIの機能スーパセットではない場合でも,現在 32ビットAPIを呼び出しているアプリケーションは,古い32ビットAPI への古い呼び出しの一部を保持することなく,完全にアップグレードして64ビットAPI を呼び出すことができなければなりません。

例: SYS$EXPREG_64はP0,P1,およびP2プロセス空間で動作します。 SYS$EXPREG_64は$EXPREGの機能スーパセットであるため, 呼び出し側はすべての呼び出しをSYS$EXPREGに置換できます。


接尾辞"_64"を適切に使用する

システム・サービスの場合,この接尾辞は 64ビット・アドレスを参照渡しで受け取るサービスで使用されます。 拡張したサービスの場合,これによって64ビット機能のバージョンと,対応する 32ビット機能のバージョンが区別されます。一方,新規ルーチンの場合は, この接尾辞によって, 64ビット長アドレス・セルが読み込み/書き込みされることが明確に示されます。 埋め込み64ビット・アドレスを含む構造が渡されるとき,この構造が 64ビット構造として自己識別しない場合にも,この接尾辞が使用されます。 ルーチンが受け取るのは64ビット・ディスクリプタであるという理由で,ルーチン名に "_64"を含める必要はありません。なお,任意の値を参照渡しで渡すときには, 接尾辞が必要ないことに注意してください。接尾辞が必要なのは, 64ビット・アドレスを参照渡しで渡すときです。

この規則は,ほかのルーチンに対しても同じように推奨されます。

例:


SYS$EXPREG_64 (region_id_64, length_64, acmode, return_va_64, return_length_64)
SYS$CMKRNL_64 (routine_64, quad_arglst_64)


8.4 32ビット・ルーチンおよび64ビット・ルーチンの例

64ビット・アドレッシングのサポートを目的として拡張された 32ビット・ルーチン・インタフェースの例を次に示します。この例では, ガイドラインで記述された各種の問題を処理しています。

古いシステム・サービスSYS$CRETVAのC関数宣言は,次の形式で行われます。

     #pragma required_pointer_size save
     #pragma required_pointer_size 32
     int sys$cretva (
             struct _va_range * inadr,
             struct _va_range * retadr,
             unsigned int       acmode);
     #pragma required_pointer_size restore

新しいシステム・サービスSYS$CREATE_VAのC関数宣言は,次の形式で行われます。
     #pragma required_pointer_size save
     #pragma required_pointer_size 64
     int sys$cretva_64 (
             struct _generic_64 * region_id_64,
             void *               start_va_64,
             unsigned __int64     length_64,
             unsigned int         acmode,
             void **              return_va_64,
             unsigned __int64 *   return_length_64);
     #pragma required_pointer_size restore

SYS$CRETVA_64の新規ルーチン・インタフェースは,_va_range 構造の中で埋め込みポインタを訂正し, 64ビットの region_id_64 引数を参照で渡し, 64ビットの length_64 引数を値で渡します。


[ 前のページ ] [ 次のページ ] [ 目次 ] [ 索引 ] [ DOC Home ]