UNIX間でのアプリケーション移植の基礎知識

UNIXを元にした OS は数多くあり、単純に一つのプログラムを作ったからといってすべての UNIX 上で簡単に動作するわけではありません。Solaris, HP-UX, AIX, Linux, FreeBSD など、それぞれの OS に合わせたプログラムを作らなければなりません。そこで、ここでは UNIX間で移植性のあるプログラムを作るにはどんな点に注意しなければならないかまとめてみます。

* はじめに

一般的な移植上の問題として以下のことが上げられます。

ここでは、これらの問題を取り上げどういった点に注意が必要なのかを簡単に記述します。

* ハードウェアに依存するコード

移植性を考慮したプログラムを作る上でもっとも難しいのは、特別なハードウェアを含むアプリケーションを作成する場合です。この節では、以下の3種類のハードウェア依存の問題について考えます。

● 独自仕様なインターフェースを持つものの移植性を高める

UNIX アプリケーションには、データオペレーションをより高速化するために固有の CPU 命令(アセンブラ)を使用することによって、独自なインターフェースを使用するものがあります。これらのインターフェースは、システムモニタやイメージフィルタ、デバイスドライバなどでしばしば書かれています。ほとんどの場合、これらは以下の方法のうちのいずれかにより、インターフェースの移植性を高めることができます。

機能を既存の標準に割り当てる 独自で機能を実現しているものが、POSIXなど標準的なもので実現されている場合、それらを標準のものに置き換えることで移植性が高まります。
ルーチンをカプセル化したラッパー関数を作成する

どうしても、高速化が必要なところやハードウェアに直接アクセスしなければならないところがある場合、それらの部分をカプセル化するようなラッパー関数を作成する。システムに依存する関数を単純に置き換えることで、他の部分を変更せずにアプリケーションが作成できるようにすることで移植性が高まります。

ハードウェア依存性のないアプリケーションに書き直す ハードウェアに依存しない記述が可能なら、独特な記述をするのではなくハードウェアに依存しない記述をすることで移植性が高まります。

● バイトオーダーに注意して移植性を高める

データ構造体内の個々のバイトの順序(バイトオーダー)は、移植性のあるアプリケーションを作成する上で問題となることがあります。バイトオーダーの扱いで注意すべき問題は主に次の 2 点です。

バイトオーダーには、ビックエンディアンとリトルエンディアンの二種類があります。

ビックエンディアンプロセッサ(SPARC, PowerPC)は、最上位バイト(MSB)を最下位のバイトアドレス(最下位バイト)にストアするという順序化を行います。また、リトルエンディアンプロセッサ(x86, Alpha)は、最下位バイト(LSB)を最下位のバイトアドレス(最下位バイト)にストアするという順序化を行います。

シングルバイト(8 ビット)でアドレスする場合、ビックエンディアンとリトルエンディアンのマシンでは、同じ方法でアドレスを行うので問題はありません。しかし、マルチバイト(ハーフワード、ワード、ダブルワード)でアドレスする場合は、バイトオーダーは異なります。

アドレス
+0
+1
+2
+3
リトルエンディアン 
0x34
0x12
0xCD
0xAB
ビッグエンディアン
0xAB
0xCD
0x12
0x34
バイトオーダーによる格納方法の違い
4bytes幅のデータ「0xABCD1234」を、メモリ中へ格納した場合。最下位バイトから順に格納するのがリトルエンディアン、最上位バイトから順に格納するのがビッグエンディアン。

ネットワークを使った通信では、バイトオーダーの異なるマシンやシステム同士で通信する可能性があります。このとき、バイトオーダーが異なっていると正しく通信できません。このような不都合が起こらないように、ネットワークで通信をする場合、あらかじめバイトオーダーを決めてあります。これをネットワークバイトオーダーといいます。たとえばTCP/IPプロトコルでは、ネットワークバイトオーダーはMSBから先に送るビッグエンディアンとなっています。

● アライメントに注意して移植性を高める

