We don’t allow questions seeking recommendations for software libraries, tutorials, tools, books, or other off-site resources. You can edit the question so it can be answered with facts and citations.
Closed 5 years ago.
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
接受
或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
发布评论
评论(6)
我们的情况类似。为了解决这些问题,我们实现了一个构建系统,该系统支持所需接口的多种实现(所使用的实现是编译目标的函数),并避免使用可移植包装器中未包含的 API 功能。包装器定义位于 .h 文件中,该文件是特定于实现的头文件。以下模型演示了我们如何处理信号量接口:
公共 API 已记录使用情况,并且实现在编译时是隐藏的。使用这些包装器也不应该产生任何开销(尽管可能会产生任何开销,具体取决于您的编译器)。不过,其中涉及很多纯粹的机械打字。
We're in a similar situation. To address these concerns, we've implemented a build system that supports multiple implementations of desired interfaces (which implementation used is a function of the compilation target), and avoid use of API features that aren't included in the portable wrappers. The wrapper definition lives in a .h file that
#include
's the implementation-specific header file. The following mock-up demonstrates how we might handle a semaphore interface:The public API is documented for use, and the implementation is hidden until compilation time. Using these wrappers also should not impose any overhead (though it might, depending on your compiler). There is a lot of purely mechanical typing involved, though.
您可能想查看 xDAIS 算法标准。它是为 DSP 应用而设计的,但其思想也可以调整为低资源嵌入式设计。
http://en.wikipedia.org/wiki/XDAIS_algorithms
简而言之:xDAIS 是一个OOP 风格的接口约定与 C 语言的 COM 不同。您有一组固定的接口,模块可以通过函数指针结构来实现这些接口。
接口被严格定义,因此很容易交换组件、将它们堆叠在一起以构建更高级别的功能等等。接口(和代码检查器)还确保所有组件保持分离。如果使用代码检查器,则不可能编写直接调用其他组件私有函数的组件。
内存分配通常在初始化时完成,并在系统设计者的控制下完成(它是所有组件必须实现的主接口的一部分)。
静态和动态分配策略都是可能的。您甚至可以动态化,而无需担心内存碎片的风险,因为所有组件都必须能够将自身重新定位到不同的内存地址。
xDAIS 标准定义了一种非常精简的 OOP 风格的继承机制。这对于调试和记录目的非常方便。有一个可以做有趣事情的算法吗?只需围绕现有算法添加一个简单的单文件包装器,并记录对 UART 等的所有调用。由于严格定义的接口,因此无需猜测模块如何工作以及如何传递参数。
我过去使用过 xDAIS,效果很好。需要一段时间来适应它,但即插即用架构和易于调试的好处超过了最初的努力。
You may want to have a look at the xDAIS algorithm standard. It was designed for DSP applications but the ideas can be adjusted to low resource embedded designs as well.
http://en.wikipedia.org/wiki/XDAIS_algorithms
In a nutshell: xDAIS is an OOP style interface convention not unlike COM for the C language. You have a fixed set of interfaces that a module may implement via a structure of function pointers.
The interfaces are strictly defined, so it is very easy to exchange components, stack them together to build higher level functionality and so on. The interfaces (and a code-checker) also make sure that all components remain separated. If the code-checker is used it is impossible to write a component that directly calls other components private functions.
Memory allocation is usually done at initialization time and under the control of the system designer (it's part of the main interface that all components must implement).
Static and dynamic allocation strategies are possible. You can even go dynamic without the risk of memory fragmentation because all components must be able to relocate themselves to different memory addresses.
The xDAIS standard defines a very lean OOP style mechanism for inheritance. This comes very handy for debugging and logging purposes. Having an algorithm that does funny things? Just add a simple single-file wrapper around an existing algorithm and log all calls to an UART or so. Due to the strictly defined interface there is no guesswork how a module works and how parameters are passed.
I've used xDAIS it in the past, and it works well. It takes a while to get used to it, but the benefits of the plug-and-play architecture and ease of debugging outweigh the initial effort.
我将尝试在这里开始回答。如果还有什么想法,我会回到这里,因为这个问题很有趣。我也会关注这个问题的其他答案。
逻辑和执行分离:
嵌入式系统可以受益于与大型业务应用程序相同的逻辑和 I/O 分离。
例如,如果您正在为某些读取值、解释这些值并根据这些读数更改某些内容的嵌入式设备进行编码,
您可能希望将“逻辑”部分与实际与网络、硬件、用户或任何外部实体通信的部分完全分开。
当您可以用某种内存结构或 C 代码完整地描述“规则”,而无需链接到除消息传递例程或类似内容之外的任何内容时,您就拥有了我试图描述的内容。简而言之,减少副作用使代码更加模块化。
我不知道您是否使用线程,但无论如何原型线程< /a> 提供了类似的抽象,功能不如线程强大,但也不太可能让程序员感到困惑。
我是在 Amiga 上长大的,很难忘记它。一个支持 ROM 的操作系统,但可以通过可加载库在 RAM 中轻松扩展。大量使用指针传递< /a> 专为紧凑的代码和快速的消息而设计。
阅读 Nils Pipenbrinck 的另一个答案,他建议使用 xDAIS 看起来不错(但还很远)仅从)实现这一点的方式。如果您的大部分代码都使用像这样的消息约定,那么您的代码很可能是模块化且可维护的。
我还会在编译目标之前在代码上运行预处理器或预编译器,但随后我们陷入灰色区域......这几乎就像切换语言,并且要求是 C。
I will try to begin an answer here. If anything else comes to mind, I will get back here, because this problem is interesting. I will also monitor this question for other answers.
Separate logic and execution:
Embedded systems can benefit from the same kind of separation of logic and I/O as large business applications.
If, for example you are coding for some embedded device that reads values, interprets these and alter something based on these readings,
you might want to separate the "logic" part completely from the part where you actually communicate with the network, the hardware, the user or whatever external entity.
When you can describe the "rules" completely in some kind of memory structure or C code, without linking to anything but the message passing routines or similar, you have what I try to describe. In short, reducing side effects makes the code more modular.
I don't know if you are using threads or not, but either way proto threads offers a similar abstraction, less powerful than threads, but also a lot less likely to confuse the programmer.
Growing up on the Amiga, I have a hard time forgetting it. A ROM-able operating system, yet easily extended in RAM by loadable libraries. Heavy use of pointer passing made for both tight code and fast messages.
Reading another answer from Nils Pipenbrinck, his suggestion to use xDAIS looks to be a good (but far from only) way of implementing this. If most of your code is using some message convention like this, chances are your code is modular and maintainable.
I would also bring up running preprocessor or precompiler passes on the code before compiling for the target, but then we drift into a gray area ... this is almost like switching languages, and the requirement was C.
你提到的模仿 OOP 是一个很好的实践。除了了解标准之外,我还可以让您了解我是如何做到这一点的。我实际上正在使用它来处理一些细节:
@my_module.c
@my_module.h
@my_application.c
如果您有建议或问题,请告诉我,因为它们也可以帮助我了解更多信息!
What you mention about imitating OOP is a good practice. More than knowing a standard I can give you an idea on how I do it. I am actually using it taking care of some details:
@my_module.c
@my_module.h
@my_application.c
Let me know if you have either suggestions or questions as they also help me learn more!
我们没有使用很多小型设备,但确实有一些内存有限。我们分配静态缓冲区,但我们发现有时动态内存分配实际上有助于减少内存使用。我们严格控制堆大小和分配策略,并且必须检查和处理内存不足的情况,而不是作为错误,而是作为正常操作。例如,我们内存不足,因此我们发送现有的数据,清除缓冲区并从上次离开的位置恢复操作。
为什么我们不转向 C++?我很乐意。我们不进行转换的主要原因如下:
We are not using many small devices, but we do have some with memory constraints. We are allocating static buffers, but we found that sometimes dynamic memory allocation actually helps reducing the memory usage. We tightly control the heap size and allocation policy and have to check and handle out of memory conditions not as errors but as normal operation. E.g. we are out of memory, so we send out the data that we have, clear the buffers and resume operations where we left of.
Why do we not switch to C++? I would love to. We don't switch for mainly these reasons:
您最好非常确定固定布局就是您想要的!拆掉它并放入一个动态的可能会变得非常棘手!
我建议任何嵌入式框架都试图解决的问题是:
计算数据偏移量
应该可以为所有内存创建单个结构,但这个*so*不会感觉这不是正确的方法。 C 编译器通常不会被要求处理多兆字节的结构,而且我感觉这样做在编译器之间不太可移植。
如果不使用结构,则需要基于数据模式本质上的五组定义:
这些定义有一个树状的依赖树,在原始 C 中很快就会变得非常复杂,因为类型通常必须打包/对齐,例如以 4 字节块的形式打包/对齐以获得最佳性能。完全扩展的定义很快就会变得比某些编译器愿意处理的更加复杂。
在原始 C 项目中管理这些问题的最简单方法是使用 C 程序计算偏移量,该程序是项目的“构建工具”可执行文件,并将它们作为 .h 文件导入到项目中,包含明确的 opoffset 数字。通过这种方法,在主编译期间应该可以使用带有基地址和相关索引的单个宏来访问数据结构的每个叶子。
避免函数指针损坏并最大限度地提高调试效率
如果函数指针存储在对象中,它们更容易受到数据损坏的影响,从而导致神秘错误。更好的方法(对于那些不时包含不同对象类型的内存范围)是在对象中存储 vtable 代码,该代码是一组函数指针集的查找索引。
vtable 可以再次由作为可执行“构建工具”的生成器 C 程序计算并生成为带有 #defines 的 .h 文件。
需要一个特殊的构造函数宏来写入适当的vtable id来初始化对象的使用。
这两个问题都已经通过 Objective-C 预处理器(它将输出原始 C)有效地得到了很好的解决,但是如果您想继续使用非常小的工具集,您可以从头开始。
为静态内存结构中的资源/任务分配内存块
如果需要支持多线程,请将短期动态任务与树结构中的特定索引相关联(与在树结构中分配对象最接近的索引)等效的过程/面向对象程序)可能最好通过尝试锁定任意索引来执行,例如使用原子增量(从零开始并进行 ==1 检查)或互斥体,然后检查该块是否可用,如果是,将其标记为已使用,然后解锁该块。
如果不需要多线程支持则没有必要;我建议编写一个自定义框架来管理此类资源分配进程,该进程可以在多线程或单线程模式下运行,以允许其余代码库不关心此主题,并允许在单线程系统上获得更快的性能。
You'd better be very sure that a fixed layout is what you want! Tearing it down and putting in a dynamic one could get very tricky!
I suggest the problems that any embedded framework are trying to manage are:
Calculating offsets to data
It should be possible to create a single struct for all memory but this *so* doesn't feel like the right way to do it. C compilers are not usually asked to work with multi-megabyte structures and I get the feeling doing this is not very portable between compilers.
If structures are not used, then five sets of defines are needed, based on what is essentially a data schema:
These defines have a tree-like dependency tree, that rapidly gets very complicated in raw C because types usually have to be packed/aligned eg in 4-byte clumps for optimal performance. The fully expanded defines can quickly end up more complex than some compilers are happy to process.
The easiest way to manage these issues in raw C projects is to calculate the offsets with a C program that is a "build tool" executable of the project and import them into the project as a .h file that contains explicit opffset numbers. With this approach a single macro taking a base address and relevant indices should be made available during the main compile for accessing each leaf of the data structure.
Avoiding function pointer corruption and maximising debugging productivity
If function pointers are stored in the object they are more vulnerable to data corruption leading to mysterious errors. The better way (for those memory ranges that contain different object types from time to time) is to store a vtable code in the object which is a lookup index into a set of function pointer sets.
The vtables can again be computed and generated as a .h file with #defines by a generator C program that is an executable "build tool".
A special constructor macro is required to write in the appropriate vtable id to initialise usage of an object.
Both of these problems are effectively well-solved already by for example the objective-C preprocessor (which will output raw C), but you can do it from scratch if you want to stay with a very small set of tools.
Allocating memory blocks to resources/tasks in the static memory structure
If you need to support multi-threading, associating short-lived dynamic tasks with particular indexes in the tree structure (nearest thing to allocating an object in the equivalent procedural/OO program) is perhaps best performed by trial-locking an arbitrary index, using for example an atomic increment (from zero with ==1 check) or mutex, then checking to see if the block is available, and if so, marking it as used, then unlocking the block.
If multi-threading support is not required then this is unnecessary; I suggest writing a custom framework for managing such resource allocation processes which can be run in either a multi-threaded or single-threaded mode, to allow the rest of the code base to be unconcerned with this topic, and to allow faster performance on single-threaded systems.