01signal.com

Xillybusの device filesへのアクセス

序章

このページはXillybus' programming guide for Linuxに基づいています。以下のトピックのより包括的なビューについては、このガイドを参照することをお勧めします。 Windowsにも同様のガイドがあります。

「Hello, World」テストをまだ行っていない場合は、まずそれを行うことをお勧めします。

Xillybus IP coreとの通信は、 host上の device files によって行われます。これらの device files は、通常のファイルのようにアクセスされます。ただし、通常のファイルとは異なり、 device file はディスクに保存されている data を表していません。 device file への読み取りと書き込みは、代わりに I/O 操作になります。

したがって、ほぼすべてのプログラミング言語で Xillybusの device files にアクセスできます。この目的で Linux command-line utilities を使用することもできます。したがって、なぜこのトピックについて議論する必要があるのか 疑問に思うかもしれません。 ファイルを正しく読み書きする方法を知っていれば、 Xillybusの操作方法を知っていることになります。

実際、 APIの詳細な知識がなくても、通常のファイルにアクセスするプログラムを作成することは可能です。ただし、これは I/Oで作業するには十分ではありません。 ハードウェアとの相互作用により、通常のファイルではめったに起こらない状況が生じます。プログラマーとして、 APIを使用してこれらの状況を処理する方法を知る必要があります。

したがって、このページの大部分は、通常のファイルへのアクセスにも関連するトピックに専念しています。 device files との違いは、 APIを使用する場合のミスに対する許容度が低いことです。

API を理解していないと、主に 2 種類の混乱が生じる可能性があります。

プログラミング言語とオペレーティング システム

Windows を使用する場合、 C、 C++、 C#、 Python、 Perl 、および Cygwinに付属するものなど、一般的なプログラミング言語では問題ありません。ただし、一部のツール ( MATLABなど) は、 Xillybusの device filesでの動作を拒否する場合があります。 これらのツールは、 device file が通常のファイルではないことを検出し、それをエラーと見なします。この状況には回避策があり、通常は low-level I/O用の extensions を使用します。

Linuxでは、ファイルにアクセスできるすべてのツールまたはプログラミング言語が Xillybusで動作します。

以下の説明は C 言語に基づいていますが、トピックはすべてのプログラミング言語に関連しています。

サンプルコード

Xillybusの Web サイトからダウンロードできるコーディング例があります。これらの例は Cで書かれており、低レベルの API を正しく使用する方法を示しています。

サンプルをダウンロードするには、 demo bundle をダウンロードした Web ページ (つまり、 Xillybusのページまたは XillyUSBのページ) に移動します。

Linuxを使用している場合は、 Linux driverをダウンロードします。サンプル コードは、同じ .tar.gz ファイルに含まれています。

Windowsを使用している場合は、 Xillybus package for Windowsをダウンロードしてください。

いずれにせよ、サンプル コードは demoapps/ directory内にあります。このコードでは、小さい buffer が割り当てられているため (128 bytes)、各 I/O operation で読み取られるまたは書き込まれる data の量が少ないことに注意してください。これの目的は、コードを単純にすることだけです。実際のアプリケーションでは、より大きな buffer が推奨されます (通常は32 kBytes が適しています)。

Linux と Windows の違いは小さいですが重要です。特に:

とはいえ、サンプル コードの背後にある原則はまったく同じです。

Buffered file I/O

プログラミング言語は通常、ファイルにアクセスするための 2 つの別個の APIs を提供します。 1 つの高レベル APIと 1 つの低レベル API。高レベルの API は、操作が簡単なため、より一般的に使用されます。

たとえば、 C 言語では、高レベルの API は fopen()、 fread()、 fwrite()、 fprintf()、 fclose() などで構成されます。低レベルの API は open()、 read()、 write()、 close() などで構成されます。これら 2 つの APIs の違いはありません。 functionsの名前のわずかな違い: 高レベルの API は user-space RAM buffersを提供します。これらの buffers は、 C run-time libraryによって実装されます。 kernelの driver によって制御される DMA buffersと混同しないでください。

