目次


デバイス制御アプリケーションを Windows から Linux へ移植する

Windows と Linux でのデバイス制御の違いを理解し、マイグレーションの課題を克服する

Comments

もし皆さんが、異なるプラットフォームに対してデバイス制御アプリケーションを開発するのであれば、Windows と Linux ではデバイス制御の方法が異なり、一方のプラットフォームからもう一方のプラットフォームへ移植する作業は大変な作業になる可能性があることはご存知でしょう。この記事では両方のオペレーティング・システムでのデバイス制御の動作を分析するために、アーキテクチャーからシステム・コールに至るまでのすべてを調べ、両オペレーティング・システムでの違いに焦点を当てます。また、C/C++ での例を使ってマイグレーションの詳細についても説明します。

前提
この記事で「Windows」と言う場合は、Microsoft Visual C++® version 6 以降がインストールされた Windows 2000 以降の Windows オペレーティング・システムを指します。Linux に関しては、カーネルが 2.6 ベースで、GNU GCC がインストールされている必要があります。

デバイス制御のアーキテクチャーを比較する

Windows と Linux ではデバイス制御の方法が異なります。

Windows でのデバイス制御のアーキテクチャー

Windows では、ユーザー・アプリケーションとデバイス・ドライバーは I/O サブシステムによって接続され、またデバイス・ドライバーをサポートするインフラは I/O サブシステムによって定義されます。デバイス・ドライバーは特定のデバイスに対する I/O インターフェースを提供します (図 1)。

図 1. Windows でのデバイス制御のアーキテクチャー
Windows でのデバイス制御のアーキテクチャー
Windows でのデバイス制御のアーキテクチャー

デバイス制御のプロセスの中で、I/O 操作は IRP (I/O Request Packet) の中にカプセル化されます。I/O マネージャーは IRP を作成し、それをスタックの先頭に送信します。次にデバイス・ドライバーは、その I/O リクエストのパラメーターを含む IRP のスタック位置を取得します。各ドライバーは、IRP の中にある要件 (createreadwritedevioctlcleanupclose など) に従って、ハードウェア・インターフェースを介してそれぞれの作業を行います。

Linux でのデバイス制御のアーキテクチャー

Linux でのデバイス制御のアーキテクチャーは少し異なります。主な違いは、通常のファイルやディレクトリー、デバイス、そしてソケットが、すべてファイルである点です。つまり Linux ではすべてがファイルなのです。Linux のカーネルはデバイスにアクセスするために、ファイルシステムを介してデバイス操作の呼び出しをデバイス・ドライバーにマッピングします。Linux には I/O マネージャーはなく、すべての I/O リクエストは最初にファイルシステムに行きます (図 2)。

図 2. Linux でのデバイス制御のアーキテクチャー
Linux でのデバイス制御のアーキテクチャー
Linux でのデバイス制御のアーキテクチャー

デバイス・ファイル名とデバイス・パス名を比較する

開発の視点から見ると、デバイス制御には必ずデバイス・ハンドルの取得が不可欠です。しかし Windows と Linux ではデバイス制御のアーキテクチャーが異なるため、デバイス・ハンドルの取得方法は Windows を使用するのか Linux を使用するのかによって異なります。

一般的に、デバイス・ハンドルは特定のデバイス・ドライバーの名前によって決定されます。

Windows の場合、デバイス・ドライバーのファイル名は、通常はデバイス・パス名と呼ばれ、一般的なファイルの名前とは異なります。デバイス・パス名は \.DeviceName のような固定フォーマットを持っています。C/C++ プログラミングの場合、デバイス・パス名を表す文字列は \\.\DeviceName でなければなりません。またコード上で参照する場合は \\\\.\\DeviceName のようにします。DeviceName は、対応するデバイス・ドライバー・プログラムの中で定義されるデバイス名と同じでなければなりません。

一部のデバイス名は Microsoft によって定義されており、変更することができません (表 1)。

