如何在 C 中跨线程进行动态数据传输和内存管理?

发布于 2024-07-16 03:06:43 字数 459 浏览 3 评论 0原文

平台:ARM9

编程语言 C

要求 - 纯 C,无外部库且无 boost。

操作系统 - REX RTOS

我有两个线程在嵌入式平台中运行 -

  1. 一个在驱动程序级别处理与硬件的所有通信和数据传输。
  2. 第二个线程运行使用与硬件之间的数据的应用程序。

这个想法是将应用程序线程与驱动程序线程解耦,以便我们可以更改硬件驱动程序线程中的硬件和实现,但对应用程序线程的影响最小。

我的挑战是,从硬件接收的数据可能是动态的,即我们不预先知道应用程序线程应该为每个发往/来自硬件的请求留出多少内存,因为这是在运行时确定的。

我认为驱动程序线程可以通知应用程序线程有太多数据需要读取。 然后应用程序线程分配内存并请求驱动程序线程读取数据。 然后由应用程序线程来相应地处理数据。 这样,所有内存管理都在应用程序线程内进行。

Platform: ARM9

Programming Language C

Requirements - plain C and no external libs and no boost.

OS - REX RTOS

I have two threads running in an embedded platform -

  1. one is at driver level handling all the comms and data transfer with the hardware.
  2. the second thread runs the application that uses the data to/from the hardware.

The idea is to decouple the app thread from the driver thread so we can change the hardware and implementation in the hardware driver thread but have minimal impact on the application thread.

My challenge is that the data received from the hardware may be dynamic i.e. we do not know upfront how much memory the application thread should set aside for each request to/from the hardware as this is determined at run-time.

I was thinking the driver thread could inform the application thread that there is so much data to read. The application thread then allocates the memory and requests the driver thread to read the data. It is then up to the application thread to process the data accordingly. This way, all the memory management is within the application thread.

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

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

发布评论

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

评论(4

傲世九天 2024-07-23 03:06:43

我想到了几个选项:

1)在驱动程序中分配内存,在应用程序中释放它。 但是......我们倾向于避免在任何接近实时要求的情况下使用 malloc。 如果您可以访问 malloc/free,并且不存在“实时”问题或内存碎片问题(即您的堆足够大),那么这是一个相当简单的方法。 驱动程序只是通过消息队列将分配的指针发送到应用程序线程,完成后应用程序会释放内存。 注意内存泄漏。

2) 环形或循环缓冲区。 驱动程序完全管理固定大小的环形缓冲区,并在缓冲区准备就绪时简单地向应用程序发送消息。 请参阅此处了解一些详细信息:循环缓冲区。 然后,应用程序通过驱动程序 API 再次将数据标记为“可用”,这有助于向应用程序线程隐藏环形缓冲区详细信息。 我们对一个驱动程序使用这种方法,该驱动程序具有与您所描述的一组非常相似的要求。 在这种情况下,您需要关心确定环形缓冲区的“最佳”大小、驱动程序中的溢出处理等。

祝您好运!

Couple options come to mind:

1) malloc the memory in the driver, free it in the app. But... we tend to avoid malloc use in anything that approaches a real time requirement. If you have access to malloc/free, and there are no "real time" concerns or memory fragmentation issues (i.e. your heap is large enough), then this is a fairly easy approach. The driver just sends the allocated pointer to the app thread via a message queue and the app free's the memory when done. Watch out for memory leaks.

2) Ring or circular buffers. The driver completely manages a fixed size ring buffer and simply sends a message to the application when a buffer is ready. See here for some details: Circular buffer. Then the application marks the data "available" again via a driver API, which helps hide the ring buffer details from the app thread. We use this approach for one of our drivers that has a very similiar set of requirements as you describe. In this case, you need to be concerned with determining the "best" size for the ring buffer, overflow handling in the driver, etc.

good luck!

落在眉间の轻吻 2024-07-23 03:06:43

您没有指定操作系统,但您以某种方式拥有“线程”。 除了其中一个处于驱动程序级别(中断处理程序)而另一个听起来像应用程序(用户态/内核)。 但这也不匹配,因为您的驱动程序和应用程序甚至在数据处理之前就已经进行了通信。

您的术语令人困惑且不鼓舞人心。 这是否是自制(RT)操作系统?

如果您有一个真正的操作系统,则有一些既定的方法可以编写驱动程序并将数据传递给用户空间。 阅读文档或使用现有驱动程序之一作为参考。

如果这是一个自定义操作系统,您仍然可以参考其他开源驱动程序以获取想法,但显然您不会方便地进行设置。 预先分配驱动程序代码中的所有内存,在数据到达时填充它,然后将其交给应用程序代码。 内存量将取决于您的应用程序处理数据的速度、您计划接受的最大数据量以及支持您的应用程序需要多少内部数据队列。

You don't specify an OS, but you somehow have "threads". Except that one of them is at driver level (interrupt handler) and the other sounds like an application (userland/kernel). But that doesn't match up either, because your driver and app are communicating before the data is even processed.

