是否“易失"?保证多核系统的可移植C代码中没有任何内容? [英] Does "volatile" guarantee anything at all in portable C code for multi-core systems?

查看:79
本文介绍了是否“易失"?保证多核系统的可移植C代码中没有任何内容?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

查看 其他 问题 他们的 <一个href ="https://stackoverflow.com/a/2485733/706054">答案,我的印象是,对于C中的"volatile"关键字的确切含义尚未达成广泛共识.

After looking at a bunch of other questions and their answers, I get the impression that there is no widespread agreement on what the "volatile" keyword in C means exactly.

即使标准本身似乎还不够清晰,每个人都无法同意这是什么意思.

Even the standard itself does not seem to be clear enough for everyone to agree on what it means.

其他问题:

  1. 根据您的硬件和编译器,它似乎提供了不同的保证.
  2. 它影响编译器优化,但不影响硬件优化,因此在执行自己的运行时优化的高级处理器上,甚至不清楚编译器是否可以阻止您想要阻止的任何优化. (某些编译器确实会生成指令来阻止某些系统上的某些硬件优化,但这似乎并未以任何方式进行标准化.)
  1. It seems to provide different guarantees depending on your hardware and depending on your compiler.
  2. It affects compiler optimizations but not hardware optimizations, so on an advanced processor that does its own run-time optimizations, it is not even clear whether the compiler can prevent whatever optimization you want to prevent. (Some compilers do generate instructions to prevent some hardware optimizations on some systems, but this does not appear to be standardized in any way.)

总而言之,似乎(经过大量阅读)"volatile"保证了类似的内容:该值不仅会从寄存器中读取/写入到寄存器中,还会至少写入内核的L1缓存中. ,其顺序与读/写在代码中的顺序相同.但这似乎没有用,因为在同一线程内从寄存器读/写到寄存器已经足够,而与L1缓存协调并不能保证关于与其他线程的协调的任何其他信息.我无法想象仅与L1缓存进行同步的重要性.

To summarize the problem, it appears (after reading a lot) that "volatile" guarantees something like: The value will be read/written not just from/to a register, but at least to the core's L1 cache, in the same order that the reads/writes appear in the code. But this seems useless, since reading/writing from/to a register is already sufficient within the same thread, while coordinating with L1 cache doesn't guarantee anything further regarding coordination with other threads. I can't imagine when it could ever be important to sync just with L1 cache.

使用1
volatile唯一被广泛认可的用法似乎是用于旧的或嵌入式系统,其中某些内存位置通过硬件映射到I/O功能,例如内存中的某个位(直接(在硬件中)控制灯光),或者内存中的一小段告诉您键盘按键是否按下(因为它是通过硬件直接连接到按键的).

USE 1
The only widely-agreed-upon use of volatile seems to be for old or embedded systems where certain memory locations are hardware-mapped to I/O functions, like a bit in memory that controls (directly, in the hardware) a light, or a bit in memory that tells you whether a keyboard key is down or not (because it is connected by the hardware directly to the key).

在目标包括多核系统的可移植代码中,似乎不会出现"use 1".

It seems that "use 1" does not occur in portable code whose targets include multi-core systems.

使用2
可以通过中断处理程序(可以控制灯光或存储来自按键的信息)随时读取或写入的内存与使用1"没有太大区别.但是已经为此而存在的问题是,取决于系统,中断处理程序可能会在上运行 其自己的内存缓存的不同内核 ,"volatile"并不能保证所有系统上的缓存一致性.

USE 2
Not too different from "use 1" is memory that could be read or written at any time by an interrupt handler (which might control a light or store info from a key). But already for this we have the problem that depending on the system, the interrupt handler might run on a different core with its own memory cache, and "volatile" does not guarantee cache coherency on all systems.

因此,使用2"似乎超出了易失性"所能提供的范围.

使用3
我看到的唯一其他无可争议的用途是防止通过指向编译器未意识到的相同内存的不同变量的不同变量对访问进行错误优化.但这可能只是无可争辩的,因为人们没有在谈论它-我只看到其中一个提及.而且我认为C标准已经认识到不同"的指针(例如指向函数的不同args)可能指向同一项目或附近的项目,并且已经指定编译器必须生成即使在这种情况下也可以工作的代码.但是,我无法在最新的标准(500页!)中快速找到该主题.