表 1. Windows でのデバイス名 (x = 0, 1, 2 等)
デバイスパス名
フロッピー・ドライブA: B:
ハードディスクの論理サブ領域C: D: E: . . .
物理ドライブPhysicalDrivex
CD-ROM、DVD/ROMCdRomx
テープ・ドライブTapex
COM ポートCOMx

例えば C/C++ プログラミングでは、\\\\.\\PhysicalDrive1\\\\.\\CdRom0\\\\.\\Tape0 のようなデバイス・パス名を使います。このリストでは一般的なデバイスのみを示しましたが、他のデバイスについての詳細は、この記事の最後にある「参考文献」のセクションを参照してください。

Linux ではさまざまなデバイスがファイルとして表現されるため、こうしたデバイスはすべて ./dev ディレクトリーの中にあります。このディレクトリーの中にあるデバイス・ドライバーには次のようなものがあります。

  • IDE (Integrated Drive Electronics) ハード・ドライブ (/dev/hda や /dev/hdb など)
  • CD-ROM ドライブ。これらの一部は IDE であり、他は /dev/scd0 のように SCSI (Small Computer Systems Interface) デバイスとしてエミュレートされる CD-RW (CD read/write) ドライブです。
  • シリアル・ポート (COM1 用の /dev/ttyS0、COM2 用の/dev/ttyS1 など)
  • ポインティング・デバイス (/dev/input/mice やその他を含む)
  • プリンター (/dev/lp0 など)

一般的なデバイス・ファイルの大部分は上で説明した方法で見つけることができます。他のデバイス・ファイル名や詳細なデバイス情報に関しては dmesg コマンドを試してみてください。

主なシステム・コールを比較する

デバイス制御のための主なシステム・コールには、open、close、I/O control、read/write などの操作があります。表 2 は Windows と Linux の間の対応を示しています。

表 2. デバイス制御関数の対応
WindowsLinux
CreateFileopen
CloseHandleclose
DeviceIoControlioctl
ReadFileread
WriteFilewrite

では、最も一般的な 3 つの関数である、createclose、そして devioctl を詳しく調べてみましょう。

Windowsでのデバイスのオープンとクローズ

Windows の場合は CreateFileCloseHandle を使用します。デバイスを開くためには CreateFile 関数を使います。この関数は、オブジェクトをアクセスするために使われるハンドルを返します (リスト 1)。

リスト 1. Windows の CreateFile 関数
HANDLE CreateFile (LPCTSTR lpFileName,          //File name of the device 
                                                  (Device Pathname)
   DWORD dwDesiredAccess,                       //Access mode to the object (read, write, 
                                                  or both)
   DWORD dwShareMode,                           //Sharing mode of the object (read, 
                                                  write, both or none)
   LPSECURITY_ATTRIBUTES lpSecurityAttributes,  //Security attribute determining whether 
                                                  the returned handle can be inherited by 
                                                  child processes
   DWORD dwCreationDisposition,                 //Action taken on files that exist and 
                                                  do not exist
   DWORD dwFlagsAndAttributes,                  //File attributes and flags
   HANDLE hTemplateFile);                       //A handle to a template file

lpFileName パラメーターは、先ほど触れたデバイス・パス名です。一般的に、デバイスを開くためには dwDesiredAccess を 0 または GENERIC_READ|GENERIC_WRITEに、dwShareModeFILE_SHARE_READ|FILE_SHARE_WRITE に、dwCreationDispositionOPEN_EXISTING に、そして dwFlagsAndAttributeshTemplateFile を 0 または NULL に設定します。返されたハンドルは、さらにデバイス制御の操作に使われます。

デバイスを閉じるためには CloseHandle 関数を使います。CloseHandle ではデバイスを開いたときに返されるハンドルとして hObject パラメーターを BOOL WINAPI CloseHandle (HANDLE hObject); のように設定します。

Linux でのデバイスのオープンとクローズ

Linux の場合には openclose を使います。先ほど触れたように、デバイスを開くための操作は一般的なファイルを開く場合の操作とまったく同じです。リスト 2 は、open を使ってデバイス・ハンドルを取得する方法を示しています。

