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

发布于 2024-10-09 15:36:42 字数 385 浏览 5 评论 0原文

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

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

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

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

I'm working on the Embedded platform, where I have full access to directly read/write physical memory.

I'm also working on asymmetric processing, in which I have a real time application running concurrently with Linux but totally isolated from Linux.

I want to display the messages from RT app to Linux console (and perhaps send command from Linux to RT app). My current solution is to output everything from RT app to serial port. Then, in Linux I will read the serial port input.

This works but it seems unnecessary, because the RT apps and Linux is on the same physical machine. Thinking back how serial port works, it has a memory buffer and application can read/write to that buffer. Thus, I was wondering if it is possible to connect a terminal display to a specific memory region (i.e. 0x10000) and when RT app "print" some message to 0x10000, Linux terminal would display the message?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

烈酒灼喉 2024-10-16 15:36:42

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

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

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

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

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

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

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

You could construct a sort of virtual serial port using "mailbox" techniques. I'll describe half of a simple working connection, you probably want one of these going in each direction so you can send commands and get responses.

Reserve a chunk of memory during kernel initialization. Let's say 4k since that's often a page, but 256 or even 16 bytes will work too. Reserve a second for the other direction.

When the "writer" of a channel wants to say something, it first checks if the first 32-bit word is zero. If it is, then starting from the 5th byte, it writes a message - textual or binary data, whatever, up to a maximum of 4k - 4 = 4092 bytes. Then it sets the first word equal to the count of bytes it has written.

The receiver watches the byte count in the first word of the channel it is receiving. When it sees a non-zero byte count, it reads that many bytes from memory. Then it sets the byte count to zero, to signify to the writer that a new message may now be written at its convenience.

The only thing this depends on is that you actually access the real memory or work through the same cache, and that you have an atomic write operation for writing the byte count (if you don't have 32-bit atomic writes, use a 16-bit count which is plenty anyway, or make the buffer smaller and use an 8-bit count). Since the writer can only set it to a nonzero value when it's zero, and the reader can only set it to a zero value when it's non-zero, it all works out.

This is of course a simple mechanism, and either side can be blocked by the other. But you can design the components that source the messages to take that into account. You can also extend it by coming up with a way to have multiple messages in flight, or adding an additional priority or error-reporting channel in parallel.

Oh, before you jump in and code this, do some web searching. I am certain there's already some mechanism like this or something else available for connecting your RT and linux components. But learning to do it yourself can be interesting too - and necessary if you run into this kind of problem on a smaller embedded system without an OS that provides the functionality for you.

蝶…霜飞 2024-10-16 15:36:42

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

编辑:

正如评论中提到的,您正在运行实时进程这一事实会使事情变得混乱,并且本机 IPC 可能不是您的最佳选择。 这里是我刚刚在 Google 上搜索到的一篇文章,似乎提供了您正在寻找的答案。

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

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

There are a few ways of performing IPC in linux, and file descriptors are usually involved. In my opinion your best bet is to continue doing what you're doing, it is probably overkill as you said, but trying to implement your own shared memory solution is definitely even more so overkill.

EDIT:

As mentioned in the comments, the fact that you're running a real-time process throws things off, and native IPC probably isn't your best bet. Here's an article I just Googled that seems to provide the answer you're looking for.

If you don't want to read all of it, it suggests either FIFOs or Shared Memory as the concurrency primitive you should use, depending on what kind of communication you require. From personal experience, FIFOs lead to less headaches in the long run, because you have to worry a lot less about synchronization.

You'd most likely have to write a small program that reads from the fifo/shared memory and sends messages to stdout, if you want to monitor the program in a terminal.

勿忘心安 2024-10-16 15:36:42

我已经成功地使用共享内存 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 缓冲区。

此设置的关键是,只要读取数据后 head 索引没有更新,读取器就可以读取 fifo 中的数据,而不必担心写入器会覆盖它。对于写入器来说也是如此 - 只要在数据写入缓冲区之前 tail 索引没有更新,它就可以写入缓冲区的“空闲”部分。唯一真正的复杂性是确保适当的项目被标记为易失性并且调用适当的内存屏障。

I've successfully used a shared memory fifo system to communicate between processes (though not the same scenario you have). The key is that only a single thread can be the producer and a single thread can be a consumer. You'll also need to make sure that as Chris Stratton mentioned that any caching is properly dealt with using appropriate memory barriers. Linux has a pretty straight forward API for memory barriers, I don't know what your real time app might have available to it. I've tried to identify where memory barriers might be required.

The following is an untested (and completely non-optimized) implementation of a shared fifo. Your RT app can write characters to the fifo, and the Linux app or driver can read characters out of the fifo. Ideally you'll have a mechanism for the Linux side to be signaled that data is ready (maybe an otherwise unused GPIO that can fire an interrupt when the RT side pokes it?). Otherwise, the Linux side could poll for data in the fifo, but that's probably less than ideal for the usual reasons.

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;
}

It might be more appropriate to use platform 'atomic' APIs when updating the indexes.

Some optimizations that can be performed:

  • if the fifo size is restricted to a power of 2, the wrapping can be handled by masking the indexes appropriately
  • the put/get functions could be changed or additional get/put functions could be added to accept a string or an array of data bytes, and the string/array can be copied to (or from) the fifo buffer more efficiently.

The key to this set up is that the reader can read data in the fifo without worry that the writer will overwrite it as long as the head index isn't updated until after the data is read out. Similarly for the writer - it can write into the 'free' portion of the buffer as long as the tail index isn't updated until after the data has be written through to the buffer. The only real complication is making sure that appropriate items are marked volatile and that appropriate memory barriers are called.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文