Linuxのシリアルポートを介してプロトコルを実装しています。プロトコルは要求応答スキームに基づいているため、スループットは、パケットをデバイスに送信して応答を取得するのにかかる時間によって制限されます。デバイスは主にアームベースであり、Linux> = 3.0を実行します。ラウンドトリップ時間を10ms未満に短縮するのに問題があります(115200ボー、8データビット、パリティなし、メッセージあたり7バイト)。
どのIOインターフェイスが最小のレイテンシーを提供しますか:ioctlを使用して手動で選択、ポーリング、epoll、またはポーリングしますか?ブロッキングまたは非ブロッキングIOレイテンシーに影響しますか?
Setserialでlow_latencyフラグを設定してみました。しかし、効果がなかったようです。
レイテンシーを減らすために他にできることはありますか?私はすべてのデバイスを制御しているので、カーネルにパッチを適用することも可能ですが、そうではない方が望ましいです。
----編集----
使用するシリアルコントローラは16550Aです。
このトピックについてさらに何人かのエンジニアと話をしたところ、この問題はユーザースペースでは解決できないという結論に達しました。ブリッジを越えてカーネルランドに入る必要があるため、プロトコルを通信し、1ミリ秒未満のレイテンシを提供するカーネルモジュールを実装する予定です。
---編集---
私は完全に間違っていたことがわかりました。必要なのは、カーネルティックレートを上げることだけでした。デフォルトの100ティックは、10ミリ秒の遅延を追加しました。 1000Hzとシリアルプロセスの負のNice値は、到達したい時間動作を示します。
要求/応答スキームは非効率的である傾向があり、シリアルポートにすぐに表示されます。スループットに関心がある場合は、Kermitファイル送信プロトコルなどのウィンドウプロトコルを確認してください。
プロトコルを守り、レイテンシーを減らしたい場合、選択、ポーリング、読み取りはすべてほぼ同じレイテンシーを提供します。これは、Andy Rossが示したように、実際のレイテンシーはハードウェアFIFO処理にあるためです。
運が良ければ、パッチを適用せずにドライバーの動作を微調整できますが、それでもドライバーコードを確認する必要があります。ただし、ARMが10kHzの割り込みレートを処理することは、システム全体のパフォーマンスにとって確かに良くありません...
もう1つのオプションは、パケットをパディングして、毎回FIFOしきい値に達するようにすることです。また、FIFOしきい値の問題であるかどうかも確認します。
100バイトを送信するには10ミリ秒@ 115200で十分です(8N1を想定)。したがって、表示されているのは、おそらくlow_latencyフラグが設定されていないためです。試してみてください
setserial /dev/<tty_name> low_latency
Low_latencyフラグを設定します。これは、ttyレイヤーでデータを上に移動するときにカーネルで使用されます。
void tty_flip_buffer_Push(struct tty_struct *tty)
{
unsigned long flags;
spin_lock_irqsave(&tty->buf.lock, flags);
if (tty->buf.tail != NULL)
tty->buf.tail->commit = tty->buf.tail->used;
spin_unlock_irqrestore(&tty->buf.lock, flags);
if (tty->low_latency)
flush_to_ldisc(&tty->buf.work);
else
schedule_work(&tty->buf.work);
}
Schedule_work呼び出しは、観察された10ミリ秒の遅延の原因である可能性があります。
LinuxのシリアルポートはUNIXスタイルのターミナル構造に「ラップ」されており、1ティックラグ、つまり10ミリ秒でヒットします。 stty -F /dev/ttySx raw low_latency
が役立つかどうか試してみてください。ただし、保証はありません。
PCでは、ハードウェアに接続して標準のシリアルポートと直接通信し、setserial /dev/ttySx uart none
を発行してLinuxドライバーをシリアルポートハードウェアからバインド解除し、inb/outb
を介してポートレジスタにポートを制御できます。私はそれを試しました、それはうまくいきます。
欠点は、データが到着したときに割り込みが発生せず、レジスタをポーリングする必要があることです。しばしば。
アームデバイス側でも同じことができるはずですが、エキゾチックなシリアルポートハードウェアでははるかに難しいかもしれません。
要するに:USBアダプターとASYNC_LOW_LATENCYを使用します。
Modbusで115.2kbsのFT232RLベースのUSBアダプターを使用しました。
ASYNC_LOW_LATENCYを使用すると、合計約20 mSで約5つのトランザクション(4つのデバイスに対して)を取得します。これには、スローポークデバイスへの2つのトランザクション(4 mSの応答時間)が含まれます。
ASYNC_LOW_LATENCY
がない場合、合計時間は約60mSです。
FTDI USBアダプターを使用すると、ASYNC_LOW_LATENCY
は、チップ自体の文字間タイマーを1 mS(デフォルトの16 mSではなく)に設定します。
現在、自家製のUSBアダプターを使用しており、アダプター自体のレイテンシーを任意の値に設定できます。 200 µSに設定すると、その20mSからさらにmSが削減されます。
ポートのファイル記述子に低遅延を設定するためにsetserial
が行うことは次のとおりです。
ioctl(fd, TIOCGSERIAL, &serial);
serial.flags |= ASYNC_LOW_LATENCY;
ioctl(fd, TIOCSSERIAL, &serial);
これらのシステムコールはいずれも遅延に影響を与えません。ユーザースペースから1バイトをできるだけ速く読み書きしたい場合は、単純なread()/write()
ペアよりもうまくいくことはありません。シリアルストリームを別のユーザースペースプロセスのソケットに置き換えて、遅延が改善されるかどうかを確認してください。そうでない場合、問題はCPU速度とハードウェアの制限です。
あなたのハードウェアがこれを行うことができると確信していますか?多くのバイトに相当するレイテンシーを導入するバッファー設計のUARTを見つけることは珍しいことではありません。
これらの回線速度では、準備状況を確認する方法に関係なく、それほど大きな遅延は見られないはずです。
シリアルポートがrawモードになっていること(「非正規読み取り」を行うため)、およびVMINとVTIMEが正しく設定されていることを確認する必要があります。キャラクター間タイマーが作動しないように、VTIMEがゼロであることを確認する必要があります。おそらく、VMINを1に設定して、そこから調整します。
Syscallのオーバーヘッドは、ネットワーク上の時間と比較して何もないため、select()とpoll()などが違いを生む可能性はほとんどありません。