リスト 2. Linux での open 関数
int open (const char *pathname,
       int flags, 
       mode_t mode);

呼び出しが成功した場合に返されるファイル記述子は、現在そのプロセスに対して開かれていないファイル記述子のうち、最も小さい番号のものです。呼び出しが失敗した場合には -1 が返されます。このファイル記述子がデバイス・ハンドルとして使われます。

パラメーター・フラグは、O_RDONLYO_WRONLYO_RDWR のうちの 1 つを含む必要があります。これら以外のフラグはオプションです。mode 引数は新しいファイルが作成された場合に使用するパーミッションを指定します。

close 関数はファイルを閉じる場合とまったく同じように Linux 上のデバイスを閉じます (例えば int close(int fd); など)。

Windows での DeviceIoControl

デバイス制御関数 (Windows での DeviceIoControl と Linux での ioctl) は、デバイス制御用に最も一般的に使われる関数であり、デバイスへのアクセス、情報の取得、命令の送信、データの交換などのタスクを行います。リスト 3 は DeviceIoControl を示しています。

リスト 3. Windows での DeviceIoControl 関数
BOOL DeviceIoControl (HANDLE hDevice,
      DWORD dwIoControlCode,
      LPVOID lpInBuffer,
      DWORD nInBufferSize,
      LPVOID lpOutBuffer,
      DWORD nOutBufferSize,
      LPDWORD lpBytesReturned,
      LPOVERLAPPED lpOverlapped);

このシステム・コールによって、指定されたデバイスに対して制御コードやその他のデータが送信されます。そのデバイスに対応するデバイス・ドライバーは、制御コード (dwIoControlCode) の指示に従って動作します。例えば物理ドライブから構造パラメーター (メディアのタイプやシリンダー数、各シリンダーのトラック数、各トラックのセクター数など) を取得するには IOCTL_DISK_GET_DRIVE_GEOMETRY を使います。制御コードの定義やヘッダー・ファイル、その他の詳細情報のすべては MSDN の Web サイトで見ることができます (「参考文献」にリンクがあります)。

I/O バッファーが必要かどうか、また I/O バッファーの構造とサイズは、実際の ioctl プロセスが関わるデバイスと操作に依存して決まります。また呼び出しの中で定義される dwIoControlCode によっても決まります。

重複した操作に対してポインターが NULL に設定されると、DeviceIoControl の動作はブロックされた形での同期した動作になります。それ以外の場合は非同期で動作します。

Linux での ioctl

Linux では、指定したデバイスに制御情報を送信するために、int ioctl(int fildes, int request, /* arg */ ...); のように ioctl を使います。最初のパラメーター fildes は開かれているファイルの記述子であり、open() 関数から返され、特定のデバイスを指します。

