终端I / O(如何模拟具有特定内存区域的终端读/写?)

时间:2010-12-28 20:27:53

标签: linux embedded terminal asymmetric

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

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

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

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

3 个答案:

答案 0 :(得分:1)

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

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

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

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

唯一依赖的是你实际访问真实内存或通过相同的缓存工作,并且你有一个原子写操作来写字节数(如果你没有32位原子写,使用16位计数,无论如何,或者使缓冲区更小并使用8位计数)。由于编写器只能在零值时将其设置为非零值,并且读取器只能在非零值时将其设置为零值,所以这一切都可以解决。

这当然是一种简单的机制,任何一方都可以被另一方阻止。但是,您可以设计源消息的组件以将其考虑在内。您还可以通过提供一种在线投放多条消息,或者并行添加其他优先级或错误报告渠道来扩展它。

哦,在你进入并编码之前,做一些网络搜索。我确信已经有一些像这样的机制或其他可用于连接RT和Linux组件的机制。但是自己学习也很有趣 - 如果你在没有为你提供功能的操作系统的小型嵌入式系统上遇到这种问题,那就很有必要。

答案 1 :(得分:1)

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

编辑:

正如评论中所提到的,你正在运行一个实时进程的事实会引发关注,而本机IPC可能不是你最好的选择。 Here我刚刚用谷歌搜索的一篇文章似乎提供了你正在寻找的答案。

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

如果你想监视终端中的程序,你很可能必须编写一个从fifo / shared内存中读取并向stdout发送消息的小程序。

答案 2 :(得分: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并调用适当的内存障碍。