double と long double の浮動小数点変数のアライメントの違いがアライメントの違いがポーティングの問題を引き起こすことがあります。double変数について、SPARC プロセッサでは 8 バイト、x86 プロセッサでは 4 バイトのアライメントが行われます。

struct TestA {
  char   c;
  short  s;
  double d;
};
 
struct TestB {
  short  s;
  double d;
  char   c;
};

sizeof 関数でサイズを調べてみると、それぞれの構造体がパディングにより

x86 TestA = 12, TestB = 16
SPARC TestA = 16, TestB = 24

となることがわかります。ちなみに、double 変数を使用しない場合は以下のようになります。

struct TestA {
  char  c;
  short s;
  int   i;
};
 
struct TestB {
  short s;
  int   i;
  char  c;
};
x86 TestA = 8, TestB = 12
SPARC TestA = 8, TestB = 12

これら、アライメントと構造体パディングは、コンパイラによって管理されているので、コンパイラによってはこれらの差異を吸収してくれるコンパイラオプションを持っているものもあります。 しかし、アプリケーションの実行速度がひどく低下することがあります。

* OSに依存するコード

オペレーティング・システム間での移植性の問題の多くは、それぞれが標準と準拠していないことから発生しています。 SVID、POSIX、ANSI-C、X11、TCP/IP、X/OPEN などの標準の準拠度合いを見てみましょう。

  SVID POSIX ANSI-C X11 TCP/IP X/OPEN 64 ビット その他
Solaris 8 SVID3
SVR4 ABI
IEEE1003.1
IEEE1003.1b
IEEE1003.1c
IEEE1003.2
X11R6
OpenGL

NFS3
PPP
Sockets
IP V6

UNIX95
UNIX98
CDE1.4
JVM,JDK
WebNFS
DirectIO,AIO
Neo/CORBA
Kodak
Solaris 2.6/7 SVID3
SVR4 ABI
IEEE1003.1
IEEE1003.1b
IEEE1003.1c
IEEE1003.2
X11R6
OpenGL

NFS3
PPP
Sockets

UNIX95
UNIX98
CDE1.2
JVM,JDK
WebNFS
DirectIO,AIO
Neo/CORBA
Kodak
HPUX 10.20 SVID3
ベースの
カーネル
Level1API
IEEE1003.1
IEEE1003.2
IEEE1003.1b
IEEE1003.1c
X3.159 X11R6
OSF/Motif1.2.5
NFS
Sockets
UNIX95
CDE1.0
× DCE1.0
HPUX 11.0 SVID3 IEEE1387.2 X3.159 X11R6 NFS3
IPv6
UNIX95
base95
CDE1.0

LP64
KernelJVM
JIT,JDK
WebAdmin
JFS
VLM(4TB)
IBM-AIX 4.2 SVID2
ベース 
IEEE1003.1
IEEE1003.2
X3.159 X11R5
OSF/Motif1.2
SGI-GL3.3
NFS3
Sockets
UNIX95
base95
CDE1.0
× Monitor、API 
Scientific,Lib 
Parallel,prog 
OLTP
Clusters/HA
IBM-AIX 4.3 SVID2
ベース
IEEE1003.1
IEEE1003.2
X3.159 X11R6
OpenGL,1.1
PHIGS,1.0
IPv6
LDAP
SSLv3
 
UNIX98
base95
CDE1.0
JDK,JIT
WebAdmin
DirectIO
SGI-IRIX 6.2 Subset of
SVID3
IEEE1003.1
IEEE1003.1b
IEEE1003.1c
IEEE1003.2
X11R6
OpenGL
NFS3
Sockets
PPP
UNIX95
Base95
DirectIO
MagicUI
CosmoVRML
DCE1.1
DEC OSF/1 SVID2
ベースの
カーネル
 
IEEE1003.1 X3.159 XPG3 NFS
Sockets
** DLI
DigitalUnix  
4.0C
SVID2
ベース 
SVID3
カーネル
IEEE1003.1
IEEE1003.1b
IEEE1003.1c
IEEE1003.2
X11R6
OSF/Motif1.0
NFS3
Sockets
PPP
UNIX95
CDE1.0

