序章
このページはXillybus' programming guide for Linuxに基づいています。以下のトピックのより包括的なビューについては、このガイドを参照することをお勧めします。 Microsoft 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 種類の混乱が生じる可能性があります。
- I/O operation の data の量は、予想とは異なる場合があります (通常は予想よりも少なくなります)。
- FPGA との通信が予想より遅く発生します。
プログラミング言語とオペレーティング システム
Linuxでは、ファイルにアクセスできるすべてのツールまたはプログラミング言語が Xillybusで動作します。
Windows を使用する場合、 C、 C++、 C#、 Python、 Perl 、および Cygwinに付属するものなど、一般的なプログラミング言語では問題ありません。ただし、一部のツール ( MATLABなど) は、 Xillybusの device filesでの動作を拒否する場合があります。 これらのツールは、 device file が通常のファイルではないことを検出し、それをエラーと見なします。この状況には回避策があり、通常は low-level I/O用の extensions を使用します。
以下の説明は C 言語に基づいていますが、トピックはすべてのプログラミング言語に関連しています。
例 code
Xillybusの Web サイトからダウンロードできるコーディング例があります。これらの例は Cで書かれており、低レベルの API を正しく使用する方法を示しています。
サンプルをダウンロードするには、 demo bundle をダウンロードした Web ページ (つまり、 Xillybusのページまたは XillyUSBのページ) に移動します。
Linuxを使用している場合は、 Linux driverをダウンロードしてください。サンプルの code は同じ .tar.gz ファイルに含まれています。
Windowsを使用している場合は、 Xillybus package for Windowsをダウンロードしてください。
いずれにしても、例の code は demoapps/ subdirectory内にあります。この codeでは、小さな buffer (128 bytes) が割り当てられているため、各 I/O operation で読み書きされる data の量が少ないことに注意してください。この目的は、 code をシンプルにすることだけです。実際のアプリケーションでは、より大きな buffer が推奨されます (通常は32 kBytes が適切な選択です)。
Linux と Windows の違いは小さいですが重要です。特に:
- functions の名前は少し異なります。 _open() 対 open() など
- Windowsの場合: ファイルを開くときは、 _O_BINARY を使用する必要があります。
そうは言っても、例の code の背後にある原理はまったく同じです。
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です。この program は demoapps/ directoryにあります。ただし、以下に示す code は別のプログラムからのものです。 memread.c (同じ directoryから)。この program は別の目的のために用意されていますが、 allread()という名前の function が含まれています。この functionを使用していくつかのトピックを説明する方が便利です。
この commandでファイルを開いたとします。
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 は program を終了させます。これは通常の使用シナリオでは過激すぎる可能性があります。 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に関しては: 要求された量の data (@len bytes) が利用できない場合、 read() は最大 10 ms まで待機します。この短い時間が経過すると、 function は必要な量よりも少ない data (ただし、少なくとも 1 つの byte) を返します。 data がまったくない場合、 read() は FPGA から data が到着するまで無期限に待機します (ただし、例外もあります。これについては後述します)。 この動作は、 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() がデータを読み取れる場合に何が起こるかに焦点を当ててきました。ただし、 read() が何も読み取らない状況が 3 つあります。これらの状況はそれぞれ、独自の if-statementによって処理されます。
POSIX signals
CTRL-C を押して programを停止すると、オペレーティング システムは processに POSIX signal を送信します。これが program を終了するメカニズムです。同じ目的で "kill" command を使用した場合も同じことが起こります。ただし、他の多くの種類の signals もあり、ほとんどの場合は無視する必要があります。
では、 process が read()への関数呼び出しの途中で signal を受信した場合はどうなるでしょうか。 Linuxの規則によれば、 read() はすぐにメインの program に制御を戻す必要があります。これが起こる前に read() が data を読み取ることができた場合、異常なことは何も起こりません。 @rc には bytesの番号が含まれ、 signal が受信されたことを示すものはありません。
しかし、新しい data が到着していない場合、 @rc は負の数になり、 @errno は EINTRと等しくなります。この状況を処理する標準的な方法は、 codeに示されているとおりです。 何事もなかったかのように振舞って、もう一度やり直してください。
if ((rc < 0) && (errno == EINTR))
continue;
これは、 signal が無視されるという意味ではありません。 たとえば、 signal の原因がユーザーが CTRL-Cを押したことである場合、 program は通常どおり終了します。これを処理する別のメカニズムがあります。この if-statement の目的は、劇的なことを引き起こすことを意図していない signals を処理することです。 continue-statement は、 process がこの種の signal を受信しても、異常なことが起こらないようにします。
たとえば、 process が CTRL-Zで停止した場合、実行が再開されたときに program が引き続き実行されるようにするには、この if-statement が必要です。さらに、人間の介入なしに到着する可能性のある他の signals がいくつかあります。
本当の間違い
当然ですが、 dataを読み取ろうとすると、何か問題が発生することがあります。その場合、 @rc は負の数になり、 @errnoの値は EINTR以外の値になります。例の code は単にこのエラーを報告し、 programを終了します。
if (rc < 0) {
perror("allread() failed to read");
exit(1);
}
EOF
ファイルの終わりに達したために read() が data を提供できない場合、この function は値ゼロを返します。もちろん、これは通常のファイルにも当てはまります。しかし、 Xillybus には、 data stream が終了したことを宣言する機能もあります。動作は同じです。
関連する code は次のとおりです。
if (rc == 0) {
fprintf(stderr, "Reached read EOF\n");
exit(1);
}
これもエラーとして扱われ、 programが終了します。 これは、要求された量の data (@len bytes) が読み取られる前に EOF に到達したことを意味します。この if-statement に到達できるのは、 @received が @lenより小さい場合のみです。
allread() の背後にある考え方は、常に必要な量の dataを読み取るというものだったことを思い出してください。これが不可能な場合、この function はコンピューター programを停止します。
他のプログラミング言語との関連性
上記の例 code は Cで書かれていますが、プログラミング言語に関係なく関連するいくつかの重要なポイントを示しています。
- read() は、要求されたよりも少ない data で返される場合があります。これは buffered I/O でも発生する可能性があります (例: fread() )。しかし、 buffered I/Oでは、これはエラーが発生した場合、または EOF に達した場合にのみ発生します。対照的に、これは read()では正常であり、何か特別なことが起こったことを示しているわけではありません。
- program は POSIX signals を適切に処理する必要があります。
- read() は、すべての data が読み取られ、 end-of-file (EOF) に到達した後、値ゼロで戻ります。
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 つだけです。
- read()の代わりに write() を使用します。しかし、これらの functions はまったく同じように使用されます。
- variable @received の名称が @sentに変更されました。ただし、違いは variableの名称のみです。この variable の意味と使い方は全く同じです。
- テキスト出力は次のように調整されます。 以前は「read」と言いましたが、「write」と言いました。
したがって、原則として、書くことと読むことの間に違いはありません。
そうは言っても、ファイルへの書き込み時に 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に残り、関数呼び出しを実行した program は引き続き実行されます。このメカニズムの目的は、特に write()への関数呼び出しが多い場合に、パフォーマンスを向上させることです。
では、 driverの RAM buffer 内の data が FPGAに送信されるのはいつですか?次の 4 つの状況が考えられます。
- RAM buffer がいっぱいになります。
- device file は閉じています。
- 10 ms の期間が経過しました (automatic flush)
- zero-length write() が実行されます
したがって、 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との通信中に一貫した動作を確保するには、これらのガイドラインに従うことが重要です。これらのトピックを考慮せずに作成された Programs は、時折障害が発生する可能性があります。これらの障害は、多くの場合、 FPGA または driverの問題として現れます。適切なプログラミング手法により、多くの混乱や不要な労力を節約できます。