最も重要な違いは、 fwrite()の動作です。 この function に対する call の結果は、 data が user-space bufferに格納されるということです。 FPGA への送信が遅れる場合があります。実際、 data は、ファイルが閉じられるまで、無期限に fwrite()の buffer にとどまることができます。 data がファイルに書き込まれたにもかかわらず、何も起こらなかったため、これは Xillybusのバグのように見えることがあります。

したがって、低レベル (non-buffering) APIを使用することをお勧めします。これは、ほとんどのプログラミング言語が高レベル APIの使用を促進しているにもかかわらず、通常は可能です。ツールまたはプログラミング言語が低レベルの APIをサポートしていない場合は、代わりに利用可能な API を使用してください。この場合、 I/O がいつ行われるかは制御できないことを覚えておくことが重要です。それにもかかわらず、これは多くのアプリケーション ( data acquisitionなど) では十分です。

繰り返しますが、 buffered I/O を Xillybusの buffersと混同しないでください。最も重要なことは、 Xillybusの buffersが原因で、 data が無期限に動かなくなることはありません。これについては、 zero-length write()のコンテキストで以下でさらに説明します。

device fileからの読み取り: 基礎

device file から読み取るためのコード例は streamread.cです。このプログラムは demoapps/ directoryにあります。ただし、以下に示すコード スニペットは、別のプログラムからのものです。 memread.c (同じ directoryから)。このプログラムは別の目的を意図していますが、 allread()という名前の function が含まれています。この functionでいくつかのトピックを示す方が便利です。

次のコマンドでファイルが開かれたとしましょう。

int fd, len;
char *buf;

fd = open("/dev/xillybus_read_32", O_RDONLY);

ここで、 @len bytes をファイルから bufferに読み込みます。ただし、部分的な結果は許可されません。 必要な量の dataを常に読み取る function が必要です。

allread() を次のように使用すると、次のようになります。

allread(fd, buf, len);

この function は次のように定義されます。

void allread(int fd, unsigned char *buf, int len) {
  int received = 0;
  int rc;

  while (received < len) {
    rc = read(fd, buf + received, len - received);

    if ((rc < 0) && (errno == EINTR))
      continue;

    if (rc < 0) {
      perror("allread() failed to read");
      exit(1);
    }

    if (rc == 0) {
      fprintf(stderr, "Reached read EOF\n");
      exit(1);
    }

    received += rc;
  }
}

この function は、要求された数の bytesを常に読み取ることに注意してください。これが不可能な場合、 function はコンピュータ プログラムを終了させます。これは、通常の使用シナリオでは極端すぎる可能性があります。 allread() は、低レベルの APIでファイルにアクセスする方法の簡単なデモンストレーションとして扱われるべきです。

この functionについて説明しましょう。最初の興味深い部分は次のとおりです。

rc = read(fd, buf + received, len - received);

@received は、最初の loopの間はゼロです。したがって、この行は次と同等です。

rc = read(fd, buf, len);

read() は、 file descriptor (@fd) から @len bytes を読み取り、 data を buffer (@buf) に格納しようとします。

Xillybus device fileに関しては: read() は、要求された量の data (@len bytes) が利用できない場合、最大 10 ms まで待機します。この短い期間の後、関数は必要よりも少ない data を返します (ただし、少なくとも 1 つの byte)。 data がまったくない場合、 read() は、 data が FPGA から到着するまで無期限に待機します (ただし、例外があり、これについては以下で詳しく説明します)。この動作は Xillybusの driver に固有のものです (ただし、標準の APIには準拠しています)。

read() が何かを読み取ることができた場合、 @rc は読み取られた bytes の数に等しくなります。これは、 @rc が正の数であり、 loop 内のすべての if-statements がスキップされることを意味します。したがって、これは次に起こります。

received += rc;

したがって、 @received には常に、これまでに読み取られた bytes の総数が含まれます。 @rc が @len (要求された bytes の数) よりも小さいことは完全に合法であり、正常であることに注意してください。

while-loop は、 @received (読み取られた bytes の総数) が @lenに達するまで続きます。これは while statementに反映されています。

while (received < len) { ... }

したがって、必要に応じてさらに data が読み取られます。

