我已经成功地使用共享内存 fifo 系统在进程之间进行通信(尽管与您的情况不同)。关键是只有一个线程可以是生产者,一个线程可以是消费者。您还需要确保正如 Chris Stratton 提到的那样,使用适当的内存屏障正确处理任何缓存。Linux 有一个非常直接的内存屏障 API,我不知道你的实时应用程序可能有什么可用的。我试图确定可能需要内存屏障的位置。
以下是共享 fifo 的未经测试(且完全未优化)的实现。您的 RT 应用程序可以将字符写入 fifo,而 Linux 应用程序或驱动程序可以从 fifo 中读取字符。理想情况下,您将有一种机制让 Linux 端收到数据准备就绪的信号(也许是一个未使用的 GPIO,它可以在 RT 端戳它时触发中断?)。否则,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 缓冲区。
这个设置的关键是读取器可以读取fifo中的数据,而不必担心写入器会覆盖它,只要在head
读取数据之前不更新索引即可。与写入器类似 - 只要tail
在数据写入缓冲区之前不更新索引,它就可以写入缓冲区的“空闲”部分。唯一真正的复杂性是确保标记了适当的项目volatile
并调用了适当的内存屏障。