Your terminology is confusing and not encouraging. Is this a homebrew (RT)OS or not?

If you have a real OS, there are established methods for writing drivers and handing data to userland. Read the documentation or use one of the existing drivers as reference.

If this is a custom OS, you can still refer to other open source drivers for ideas, but you clearly won't have things set up as conveniently. Preallocate all the memory in the driver code, fill it with data as it arrives, and hand it off to the application code. The amount of memory will be a function of how fast your app can process data, the largest amount of data you plan to accept, and how much internal data queuing is needed to support your app.

揪着可爱 2024-07-23 03:06:43

这是 C,我最终不得不让应用程序向驱动程序注册回调。 回调的目的是在驱动程序从设备读取数据后处理数据。 驱动程序管理内存,即分配内存、调用回调并最终释放内存。 此外,回调仅具有内存的读取权限。 因此,理想情况下,应用程序应该将缓冲区的内容复制到自己的内存中,并立即退出回调。 然后,它就可以按照自己的意愿随时随地处理数据。

我更新了文档,以明确应用程序回调的使用,即假定回调返回时,内存不应再被视为有效。 如果以任何其他方式使用回调,则行为是未定义的。

This being C, I have ended up having to make the app register a callback with the driver. The purpose of the callback is to process the data after the driver reads it from the device. The driver manages the memory i.e. allocates memory, invokes the callback and finally frees memory. Additionally, the callback only has read permission on the memory. Therefore, the app should ideally just copy the contents of the buffer to its own memory and exit from the callback right away. Then it is free to process the data when and how it wishes.

I updated the documentation to make it clear to uses of the app callback that it is assumed when the callback returns, the memory should no longer be considered valid. If the callback is used any other way, the behavior is undefined.

淡墨 2024-07-23 03:06:43

我的第一个想法是使用循环缓冲区。 这是一些示例代码。 请随意调整它以适应您自己的用途。 您可能不想要全局变量。 而且您可能不想要#defines:

#define LENGTH (1024)
#define MASK (LENGTH-1)
uint8 circularBuffer[ LENGTH ];
int circularBuffer_add = 0;
int circularBuffer_rmv = 0;

void copyIn( uint8 * circularBuffer, uint8 * inputBuffer, int n ) {
    int i;
    for( i = 0; i < n; i++ ) {
        circularBuffer[ circularBuffer_add ] = inputBuffer[ i ];
        circularBuffer_add = ( circularBuffer_add + 1 ) & MASK;
    } 
}

void copyOut( uint8 * circularBuffer, uint8 * outputBuffer, int n ) {
    int i;
    for( i = 0; i < n; i++ ) {
        outputBuffer[ i ] = circularBuffer[ circularBuffer_rmv ];
        circularBuffer_rmv = ( circularBuffer_rmv + 1 ) & MASK;
    } 
}

此外,上面的代码假设您的数据单位是数据类型“uint8”。 您可以更改它,以便它使用其他数据类型。 或者您甚至可以使其通用并使用 memcpy() 复制到循环缓冲区中。

这段代码的主要特点是它如何处理 add 和 rmv ptr。


一旦你使用上面的代码就可以工作了。 我建议在某个时候切换从硬件读取的所有内容以使用平台的 direct-memory-访问 API

切换到直接内存访问非常重要,因为相对于 DMA,上述代码使用了很多周期,而 DMA 几乎使用了零周期。

My first thought would be to use circular buffers. Here is some example code. Feel free to adapt this to your own uses. You probably wouldn't want global variables. And you might not want #defines:

#define LENGTH (1024)
#define MASK (LENGTH-1)
uint8 circularBuffer[ LENGTH ];
int circularBuffer_add = 0;
int circularBuffer_rmv = 0;

void copyIn( uint8 * circularBuffer, uint8 * inputBuffer, int n ) {
    int i;
    for( i = 0; i < n; i++ ) {
        circularBuffer[ circularBuffer_add ] = inputBuffer[ i ];
        circularBuffer_add = ( circularBuffer_add + 1 ) & MASK;
    } 
}

void copyOut( uint8 * circularBuffer, uint8 * outputBuffer, int n ) {
    int i;
    for( i = 0; i < n; i++ ) {
        outputBuffer[ i ] = circularBuffer[ circularBuffer_rmv ];
        circularBuffer_rmv = ( circularBuffer_rmv + 1 ) & MASK;
    } 
}

Also the above code assumes that your unit of data is datatype "uint8". You can change it so that it uses some other datatype. Or you can even make it generic and use memcpy() to to copy into the circularBuffer.

The main feature of this code is how it handles the add and rmv ptr.


Once you get things working with the above code. I suggest at some point switching over all your reads from the hardware to use your platform's direct-memory-access API.

It is important to switch to direct-memory-access because the above code uses a lot of cycles relative to DMA which uses almost zero cycles.

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