1

我在嵌入式平台上工作,在那里我可以完全访问直接读/写物理内存。

我还在研究非对称处理,其中我有一个与 Linux 并发运行但与 Linux 完全隔离的实时应用程序。

我想将来自 RT 应用程序的消息显示到 Linux 控制台(并且可能将命令从 Linux 发送到 RT 应用程序)。我目前的解决方案是将 RT 应用程序中的所有内容输出到串行端口。然后,在 Linux 中,我将读取串行端口输入。

这可行,但似乎没有必要,因为 RT 应用程序和 Linux 在同一台物理机器上。回想一下串口的工作原理,它有一个内存缓冲区,应用程序可以读取/写入该缓冲区。因此,我想知道是否可以将终端显示器连接到特定的内存区域(即 0x10000)并且当 RT 应用程序“打印”一些消息到 0x10000 时,Linux 终端会显示该消息?

4

3 回答 3

1

您可以使用“邮箱”技术构建一种虚拟串行端口。我将描述一个简单的工作连接的一半,您可能希望每个方向都有一个,这样您就可以发送命令并获得响应。

在内核初始化期间保留一块内存。假设是 4k,因为这通常是一页,但 256 甚至 16 字节也可以。为另一个方向预留一秒钟。

当通道的“作者”想要说些什么时,它首先检查第一个 32 位字是否为零。如果是,那么从第 5 个字节开始,它会写入一条消息 - 文本或二进制数据,无论如何,最多 4k - 4 = 4092 字节。然后它将第一个字设置为它已写入的字节数。

接收器观察它正在接收的通道的第一个字中的字节数。当它看到一个非零字节数时,它会从内存中读取那么多字节。然后它将字节计数设置为零,以向写入者表明现在可以在方便时写入新消息。

这取决于您实际访问真实内存或通过相同的缓存工作,并且您有一个用于写入字节数的原子写入操作(如果您没有 32 位原子写入,请使用 16 - 位计数,无论如何都足够了,或者使缓冲区更小并使用 8 位计数)。由于作者只能在它为零时将其设置为非零值,而读者只能在它为非零时将其设置为零值,所以一切正常。

这当然是一个简单的机制,任何一方都可以被另一方阻止。但是您可以设计获取消息的组件以将其考虑在内。您还可以通过想出一种方法让多条消息在传输中,或者并行添加一个额外的优先级或错误报告通道来扩展它。

哦,在你开始编写代码之前,先做一些网络搜索。我确信已经有一些类似的机制或其他可用于连接您的 RT 和 linux 组件的机制。但是学习自己做也很有趣——如果你在一个没有为你提供功能的操作系统的小型嵌入式系统上遇到这种问题,这是必要的。

于 2010-12-29T04:30:11.670 回答
1

在 linux 中执行 IPC 的方法有几种,通常会涉及到文件描述符。在我看来,你最好的选择是继续做你正在做的事情,正如你所说的那样,这可能是矫枉过正,但尝试实现自己的共享内存解决方案肯定更加矫枉过正。

编辑:

正如评论中提到的那样,您正在运行实时进程这一事实会让事情变得很糟糕,而原生 IPC 可能不是您最好的选择。是我刚刚在 Google 上搜索的一篇文章,似乎可以提供您正在寻找的答案。

如果您不想阅读所有内容,它建议您使用 FIFO 或共享内存作为您应该使用的并发原语,具体取决于您需要的通信类型。从个人经验来看,从长远来看,FIFO 可以减少令人头疼的问题,因为您不必担心同步问题。

如果您想在终端中监视程序,您很可能必须编写一个从先进先出/共享内存读取并将消息发送到标准输出的小程序。

于 2010-12-29T04:41:31.867 回答
0

我已经成功地使用共享内存 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并调用了适当的内存屏障。

于 2010-12-30T07:06:27.217 回答