【问题标题】:Embedded C++11 code — do I need volatile?嵌入式 C++11 代码——我需要 volatile 吗?
【发布时间】:2015-09-28 03:11:42
【问题描述】:

带有 Cortex M3 MCU(STM32F1) 的嵌入式设备。它具有嵌入式闪存(64K)。 MCU固件可以在运行时重新编程闪存扇区;这是由闪存控制器(FMC)寄存器完成的(所以它不像 a=b 那样简单)。 FMC 获取缓冲区指针并将数据烧录到某个闪存扇区。

我想将最后一个闪存扇区用于设备配置参数。 参数存储在带有数组的打包结构中,并包含一些自定义类。

可以在运行时更改参数(复制到 RAM,使用 FMC 更改并烧回闪存)。

所以有一些问题:

  1. 参数结构的状态(按位)由 FMC 硬件更改。 C++ 编译器不知道它是否已更改。 这是否意味着我应该将所有结构成员声明为 volatile? 我认为是的。

  2. Struct 应该在编译时静态初始化(默认参数)。结构应该是 POD(TriviallyCopyable 并且具有标准布局)。请记住,那里有一些自定义类,所以我记住这些类也应该是 POD。 但是有一些问题: cppreference.com

    唯一可平凡复制的类型是标量类型,可平凡复制 类和此类类型/类的数组(可能是 const 限定的, 但不是 volatile 限定的)。

这意味着我不能同时保持我的类 POD 和 volatile? 那么我该如何解决这个问题呢?

可以在参数结构中只使用标量类型,但这可能会导致配置处理的代码更不干净...

附言 即使没有 volatile,它也可以工作,但恐怕有一天,一些智能 LTO 编译器会看到静态初始化,而不是更改(通过 C++)结构并优化对底层内存地址的一些访问。这意味着不会应用新的编程参数,因为它们已被编译器内联。

编辑: 不使用 volatile 也可以解决问题。而且似乎更正确。

您需要在单独的翻译单元(.cpp 文件)中定义配置结构变量,并且不要初始化变量以避免在 LTO 期间替换值。如果不使用 LTO - 一切都可以,因为一次在一个翻译单元中进行优化,因此不应优化具有静态存储持续时间和在专用翻译单元中定义的外部链接的变量。只有 LTO 可以在不发出内存提取的情况下将其丢弃或进行值替换。特别是在将变量定义为 const 时。如果不使用 LTO,我认为初始化变量是可以的。

【问题讨论】:

  • 但是,易失性内存不是普通的旧数据。它是读取和写入是可观察行为的数据。在普通的旧数据中,跳过或读取或写入结构成员之间的“填充”没有(定义的)副作用。在易失性内存中,这样的读/写可能不是?为什么需要您的数据成为 POD?
  • 为什么不将闪存中的数据 memcpy 到您的 POD 中?
  • >为什么不将闪存中的数据 memcpy 到您的 POD 中?我没有足够的 RAM 在运行时这样做。我希望在程序中某处需要配置数据时直接从闪存中读取它们。当我有 RAM 并且可以重新刷新配置时,我有特殊的配置模式。我认为配置结构应该是 POD,因为这样我就可以在配置模式下使用 memcpy。我也可以静态初始化结构,这非常重要,因为动态初始化不能应用于闪存。可能是我没有清楚地理解易失性的含义。基本上我害怕优化闪存读数,就像我在 P.S 中所说的那样
  • 请记住,必须告诉闪存控制器对闪存进行编程。 FMC 不会自行对闪存进行编程。
  • 对闪存的编程只是在操作 FMC 硬件寄存器(从 C++ 编译器的角度来看)。 FMC 寄存器修改不能被视为配置结构修改。 IE。 FMC 工作并不意味着配置结构的“可观察行为”。这就是我要说的。

标签: c++ c++11 arm embedded stm32


【解决方案1】:

通过重新编程闪存,您正在更改底层对象的表示。 volatile 限定符是适用于 以确保数据的变化不会被优化掉。

您希望声明为:const volatile Settings settings;

缺点是volatile 会阻止对象的静态初始化。这会阻止您使用链接器将已初始化的对象放在其适当的内存地址中。

您希望定义为:const Settings settings = { ... };

幸运的是,您可以初始化一个const 对象并将其作为const volatile 访问。


// Header file
struct Settings { ... };
extern const volatile Settings& settings;

// Source file
static const Settings init_settings = { ... };
const volatile Settings& settings = init_settings;

init_settings 对象是静态初始化的,但通过settings 引用进行的所有访问都被视为易失性。

