<p><pre><code> An understanding of READ_ONCE() and WRITE_ONCE() is important for kernel developers who will be dealing with any sort of concurrent access to data. So, naturally, they are almost entirely absent from the kernel's documentation.
</code></pre>
Made me chuckle.
More chuckles from the source:<p><pre><code> /*
* Yes, this permits 64-bit accesses on 32-bit architectures. These will
* actually be atomic in some cases (namely Armv7 + LPAE), but for others we
* rely on the access being split into 2x32-bit accesses for a 32-bit quantity
* (e.g. a virtual address) and a strong prevailing wind.
*/</code></pre>
The problem is that the compiler has a too abstract view of the underlying system.<p>If reading twice would, say, launch missiles, then a read can be modeled as:<p><pre><code> some_register = do_read()
counter += 1
if counter >= 2: launch_missiles()
</code></pre>
With this substitution, a compiler would never replace a single read by two reads.
> There are a couple of interesting implications from this outcome, should it hold. The first of those is that, as Rust code reaches more deeply into the core kernel, its code for concurrent access to shared data will look significantly different from the equivalent C code, even though the code on both sides may be working with the same data. Understanding lockless data access is challenging enough when dealing with one API; developers may now have to understand two APIs, which will not make the task easier.<p>The thing is, it'll be far less challenging for the Rust code, which will actually define the ordering semantics explicitly. That's the point of rejecting the READ_ONCE/WRITE_ONCE approach - it's unclear what the goal is when using those, what guarantee you actually want.<p>I suspect that if Rust continues forward with this approach it will basically end up as the code where someone goes to read the actual semantics to determine what the C code should do.
In my experience, in practice, it usually isn't that hard to figure out what people meant by a READ/WRITE_ONCE().<p>Most common cases I see are:<p>1. I'm sharing data between concurrent contexts but they are all on the same CPU (classic is sharing a percpu variable between IRQ and task).<p>2. I'm reading some isolated piece of data that I know can change any time, but it doesn't form part of a data structure or anything, it can't be "in an inconsistent state" as long as I can avoid load-tearing (classic case: a performance knob that gets mutated via sysfs). I just wanna READ it ONCE into a local variable, so I can do two things with it and know they both operate with the same value.<p>I actually don't think C++ or Rust have existing semantics that satisfy this kinda thing? So will be interesting to see what they come up with.
> I suspect that if Rust continues forward with this approach it will basically end up as the code where someone goes to read the actual semantics to determine what the C code should do.<p>That will also put it on the unfortunate position of being the place that breaks every time somebody adds a bug to the C code.<p>Anyway, given the cultures involved, it's probably inevitable.
> That will also put it on the unfortunate position of being the place that breaks every time somebody adds a bug to the C code.<p>Can someone explain charitably what the poster is getting at? To me, the above makes zero sense. If the Rust code is what is implemented correctly, and has the well-defined semantics, then, when the C code breaks, it's obviously the C code's problem?
I think a charitable interpretation is that given that the Rust code will be less forgiving, it will "break" C code and patterns that "used to work", albeit with latent UB or other nonobvious correctness issues. Now, obviously this is ultimately a good thing, and no developer worth their salt would seriously argue that latent bugs should stay latent, but as we've already seen, people have egos and aren't always exceedingly rational.
Very interesting. AFAIK the kernel explicitly gives consume semantics to read_once (and in fact it is not just a compiler barrier on alpha), so technically lowering it to a relaxed operation is wrong.<p>Does rust have or need the equivalent of std::memory_order_consume? Famously this was deemed unimplementable in C++.
> The truth of the matter, though, is that the Rust community seems to want to take a different approach to concurrent data access.<p>Not knowing anything about development of the kernel, does this kind of thing create a two tier Linux development experience?
What is your take on their names instead of "atomic_read" and "atomic_write"?
The problem with atomic_read and atomic_write is that some people will interpret that as "atomic with a sequentially consistent ordering" and some as "atomic with a relaxed ordering" and everything in between. It's a fine name for a function that takes an argument that specifies memory ordering [1]. It's not great for anything else.<p>Read_once and Write_once convey that there's more nuance than that, and tries to convey the nuance.<p>[1] E.g. in rust anything that takes <a href="https://doc.rust-lang.org/std/sync/atomic/enum.Ordering.html" rel="nofollow">https://doc.rust-lang.org/std/sync/atomic/enum.Ordering.html</a>
I think “atomic” implies something more than just “once” because for atomic we customarily consider the memory order with that memory access, but “once” just implies reading and writing exactly once. Neither are good names because the kernel developers clearly assumed some kind of atomicity with some kind of memory ordering here but just calling it “atomic” doesn’t convey that.
Those things both exist in the kernel and they refer to CPU atomics similar to std::atomic in C++.