我在过去看过以下技巧:
struct MyStruct
{
field 1;
field 2;
field 3;
};
void f()
{
MyStruct *example = (memory mapped peripheral address);
}
这基本上使得变量示例现在包含存储器映射外围设备的地址(值),然后字段1处于相同的偏移量,字段2处于(基地址+ sizeof(字段1))并且等等。
当外围设备中存在多个与连续内存关联的偏移时,这很有用,因为它提供了一个抽象层。
我想知道你是否可以对C ++对象做同样的事情,以及如何使这个对象成为单例,继承如何影响这种映射。
答案 0 :(得分:4)
我想知道你是否可以对C ++对象做同样的事情,
是的,同样可以做到。十多年来,我在电信系统开发中使用C ++进行内存映射。我很少处理嵌入式软件中的结构,结构没有优于类。此外,我更喜欢掩盖并转移到处理字段,我对位字段产生了一些厌恶。
对于struct和class实例,可以确定和确认数据布局和打包,因此assert()可以在运行时通知错误地构建数据。
a)C和C ++都不提供内存布局语义和
b)编译器选项可以改变结果(打包,对齐,字段重新排列)
这两项需要某种运行时检查。我们通常断言数据属性总长度,以及字段偏移开始之间的一个或多个距离,所有这些都在命名符号中。
许多编译器提供编译指示来支持内存映射io打包和对齐问题。不幸的是,不同编译器的编译器通常有不同的名称。这通常并不重要,因为硬件特定的软件几乎从不便携。我遇到的所有pragma似乎都有效。最终我们选择不使用它们。
请注意,字节序也会产生影响,但与一起使用的硬件将(或者必须)设计为具有正确的字节顺序。
我拥有使用vxWorks工具的大部分嵌入式系统经验,还有一些使用OSE。两者都在2011年之前使用GCC。
以及如何使这个对象成为单身和
我发现单身人士没有价值。但我相信单例可以用传统方式构建,因为实例的代码不存储在数据字段中。它们是分开的。当你调用你的singletonGet()方法时,它需要做数据的映射' out'到hw地址。
如何在地址0xAAAA0001'处理hw的最典型机制。是将uint64_t枚举地址强制转换为指针
class FooHW; // hardware class
FooHW* foo = reinterpret_cast<FooHW*>(AAAA0001_uint64_t); // enum addr
这会跳过所有ctor / dtor的东西。这个是是可取的。如何访问内存映射设备通常必须不在软件重启期间干扰操作hw。热启动(其中hw已经运行,但软件已经重新启动)在开发和运行期间比冷启动(也称为电源反弹)更为常见。
可以使用展示位置&#39; new&#39; (以及&#39;删除&#39;)在正确的内存地址创建实例。但这没有任何优势。你应该研究冷启动,热启动,保护开关等。与其余代码一起存在的Foo *指针是典型的。
继承如何影响这种映射。
我认为所有这些问题都是特定于工具的。我所看到的是基类数据属性预先设置为派生类数据属性。有些工具可能会反过来做。
在我的工作中,我记不起要派生的内存映射实例。但是,我认为它可以起作用,我会选择避免更多的努力。
答案 1 :(得分:1)
你根本不需要让它成为单身人士。根据您使用的技术,您总是会指向同一个地址。只要它包含正确的地址,用于到达那里的指针变量并不重要。
语法如下:
#define DEV_REG_SET (*(volatile MyStruct *)(0x4CF3217))
其中0x4CF3217
是您的特殊设备地址,您可以像
long f1 = DEV_REG_SET->field1; // to read from device
你 绝对做 需要将指针声明为指向易变的东西的指针。这将确保编译器始终读取和写入指向的内容,并且不会优化读写操作。
有关volatile
以及如何正确使用see this blog post "Nine ways to break your systems code using volatile"的精彩讨论。
请注意,我发现博客帖子声称某些编译器在应用于结构字段时可能会或可能不会正确尊重volatile
关键字。但是我还没有在网上看到任何编译器都不尊重指向struct的指针volatile
。但是你永远都不知道,所以在提交之前先看看生成的机器语言(如果你得到时髦的结果,请记得检查它。)
现在,关于继承 - 只要你远离编译器将隐藏数据添加到结构的任何东西,你就可以了。但无论如何你知道。毕竟,您已经知道必须确保理解编译器可能执行的所有填充。因此,您必须远离拥有任何虚拟方法或虚拟继承,并且需要避免访问说明符(这可能会影响字段顺序)。换句话说,坚持使用POD - 普通的旧(C)数据结构。