ioctl に対応するシステム・コール (DeviceIOControl) の場合とは異なり、ioctl での入力パラメーターのリストは固定されていません。入力パラメーターは、その ioctl がどんな種類のリクエストを実行するのか、またそのパラメーター・リクエストによって何が指定されるのかに依存します (これは Windows の DeviceIOControl での dwIoControlCode の場合とよく似ています)。しかし、マイグレーションを行う場合にリクエスト・パラメーターを選択する際には、適切なパラメーターを選択するように注意する必要があります。なぜなら DeviceIOControldwIoControlCode の値と ioctlrequest の値は同じではなく、dwIoControlCode/request に対する明確なマッピング・リストがないからです。通常、パラメーター・リクエストの値を選択するためには、そのリクエストのヘッダー・ファイルの中にある、そのリクエストの定義を調べます。制御コードの定義はすべて /usr/include/{asm,linux}/*.h の中にあります。

arg パラメーターは、特定のデバイスが、そのデバイスに要求される作業を行うために必要とする詳細なコマンド情報を提供するためにあります。arg のデータ型は、その特定の制御リクエストに依存します。この引数は、詳細なコマンドを提供することと、返されるデータを受信することの両方の目的に使われます。

マイグレーションの例

では、Windows から Linux へのマイグレーション・プロセスの例を調べてみましょう。この例ではパーソナル・コンピューター上のメインの IDE ハード・ドライブから SMART ログを読み取ります。

ステップ 1. デバイスのタイプを識別する

上で説明したとおり、Linux の各デバイスはファイルと見なされます。最初のステップは、Linux 上のデバイスのファイル名を判断することです。このファイル名を使わないと、デバイス制御に必要なデバイス・ハンドルを取得することができません。

この例での対象は IDE のハード・ドライブです。 Linux では IDE のハード・ドライブは /dev/hda や /dev/hdb のように記述されます。この例でマイグレートしようとしているハード・ディスクのデバイス・パス名は \\\\.\\PhysicalDrive0 です。/dev/hda は、Linux 上でこれに対応するデバイスのファイル名です。

ステップ 2. include ヘッダーを変更する

#include ヘッダー・ファイルを Linux での形式に変更する必要があります (表 3)。

表 3. #include ヘッダー・ファイル
WindowsLinux
#include <windows.h> #include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <devioctl.h> #include <sys/ioctl.h>
#include <ntddscsi.h> #include <linux/hdreg.h>

windows.h はデバイスを開く関数と閉じる関数 (CreateFileCloseHandle) に対して使われます。従って、Linux での open()close() に必要なヘッダー・ファイル (sys/types.h、sys/stat.h、fcntl.h) をインクルードする必要があります。

Windows における devioctl.hDeviceIoControl 関数のためのものであり、ここでは ioctl 関数が確実に動作するように devioctl.h を sys/ioctl.h に変更します。

ntddscsi.h (DDK のヘッダー・ファイル) には、デバイスを制御するための一連の制御コードが定義されています。この例では IDE のハード・ドライブにしか扱わないため、linux/hdreg.h を Linux プログラムに追加すればよいだけです。

他の場合には、必要な制御コードが定義されているすべてのヘッダー・ファイルをインクルードするようにします。例えば、ハード・ドライブではなく CD-ROM にアクセスするためには、(linux/hdreg.h ではなく) linux/cdrom.h が含まれるようにします。

ステップ 3. 関数とパラメーターを変更する

ではコードを詳細に見てみましょう。リスト 4 はコマンドの詳細についての情報を示しています。

リスト 4. コマンドの詳細
unsigned char cmdBuff[7];
cmdBuff[0] = SMART_READ_LOG;  // Used for specifying SMART "commands"
cmdBuff[1] = 1;               // IDE sector count register
cmdBuff[2] = 1;               // IDE sector number register
cmdBuff[3] = SMART_CYL_LOW;   // IDE low order cylinder value
cmdBuff[4] = SMART_CYL_HI;    // IDE high order cylinder value
cmdBuff[5] = 0xA0 | (((Dev->Id-1) & 1) * 16); // IDE drive/head register
cmdBuff[6] = SMART_CMD;       // Actual IDE command

このコマンドの情報は ATA コマンドの仕様から引用したものです。このコードを Linux に移植するためには何も変更が必要ないため、このコードを詳しく分析する必要はありません。

リスト 5 に示すコードは Windows のメインのハード・ドライブを開きます。

リスト 5. Windows のメインのハード・ドライブを開
HANDLE devHandle = CreateFile("\\\\.\\PhysicalDrive0",           //pathname
                             GENERIC_WRITE|GENERIC_READ,         //Access Mode
                             FILE_SHARE_READ|FILE_SHARE_WRITE,   //Sharing Mode
                             NULL,OPEN_EXISTING,0,NULL);

オープンとクローズに関するセクションで、Linux でデバイスを開くためには 2 つのパラメーター (ファイル・パス名とデバイスに対するアクセス・モード) が必要であると説明したことを思い出してください。先ほどのオリジナル・コードによると、最初のパラメーターは /dev/hda であり、2 番目のパラメーターは O_RDONLY|O_NONBLOCK です。変更されたコードは HANDLE devHandle = open("/dev/hda", O_RDONLY | O_NONBLOCK); のようになります。同様に CloseHandle(devHandle);close(devHandle); に変更します。

メインの部分は、ioctl を使って必要な特定のデバイスと情報にアクセスするための方法に関するものです。Windows のオリジナル・コードをリスト 6 に示します。

リスト 6. Windows での DeviceIoControl のソース
typedef struct _Buffer{
       UCHAR   req[8];              // Detailed command information other than 
                                       control code
       ULONG   DataBufferSize;      // Size of Data Buffer, here is 512
       UCHAR   DataBuffer[512];     // Data Buffer
} Buffer;

Buffer regBuffer;
memcpy(regBuffer.req, cmdBuff, 7);  //req[7] is reserved for future use. Must be zero.
regBuffer.DataBufferSize = 512;
unsigned int size = 512+12;         // Size of regBuffer
                                    // 8 for req, 4 for DataBufferSize, 512 for data
DWORD bytesRet = 0;                 // Number of bytes returned
int retval;                         // Returned value

retval = DeviceIoControl(devHandle,
                         IOCTL_IDE_PASS_THROUGH,  //Control code
                         regBuffer, // Input Buffer, including detailed command
                         size, 
                         regBuffer, // Output Buffer, use the same buffer here
                         size, 
                         &bytesRet, NULL);
if (!retval)
	cout<<"DeviceIoControl failed."<<endl;
else
memcpy(data, retBuffer.DataBuffer, 512);

DeviceIoControlioctl よりも多くのパラメーターを必要とします。デバイス・ハンドルは両方のプラットフォームでの最初のパラメーターであり、CreateFile または Linux の場合 open() から返されます。しかし Windows での制御コードと Linux でのリクエストは非常に異なった方法で定義されるため、先ほど説明したとおり、この 2 つのパラメーターの間の対応関係を見つけるための決まったルールはありません。IOCTL_IDE_PASS_THROUGH はヘッダー・ファイル ntddscsi.h の中で CTL_CODE (IOCTL_SCSI_BASE, 0x040a, METHOD_BUFFERED, FILE_READ_ACCESS | FILE_WRITE_ACCESS) として定義されています。ここではヘッダー・ファイル /usr/include/linux/hdreg.h の中の定義を調べ、Linux で対応する制御コードとして HDIO_DRIVE_CMD for Linux を選択します。

また、このデバイスが特定のタスクを満足するためにはコマンドに関する詳細な情報が必要です。このコマンドはプロセスの中で交換されるバッファーの中に、返されるデータ用の空間と共に含まれています。この同じバッファーを、コマンドの送信と必要なログ情報の取得の両方に使います。Linux の場合、データ・バッファー・サイズのスペースは削除することができ、8 バイトすべてが必要にはなりません。この例ではコマンドのうちの 4 バイトのみが使われています。

これに対応する Linux のコード (リスト 7) はずっと単純に見えますが、これは構造や関数の引数が Windows の場合よりも単純なためです。

リスト 7. Linux での ioctl のソース
int retval;
unsigned char req[4+512]; // Enough for returned data and the 4 byte detailed 
                             command information
req[0]= cmdBuff[6];       // Consider the requirement in this sample, only 4 bytes 
                             are used
req[1]= cmdBuff[2];
req[2]= cmdBuff[0];
req[3]= cmdBuff[1];

retval = ioctl(devHandle, HDIO_DRIVE_CMD, &req);
if(ret)
	cout<<"ioctl failed."<<endl;
else 
memcpy(data, &req[4], 512);

ステップ 4. Linux 環境でテストする

ヘッダー・ファイルと関数、そしてパラメーターを変更すると、このプログラムを Linux 上で実行させるための準備ができたことになります。これから必要な作業としては、このプログラムを Linux プラットフォーム上でコンパイルし、残った構文エラーを修正することです。使用する Linux の種類やコンパイル環境によっては、さらにいくつかの変更が必要かもしれません。


ダウンロード可能なリソース


関連トピック


コメント

コメントを登録するにはサインインあるいは登録してください。

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=60
Zone=Linux
ArticleID=321949
ArticleTitle=デバイス制御アプリケーションを Windows から Linux へ移植する
publish-date=06242008