rc = read(fd, buf + received, len - received);

今回は buffer での始点を @receivedで移動。要求された bytes の数も同じ数で減少します。これらの調整は、これが dataを読み取ろうとする繰り返しの試みであることを反映しているだけです。

read() が何も読み取らない場合

ここまでは、 read() が dataを読み取れるようになったときに何が起こるかに焦点を当ててきました。ただし、 read() が何も読み取らない状況が 3 つあります。これらの状況のそれぞれは、独自の if-statementによって処理されます。

POSIX signals

CTRL-C を押してプログラムを停止すると、オペレーティング システムは POSIX signal を processに送信します。これは、プログラムを終了させるメカニズムです。同じ目的で "kill" コマンドを使用すると、同じことが起こります。しかし、ほとんどの状況で無視されるべき signals の他のタイプもたくさんあります。

では、 process が read()への関数呼び出しの途中で signal を受信するとどうなるでしょうか? Linuxの規約によると、 read() はすぐに制御をメイン プログラムに戻す必要があります。これが発生する前に read() が data を読み取ることができた場合、異常は何も起こりません。 @rc には bytesの番号が含まれ、 signal が受信されたことを示すものはありません。

ただし、新しい data が到着していない場合、 @rc は負の数になり、 @errno は EINTRと等しくなります。この状況を処理する標準的な方法は、コードに示すとおりです。 何事もなかったかのように振舞って、もう一度やり直してください。

if ((rc < 0) && (errno == EINTR))
  continue;

これは、 signal が無視されるという意味ではありません。 たとえば、 signal の理由がユーザーが CTRL-Cを押したことが原因である場合、プログラムは通常どおり終了します。それを処理する別のメカニズムがあります。この if-statement の目的は、劇的なことを引き起こすことを意図していない signals を処理することです。 continue-statement は、 process がこの種の signal を受信した場合に、奇妙なことが起こらないことを保証します。

たとえば、 process が CTRL-Zで停止された場合、この if-statement は、実行が再開されたときにプログラムが引き続き実行されるようにするために必要です。さらに、人間の介入なしで到着できる signals が他にもいくつかあります。

本物の error

当然のことながら、 dataを読み取ろうとしているときに問題が発生する可能性があります。この場合、 @rc は負の数になり、 @errnoの値は EINTR以外の値になります。サンプル コードは、単にこのエラーを報告し、コンピューター プログラムを終了します。

if (rc < 0) {
  perror("allread() failed to read");
  exit(1);
}

EOF

ファイルの終わりに達したために read() が data を提供できない場合、この function は値ゼロを返します。もちろん、これは通常のファイルにも当てはまります。しかし、 Xillybus には、 data stream が終了したことを宣言する機能もあります。動作は同じです。

関連するコードは次のとおりです。

if (rc == 0) {
  fprintf(stderr, "Reached read EOF\n");
  exit(1);
}

これもエラーとして扱われ、コンピュータ プログラムが終了します。 これは、要求された量の data (@len bytes) が読み取られる前に EOF に到達したことを意味します。この if-statement に到達できるのは、 @received が @lenより小さい場合のみです。

allread() の背後にある考え方は、必要な量の dataを常に読み取るというものだったことを思い出してください。これが不可能な場合、この関数はコンピューター プログラムを停止します。

他のプログラミング言語との関連性

上記のコード例は Cで書かれていますが、プログラミング言語に関係なく重要な点がいくつか示されています。

device fileへの書き込み

ファイルへの書き込み用の低レベル API は、ファイルからの読み取りとほとんど同じです。これを実証するために、これは streamwrite.cにある allwrite()という名前の function です。

void allwrite(int fd, unsigned char *buf, int len) {
  int sent = 0;
  int rc;

  while (sent < len) {
    rc = write(fd, buf + sent, len - sent);

    if ((rc < 0) && (errno == EINTR))
      continue;

    if (rc < 0) {
      perror("allwrite() failed to write");
      exit(1);
    }

    if (rc == 0) {
      fprintf(stderr, "Reached write EOF (?!)\n");
      exit(1);
    }

    sent += rc;
  }
}