但请注意,修改定义为 const 的对象是未定义的行为。

【讨论】:

  • 谢谢。 AFAIK,更改 volatile const 的底层表示似乎没问题,因为它被视为“只读”变量,每次读取访问时都会从内存中获取值。你对没有 volatile 的 Const 是正确的,我认为它甚至不需要具有底层内存表示(但它可以拥有它)。易失性变体或多或少很清楚,现在我对有关在没有易失性的情况下解决问题的信息更感兴趣,并考虑将来此解决方案可能产生的副作用。例如,我正在考虑 LTO...
【解决方案2】:

你有一些选择取决于你的编译器:

  • 您可以声明一个指向该结构的指针并初始化该指针 到该地区。
  • 告诉编译器变量应该驻留在哪里

指向 Flash 的指针

声明结构的指针。
将指针分配到 Flash 中的正确地址。
通过取消引用指针来访问变量。
该指针应该被声明和分配为指向常量数据的常量指针。

告诉编译器变量的地址。

某些编译器允许您将变量放置在特定的内存区域中。第一步是在链接器命令文件中创建一个区域。下一步是告诉编译器该变量在该区域中。

同样,变量应该声明为“静态常量”。 “静态”是因为只有 1 个实例。 “const”是因为闪存在大多数情况下都是只读的。

闪存:易失性与常数

在大多数情况下,无论如何编程,闪存都是只读的。事实上,在 Flash 中读取数据的唯一方法是锁定它,也就是将其设为只读。一般情况下,未经程序同意不会更改。

大多数闪存由软件编程。通常,这是您的程序。如果您的程序要对 Flash 重新编程,它知道值已更改。这类似于写入 RAM。 程序 改变了值,而不是硬件。因此,Flash 不是易失的。

我的经验是可以通过其他方式对 Flash 进行编程,通常是在您的程序未运行时。在这种情况下,它仍然不是易失性的,因为您的程序没有运行。 Flash 仍然是只读的。

当且仅当另一个任务或执行线程在您的执行线程处于活动状态时对闪存进行编程时,闪存将是易失的。我仍然不会将这种情况视为volatile。这将是一种同步情况——如果闪存被修改,那么应该通知一些听众。

总结

闪存最好被视为只读存储器。驻留在 Flash 中的变量通过指针访问以获得最佳可移植性,尽管一些编译器和链接器允许您在特定的硬编码地址声明变量。变量应声明为const static,以便编译器可以发出代码以直接访问变量,而不是在堆栈上复制。如果 Flash 由另一个任务或执行线程编程,这是一个同步问题,而不是 volatile。在极少数情况下,Flash 在程序执行时由外部源编程。

您的程序应提供校验和或其他方法来确定自上次检查后内容是否已更改。

不要让编译器从 FLASH 初始化变量。
这不是真正的便携。更好的方法是让您的初始化代码从闪存加载变量。让编译器从不同的段加载你的变量需要对编译器和链接器的内部进行大量工作;不仅仅是初始化指向 Flash 中地址的指针。

【讨论】:

  • 感谢您的精彩、完整的回答!如您所见,我害怕优化问题。抱歉,但我再次要求确定:是否有可能优化对 const 静态变量的访问?无论是通过指针还是直接访问。
  • 没有优化后顾之忧。编译器会直接从Flash中访问数据,看看编译器生成的汇编语言。唯一的优化可能是如何以最少的读取量读取最多的字节(即使用 1 个 32 位读取读取 4 个字节)。如有疑问,请查阅汇编语言列表。
  • 我看到了程序集列表,它确实是从闪存中获取数据,但我仍然担心,因为如果某些东西可能可以优化并且现在没有优化......可能明天会做吗?你能推荐一些关于优化的有用的读物​​吗?因为我认为编译器可以优化任何东西而没有副作用。因此,如果一个变量是编译时初始化的静态常量并且读取没有副作用(不是易失性) - 为什么不优化这个读取并让 0 获取而不是 1 呢?编译器可以在需要的地方替换已知值。我错了吗?
  • 如果一个内存块声明为const,编译器可以(并且可能会)假设一次读取与稍后读取没有区别。如果你有const 数据,程序甚至可以从不读取数据——也就是说,假设你有一个extern const int x = 1;,编译器可以将x 的读取视为值1,而不是存储在x命名的位置的值。
  • 你错了。编译器在编译时不知道来自闪存的值,所以它不能删除读取的值。指针只有在不使用时才会被删除。您始终可以使用常量而不是指针:my_var = ((struct My_Struct *)(0x46582000))->data_member;
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2014-05-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多