【问题标题】:Terminal I/O (how to emulate terminal read/wrtie with specific memory region?)终端 I/O(如何使用特定内存区域模拟终端读/写?)
【发布时间】:2010-12-28 20:27:53
【问题描述】:

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

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

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

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

【问题讨论】:

    标签: linux embedded terminal asymmetric


    【解决方案1】:

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

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

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

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

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

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

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

    【讨论】:

    • 这个建议不错(简单易懂),我会朝这个方向研究!
    【解决方案2】:

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

    编辑:

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

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

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

    【讨论】:

    • 普通的IPC方法和类似的内核服务可能不适用,因为RT进程不是在linux内核的监督下运行的(更多的是相反——内核往往以最低的方式运行)实时调度程序的优先任务)
    • @Chris 这是一个很好的观点。实时系统本身就太复杂了:(编辑我的答案以弥补。
    • 感谢您的链接,它看起来像是我正在寻找的答案!我将阅读它并将解决方案与第一篇文章结合起来。
    【解决方案3】:

    我已成功使用共享内存 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 索引在读取数据之前不更新。对于 writer 也是如此 - 只要在将数据写入缓冲区之前不更新 tail 索引,它就可以写入缓冲区的“空闲”部分。唯一真正复杂的是确保将适当的项目标记为 volatile 并调用适当的内存屏障。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2014-04-29
      • 2011-10-25
      • 1970-01-01
      • 1970-01-01
      • 2011-12-15
      • 1970-01-01
      • 1970-01-01
      • 2015-09-29
      相关资源
      最近更新 更多