共有メモリ fifo システムを使用してプロセス間で通信することに成功しました (ただし、同じシナリオではありません)。重要なのは、1 つのスレッドのみがプロデューサーになり、1 つのスレッドがコンシューマーになることができるということです。また、Chris Stratton が述べたように、キャッシュは適切なメモリ バリアを使用して適切に処理されることを確認する必要があります。Linux には、メモリ バリア用の非常に単純な API があります。リアルタイム アプリで何が利用できるかはわかりません。メモリバリアが必要な場所を特定しようとしました。
以下は、テストされていない (そして完全に最適化されていない) 共有 fifo の実装です。RT アプリは fifo に文字を書き込むことができ、Linux アプリまたはドライバーは fifo から文字を読み取ることができます。理想的には、データの準備が整ったことを Linux 側に通知するメカニズムを用意する必要があります (おそらく、RT 側が突いたときに割り込みを発生させる未使用の GPIO でしょうか?)。それ以外の場合、Linux 側は fifo 内のデータをポーリングできますが、通常の理由から、これはおそらく理想的ではありません。
struct fifo {
char volatile* buf;
int buf_len;
int volatile head; // index to first char in the fifo
int volatile tail; // index to next empty slot in fifo
// if (head == tail) then fifo is empty
// if (tail < head) the fifo has 'wrapped'
};
void fifo_init( struct fifo* pFifo, char* buf, int buf_len)
{
pFifo->buf = buf;
pFifo->buf_len = buf_len;
pFifo->head = 0;
pFifo->tail = 0;
}
int fifo_is_full( struct fifo* pFifo)
{
int head;
int tail;
// a read barrier may be required here
head = pFifo->head;
tail = pFifo->tail;
// fifo is full if ading another char would cause
// tail == head
++tail;
if (tail == pFifo->buf_len) {
tail = 0;
}
return (tail == head);
}
int fifo_is_empty( struct fifo* pFifo)
{
int head;
int tail;
// a read barrier may be required here
head = pFifo->head;
tail = pFifo->tail;
return head == tail;
}
// this function is the only one that modifies
// the pFifo->tail index. It can only be used
// by a single writer thread.
int fifo_putchar( struct fifo* pFifo, char c)
{
int tail = pFifo->tail;
if (fifo_is_full(pFifo)) return 0;
pFifo->buf[tail] = c;
++tail;
if (tail == pFifo->buf_len) {
tail = 0;
}
//note: the newly placed character isn't actually 'in' the fifo
// as far as the reader thread is concerned until the following
// statement is run
pFifo->tail = tail;
// a write barrier may need to be placed here depending on
// the system. Microsoft compilers place a barrier by virtue of
// the volatile keyword, on a Linux system a `wmb()` may be needed
// other systems will have other requirements
return 1;
}
// this function is the only one that modified the
// pFifo->head index. It can only be used by a single
// reader thread.
int fifo_getchar( struct fifo* pFifo, char* pC)
{
char c;
int head = pFifo->head;
if (fifo_is_empty(pFifo)) return 0;
// a read barrier may be required here depending on the system
c = pFifo->buf[head];
++head;
if (head == pFifo->buf_len) {
head = 0;
}
// as far as the write thread is concerned, the char
// hasn't been removed until this statement is executed
pFifo->head = head;
// a write barrier might be required
*pC = c;
return 1;
}
インデックスを更新するときは、プラットフォームの「アトミック」API を使用する方が適切な場合があります。
実行できるいくつかの最適化:
- fifo サイズが 2 の累乗に制限されている場合、インデックスを適切にマスキングすることでラッピングを処理できます。
- put/get 関数を変更するか、追加の get/put 関数を追加して文字列またはデータ バイトの配列を受け入れることができ、文字列/配列を FIFO バッファに (またはから) より効率的にコピーできます。
このセットアップの鍵はhead
、データが読み取られるまでインデックスが更新されない限り、ライターがデータを上書きすることを心配することなく、リーダーが fifo 内のデータを読み取ることができることです。tail
ライターの場合も同様です。データがバッファーに書き込まれるまでインデックスが更新されない限り、バッファーの「空き」部分に書き込むことができます。唯一の実際の複雑さは、適切なアイテムがマークされvolatile
、適切なメモリ バリアが呼び出されることを確認することです。