"volatile" 的正确使用是否仍然(总是)导致程序与该数据有未定义的交互?

Does the correct use of "volatile" still (always) result in a program with undefined interaction with that data?

正确使用volatile关键字的在线例子appear to be like so:

void Foo (volatile SomethingExternal * x, int data_update)
{
  while (x->busy);

  x->data = data_update;
}

但似乎如果x指向的数据确实是易变的,那么在退出while循环和写入data之间可能会发生上下文切换,所以如果当我们访问它时 busy 标志是 false 很重要那么这段代码是不是不安全?

这不完全正确。根据设计,有些构造在使用易失性操作实现时是正确的。来自 [this answer] 中引用的标准:

The observable behavior of the abstract machine is its sequence of reads and writes to volatile data and calls to library I/O functions.

这使我们能够保证所有易失性数据都将按要求读取和写入,而不会根据当前线程重新排序。

作为一个即使使用上下文切换也是正确的结构的示例,可以使用 Dekker's Algorithm 实现互斥锁的低级获取。该算法不需要原子比较和交换操作,但它确实需要使用符合易失性要求的内存。由于任何人(包括外部线程)都不会对一个线程的可变操作进行重新排序,因此该算法的正确性成立(证明要求操作不会重新排序)。同样,因为易失性读取总是从实际内存中读取而不是从缓存值中读取,所以当锁可用时算法可以取得进展。

这是一个向 reader 展示此算法可用于构建例如安全锁定习语的练习。

(安全)使用 volatile 变量的另一个示例是您问题中给出的代码,当在没有上下文切换的单线程处理器(例如,禁用中断的微控制器)上执行时,x 指向外部设备的内存映射。这假定代码对于设备的预期用途实际上是正确的(即,一旦 busy 被解除断言,对数据寄存器的单次写入将启动它需要的任何任务)。

易失性读取可确保您的程序在设备不再忙碌(活跃度)时取得进展,因为编译器不能简单地将循环合并为单个内存读取,如果设备忙碌则执行无限循环。

在您 link 的示例中,模型是使用 volatile 对象访问的某个设备。没有其他线程或进程与设备交互:一旦设备完成其任务并变得不忙,它就会保持不忙状态,直到您给它一个新命令。没有其他线程或进程会使它忙碌;您拥有该设备并拥有独占访问权。内存需要标记为易失性,以便编译器在 C 代码检查 x->busy 时执行实际读取,并在 C 代码写入 x->data.

时执行实际写入

您说得对,在测试 x->busy 和编写 x->data 之间可能会发生上下文切换。如果有另一个进程或线程正在访问该设备,这将是一个错误。但这不是此代码的用途。