USE 3
The only other undisputed use I see is to prevent mis-optimization of accesses via different variables pointing to the same memory that the compiler doesn't realize is the same memory. But this is probably only undisputed because people aren't talking about it -- I only saw one mention of it. And I thought the C standard already recognized that "different" pointers (like different args to a function) might point to the same item or nearby items, and already specified that the compiler must produce code that works even in such cases. However, I couldn't quickly find this topic in the latest (500 page!) standard.

所以使用3个"可能根本不存在?

我的问题:

易失性"是否可以保证多核系统的可移植C代码中的任何内容?

浏览 2.该标准还为setjmp/longjmp指定了"volatile"的明确含义. (重要的示例代码在其他问题

After browsing the latest standard, it is looking like the answer is at least a very limited yes:
1. The standard repeatedly specifies special treatment for the specific type "volatile sig_atomic_t". However the standard also says that use of the signal function in a multi-threaded program results in undefined behavior. So this use case seems limited to communication between a single-threaded program and its signal handler.
2. The standard also specifies a clear meaning for "volatile" in relation to setjmp/longjmp. (Example code where it matters is given in other questions and answers.)

因此,更精确的问题变为:
除(1)允许单线程程序从其信号处理程序接收信息,或(2)允许setjmp代码查看变量外,"volatile"是否完全保证了多核系统的便携式C代码中的任何内容在setjmp和longjmp之间修改?

So the more precise question becomes:
Does "volatile" guarantee anything at all in portable C code for multi-core systems, apart from (1) allowing a single-threaded program to receive information from its signal handler, or (2) allowing setjmp code to see variables modified between setjmp and longjmp?

这仍然是一个是/否问题.

如果为"yes",那么可以显示无错误的可移植代码示例,如果省略了"volatile",则该示例会出现错误,这将是很好的选择.如果为"no",那么我认为对于多核目标,在这两种非常特殊的情况下,编译器可以随意忽略"volatile".

推荐答案

总结问题,似乎(大量阅读后) "volatile"保证类似以下内容:该值将被读/写 不仅来自/指向寄存器,而且至少指向内核的L1缓存, 读/写在代码中出现的顺序相同..

To summarize the problem, it appears (after reading a lot) that "volatile" guarantees something like: The value will be read/written not just from/to a register, but at least to the core's L1 cache, in the same order that the reads/writes appear in the code.

不,绝对不是.这使得volatile对于MT安全代码几乎毫无用处.

No, it absolutely does not. And that makes volatile almost useless for the purpose of MT safe code.

如果这样做的话,那么volatile对于多线程共享的变量将非常有用,因为排序L1缓存中的事件是您在典型CPU(主板上的多核或多CPU)中所需要做的全部工作能够以通常的预期成本(即,对于大多数原子或不满足互斥锁操作而言不是很大的成本)正常进行C/C ++或Java多线程实现的方式进行协作.

If it did, then volatile would be quite good for variables shared by multiple thread as ordering the events in the L1 cache is all you need to do in typical CPU (that is either multi-core or multi-CPU on motherboard) capable of cooperating in a way that makes a normal implementation of either C/C++ or Java multithreading possible with typical expected costs (that is, not a huge cost on most atomic or non-contented mutex operations).

但是,无论从理论上还是在实践上,volatile都不会在缓存中提供任何保证的排序(或内存可见性").

But volatile does not provide any guaranteed ordering (or "memory visibility") in the cache either in theory or in practice.

(注:以下内容基于对标准文档的合理解释,标准的意图,历史惯例以及对编译器作者期望的深刻理解.此方法基于历史,实际实践以及对以下内容的期望和理解:真实世界中的真实人,这比解析不知名的,规范化的文档的单词要强得多,也更可靠.

(Note: the following is based on sound interpretation of the standard documents, the standard's intent, historical practice, and a deep understand of the expectations of compiler writers. This approach based on history, actual practices, and expectations and understanding of real persons in the real world, which is much stronger and more reliable than parsing the words of a document that is not known to be stellar specification writing and which has been revised many times.)

在实践中, volatile确实保证了ptrace-ability,即在任何优化级别上都可以为运行的程序使用调试信息的功能,并且事实上调试信息对于这些volatile对象是有意义的:

In practice, volatile does guarantees ptrace-ability that is the ability to use debug information for the running program, at any level of optimization, and the fact the debug information makes sense for these volatile objects:

  • 您可以使用ptrace(一种类似ptrace的机制)在涉及易失性对象的操作之后的序列点处设置有意义的断点:您确实可以在这些点处断点(请注意,只有在您愿意设置许多断点,因为任何C/C ++语句都可以编译为许多不同的程序集起点和终点,例如在大规模展开的循环中);
  • 当一个执行线程停止运行时,您可以读取所有易失性对象的值,因为它们具有规范的表示形式(遵循各自类型的ABI);非易失性局部变量可以具有非典型表示,例如.移位表示:用于索引数组的变量可能会乘以单个对象的大小,以便于索引编制;或者可以用指向数组元素的指针代替它(只要对变量的所有使用都进行了相似的转换)(可以将dx转换为in的整数);
  • 您还可以修改这些对象(只要内存映射允许,因为具有const限定的具有静态生存期的易失对象可能位于只读映射的内存范围内).
  • you may use ptrace (a ptrace-like mechanism) to set meaningful break points at the sequence points after operations involving volatile objects: you can really break at exactly these points (note that this works only if you are willing to set many break points as any C/C++ statement may be compiled to many different assembly start and end points, as in a massively unrolled loop);
  • while a thread of execution of stopped, you may read the value of all volatile objects, as they have their canonical representation (following the ABI for their respective type); a non volatile local variable could have an atypical representation, f.ex. a shifted representation: a variable used for indexing an array might be multiplied by the size of individual objects, for easier indexing; or it might be replaced by a pointer to an array element (as long as all uses of the variable as similarly converted) (think changing dx to du in an integral);
  • you can also modify those objects (as long as the memory mappings allow that, as volatile object with static lifetime that are const qualified might be in a memory range mapped read only).

实际上,可变性保证比严格的ptrace解释要多一点:它还保证可变性自动变量在堆栈上有一个地址,因为它们没有分配给寄存器,而寄存器分配会使ptrace的操作更加精细(编译器可以输出调试信息来说明如何将变量分配给寄存器,但是读取和更改寄存器状态比访问内存地址要复杂得多.)

Volatile guarantee in practice a little more than the strict ptrace interpretation: it also guarantees that volatile automatic variables have an address on the stack, as they aren't allocated to a register, a register allocation which would make ptrace manipulations more delicate (compiler can output debug information to explain how variables are allocated to registers, but reading and changing register state is slightly more involved than accessing memory addresses).

请注意,完整的程序调试能力(即至少在顺序点处考虑所有变量的可变性)是由编译器的零优化"模式提供的,该模式仍会执行简单的优化,如算术简化(通常存在不保证在所有模式下均不进行优化).但是volatile比非优化要强:x-x可以简化为非易失性整数x,而不是易失对象.

Note that full program debug-ability, that is considering all variables volatile at least at sequence points, is provided by the "zero optimization" mode of the compiler, a mode which still performs trivial optimizations like arithmetic simplifications (there is usually no guaranteed no optimization at all mode). But volatile is stronger than non optimization: x-x can be simplified for a non volatile integer x but not of a volatile object.

因此, volatile意味着可以保证按原样编译,就像系统调用的编译器从源代码到二进制文件/程序集的转换一样,并不是通过任何方式重新解释,更改或优化编译器.请注意,库调用可能不是系统调用.许多正式的系统功能实际上是库函数,提供了一个薄薄的中介层,并且通常在最后才遵从内核. (特别是getpid无需进入内核,并且可以很好地读取包含信息的操作系统提供的内存位置.)

So volatile means guaranteed to be compiled as is, like the translation from source to binary/assembly by the compiler of a system call isn't a reinterpretation, changed, or optimized in any way by a compiler. Note that library calls may or may not be system calls. Many official system functions are actually library function that offer a thin layer of interposition and generally defer to the kernel at the end. (In particular getpid doesn't need to go to the kernel and could well read a memory location provided by the OS containing the information.)

易失性交互是与真实机器外部世界的交互,必须遵循抽象机器".它们不是程序部分与其他程序部分的内部交互.编译器只能推理知道的内容,即内部程序部分.

Volatile interactions are interactions with the outside world of the real machine, which must follow the "abstract machine". They aren't internal interactions of program parts with other program parts. The compiler can only reason about what it knows, that is the internal program parts.

用于易失性访问的代码生成应该遵循与该内存位置的最自然的交互:这应该不足为奇.这意味着某些易失性访问应该是原子性的:如果在架构上读取或写入long表示形式的自然方式是原子性的,则可以预期对a的读取或写入volatile long将是原子的,例如,因为编译器不应生成笨拙的低效率代码来逐字节访问易失性对象.

The code generation for a volatile access should follow the most natural interaction with that memory location: it should be unsurprising. That means that some volatile accesses are expected to be atomic: if the natural way to read or write the representation of a long on the architecture is atomic, then it's expected that a read or write of a volatile long will be atomic, as the compiler should not generate silly inefficient code to access volatile objects byte by byte, for example.

您应该能够通过了解体系结构来确定这一点.您不需要了解任何有关编译器的信息,因为 volatile意味着编译器应该是透明的.

You should be able to determine that by knowing the architecture. You don't have to know anything about the compiler, as volatile means that the compiler should be transparent.

但是volatile只不过是强迫针对特定情况进行最优化的最不期望的程序集发出存储操作:volatile语义意味着一般情况下的语义.

But volatile does no more than force the emission of expected assembly for the least optimized for particular cases to do a memory operation: volatile semantics means general case semantic.

一般情况是编译器在没有任何有关构造的信息时执行的操作:f.ex.通过动态分派在左值上调用虚拟函数是一般情况,在编译时确定表达式指定的对象类型是特殊情况后,直接调用重写器.编译器始终对所有构造都具有一般情况下的处理,并且遵循ABI.

The general case is what the compiler does when it doesn't have any information about a construct: f.ex. calling a virtual function on an lvalue via dynamic dispatch is a general case, making a direct call to the overrider after determining at compile time the type of the object designated by the expression is a particular case. The compiler always have a general case handling of all constructs, and it follows the ABI.

Volatile对同步线程或提供内存可见性"并没有什么特殊要求: volatile仅提供从执行或停止的线程内部看到的抽象级别的保证,即 CPU核心:

Volatile does nothing special to synchronize threads or provide "memory visibility": volatile only provides guarantees at the abstract level seen from inside a thread executing or stopped, that is the inside of a CPU core:

  • volatile没有说什么内存操作到达主RAM(您可以使用汇编指令或系统调用来设置特定的内存缓存类型,以获得这些保证);
  • volatile无法保证何时将内存操作提交到任何级别的缓存(甚至不包括L1)..

仅第二点表示volatile在大多数线程间通信问题中没有用;在没有涉及与CPU外部但仍在内存总线上的硬件组件进行通信的任何编程问题中,第一点基本上都是无关紧要的.

Only the second point means volatile is not useful in most inter threads communication problems; the first point is essentially irrelevant in any programming problem that doesn't involve communication with hardware components outside the CPU(s) but still on the memory bus.

从运行线程的核心的角度来看,volatile的特性提供了有保证的行为,这意味着从该线程的执行顺序的角度来看,传递给该线程的异步信号是运行的,请参见源代码顺序.

The property of volatile providing guaranteed behavior from the point of the view of the core running the thread means that asynchronous signals delivered to that thread, which are run from the point of view of the execution ordering of that thread, see operations in source code order.

除非您计划向线程发送信号(一种非常有用的方法,用于合并有关当前正在运行的线程的信息,并且没有事先约定的停止点),否则volatile并不适合您.

Unless you plan to send signals to your threads (an extremely useful approach to consolidation of information about currently running threads with no previously agreed point of stopping), volatile is not for you.

这篇关于是否“易失"?保证多核系统的可移植C代码中没有任何内容?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