volatile 是声明任何引用设备寄存器的变量时必须应用的一个关键字。如果不使用 volatile,编译时优化程序可能会意外删除重要访问。省略使用 volatile 可能会生成很难跟踪的错误。
要防止出现难懂的错误,必须正确使用 volatile。volatile 关键字指示编译器对已声明的对象使用精确语义,特别指示不能删除或重新排序对对象的访问。设备驱动程序必须使用 volatile 限定符的两个实例为:
当数据引用外部硬件设备寄存器(即除了存储功能之外还具有负面影响的内存)时。但请注意,如果使用 DDI 数据访问函数访问设备寄存器,则无需使用 volatile。
当数据引用的全局内存可由多个线程访问、不受锁定保护并且依赖于内存访问的序列时。与使用锁定相比,使用 volatile 使用的资源较少。
以下示例使用 volatile。忙标志用于防止线程在设备忙时继续执行,该标志不受锁定保护:
while (busy) { /* do something else */ }
测试线程将在另一个线程关闭 busy 标志时继续执行:
busy = 0;
由于 busy 会在测试线程中被频繁地访问,因此编译器可能通过将 busy 的值放在寄存器中来优化测试,并测试寄存器的内容,而无需在每次测试前都读取内存中的 busy 值。测试线程将永远无法看到 busy 的更改,其他线程将只更改内存中的 busy 值,从而导致死锁。将 busy 标志声明为 volatile 会强制在每次测试前读取其值。
busy 标志的一种替代方法是使用条件变量。请参见线程同步中的条件变量。
使用 volatile 限定符时,请避免意外省略的风险。例如,以下代码
struct device_reg { volatile uint8_t csr; volatile uint8_t data; }; struct device_reg *regp;
比下一个示例更可取:
struct device_reg { uint8_t csr; uint8_t data; }; volatile struct device_reg *regp;
尽管这两个示例在功能上等效,但第二个示例要求编写人员确保类型 struct device_reg 的每个声明中都使用 volatile。第一个示例将导致所有声明中都将数据视为可变数据,因此首选该示例。如上所述,如果使用 DDI 数据访问函数访问设备寄存器,就不必将变量限定为 volatile 了。