LP64
MFS
DataMining
Dataware
exceeds C2
IPMulticast

表のように、それぞれ準拠しているように思えますが、Solaris は前身である SunOS 4 (BSD) の多くの機能を融合してあるため制限の多い System V ファイルシステムや関連ユーティリティがありません。 HP-UX も、多くの機能やコマンドが BSD系の UNIX に近いものがあります。

標準への準拠以外にも OS に依存することはあります。特に、UNIX ソケットを使ったクライアント/サーバ・プログラムのコーディングの際、telnet セッションで Ctrl-Cをタイプしたり、データベースのフロントエンドが SQL クエリ処理の終了を指示するなど帯域外 (Out-Of-Band) データの通信が必要なことがあります。このとき、プロセスはカーネルに対して ioctl() か fcntl() を呼び出し、OOB を受け取る準備ができていることを示す必要があります。

#if defined(hpux)
    ioctl(fd, FIOSSAIOOWN, getpid());  /* HP-UX システムの場合 */
#elif defined(sco)
    ioctl(fd, SIOCSPGRP, getpid());    /* SCO システムの場合 */
#else
    fcntl(fd, F_SETOWN, getpid());     /* 他の UNIX の場合 */
#endif

また、シグナルに関する振る舞いは、BSD系と System V 系で異なります。

4.3BSD
  • シグナルが発生した場合、さらにシグナルが発生することが自動的にブロックされ、任意の関数が呼び出されます。
  • 関数の返り値は、ハンドルされたシグナルをアンブロックし、割り込みが発生するところまでプロセスを継続実行します。ハンドラ関数は、シグナルが送信された後も、そのままです。
  • 正しいシステムコールの間に、シグナルが受信され、時期尚早にシステムコールが終了する場合、システムコールは自動的に再起動される。特に、これは、read(2V)の間に起こります。
  • どの UNIX システムにおいても、BSD シグナルが使用された時にどのシステムコールが再起動可能であるという完全なリストはありません。Solaris においては、このリストは、(速度の遅いデバイス上の)read() と write() を含むことになるでしょう。
SVR4
  • シグナルが発生した場合、任意の関数が呼び出されます。さらにシグナルが発生することは、自動的にブロックされません。シグナルが SIGILL や SIGTRAP でない場合は、受信したシグナルに関する関数の値は、関数が呼び出される前に、SIG_DFL を再設定されます。
  • 関数の返り値は、割り込みが発生するところまでプロセスを継続実行します。ハンドラ関数は、シグナルが送信された後、消滅します。

シグナルに関しては、POSIXの sigaction()関数を使用することを推奨します。この関数は、望ましい振舞いを実現するように、相当に柔軟性に富んでいるので非常に推奨されてます。 POSIX インタフェースへの移行は、コードの移植性を高める正しい方向です。

異なるデータモデルによる、問題についても認識しておく必要があります。

* 環境に依存するコード

それぞれ UNIX系の OS のあいだで、環境に依存する部分でも様々なアプリケーション移植の問題が発生します。特にヘッダーファイル(インクルードファイル)の不一致によって発生します。ヘッダーファイルの置かれている物理的な場所の不一致や、微妙にヘッダーファイル名が異なる場合があります。

#ifdef AIX
#include <sys/limits.h>
#endif 
#ifdef SOLARIS 
#include <limits.h>
#endif
 
#ifdef HP
#include <strings.h>
#endif
#ifdef SOLARIS
#include <string.h>
#endif

これは、ヘッダーファイルだけでなく、使用する関数を含むライブラリーが異なる場合があります。たとえば、ソケットに関する関数は、libc に含まれていることが多かったのですが Solaris などでは libsocket、libnsl に含まれています。また、make など開発に必要なコマンドが OS によって異なる場所にあるので、開発環境を整備する際に注意が必要になります。これらのことを考慮して、開発環境を整える必要があります。

ここであげたものだけが、移植する際に注意すべき点のすべてではありません。ヘッダーファイル内に定義してあるデータなど、まだ細かい点で異なる部分はあります。移植作業をする人のちょっとした Tips として利用してください。