上記の allread() と比較してください。 違いは次の 3 つだけです。

したがって、原則として、書くことと読むことの間に違いはありません。

そうは言っても、ファイルへの書き込み時に EOF には意味がないため、 @rc はゼロであってはならないことに注意してください。 POSIX standardによると、 write() が bytesにゼロを書き込むように要求された場合、 @rc はゼロにしかなりません。しかし、これはこの while loopでは決して起こりません。

要約すると、 allwrite() は常に、要求された bytes の数を書き込みます。唯一の代替手段は、 processを終端することです。つまり、 device file が次のように開かれたとします。

int fd, len;
char *buf;

fd = open("/dev/xillybus_write_32", O_WRONLY);

@buf から @len bytes への書き込みは、次のように行われます。

allwrite(fd, buf, len);

allread() について上で述べたことはすべて、 allwrite() にも当てはまります。これには、他のプログラミング言語との関連性が含まれます。

Zero-length write

bytesゼロで write() への関数呼び出しを行うことができます。標準の API は、その特定のケースで何が起こるかを述べていません。しかし、明らかに、これは data が書き込まれないことを意味します。

この種の関数呼び出しは、 Xillybus device fileにとって特別な意味を持ちます。 bytes にゼロを書き込むことは、 flushを要求することを意味します。この意味を理解するために、まず data が device fileに書き込まれたときに何が起こるかを見てみましょう。

device file が asynchronous streamであると仮定しましょう。この用語については、別のページで簡単に説明し、 ドキュメントで詳しく説明しています。

data が device file ( write() を使用) に書き込まれると、 Xillybus driver はこの data を RAM bufferに格納します。この data の一部またはすべてがすぐに FPGA に送信される可能性があります。しかし、一般的に言えば、 data の量が bufferに残っている可能性があり、関数呼び出しを実行したコンピューター プログラムはそれにもかかわらず続行されます。このメカニズムの目的は、特に write()への関数呼び出しが多い場合に、パフォーマンスを向上させることです。

では、 driverの RAM buffer 内の data が FPGAに送信されるのはいつですか?次の 4 つの状況が考えられます。

したがって、 data が driverの buffer に長時間とどまることはありません。これは、 data が常に 10 msの期間内に FPGA に送信されるためです。しかし、一部のアプリケーションでは、この遅延でさえ受け入れられません。その場合、 zero-length write() を使用して、残りの data をすぐに送信するように要求できます。

C 言語でこれを行う方法は次のとおりです。

write(fd, NULL, 0);

address ~ buffer は NULLであることに注意してください。書き込む bytes の数はゼロなので、これで問題ありません。ただし、この関数呼び出しは、要求が成功することを保証しません。成功する可能性は非常に高いですが、これが正しい方法です。

while (1) {
  rc = write(fd, NULL, 0);

  if ((rc < 0) && (errno == EINTR))
    continue; // Interrupted. Try again.

  if (rc < 0) {
    perror("flushing failed");
    break;
  }

  break; // Flush successful
}

これはすべて、 asynchronous streamに対して言われました。 device file が synchronous streamである場合、 data は常に、 write()への関数呼び出しの結果としてすぐに FPGA に送信されます。それに加えて、 write() は、 data が FPGA に到達するまで待ってから戻ってきます ( zero-length write() はそうしません)。

したがって、 zero-length write() は asynchronous streamsにのみ関連します。 FPGAとの通信が遅くなるため、この機能は必要でない限り使用しないでください。

概要

すでに述べたように、上記のほとんどすべては、任意のファイルへのアクセスに適しています。 Xillybusに固有のトピックは 2、3 だけです。

FPGAとの通信中に一貫した動作を確保するには、これらのガイドラインに従うことが重要です。これらのトピックを考慮せずに作成されたコンピューター プログラムは、時折失敗する可能性があります。これらの障害は、 FPGA または driverに問題があるように見えることがよくあります。したがって、適切なプログラミング手法により、多くの混乱と不必要な労力を節約できます。

このページは英語から自動翻訳されています。 不明な点は元のページを参照してください。
Copyright © 2021-2024. All rights reserved. (6f913017)