toku-sa-n
toku-sa-n

Reputation: 894

Is it safe to have a value that may be changed by processor unexpectedly?

For example, suppose I have a PageTable value and register its physical address with CR3 register.

#![no_std]

use {
    spinning_top::{const_spinlock, Spinlock},
    x86_64::structures::paging::PageTable,
};

// The physical address of `PML4` is registered with CR3 register.
static PML4: Spinlock<PageTable> = const_spinlock(PageTable::new());

The processor changes the Accessed bit of an entry of PML4 if a page is accessed. I'm afraid that the optimized code may use a cached value stored in one of the CPU registers or the stack instead of the actual value on memory, causing the use of the old value of the bit.

In this case, the impact should be small, and I don't care the Accessed bit. But, in general, is it safe to have a value that the processor may change unexpectedly (like MMIO and DMA buffer) or a reference to such a value? Or should I do a read-modify-write cycle every time through a raw pointer with read_volatile and write_volatile?

Upvotes: 9

Views: 231

Answers (1)

user2722968
user2722968

Reputation: 16535

Since you specifically asked about "a reference to such a value": If you have a &T, and T mutates for any reason while the reference is alive, the program will show undefined behavior.

As far as I know, the documentation in the Reference is not yet final on this point, yet the principle is clear. From the docs of UnsafeCell:

If you have a reference &T, then normally in Rust the compiler performs optimizations based on the knowledge that &T points to immutable data. Mutating that data, for example through an alias or by transmuting an &T into an &mut T, is considered undefined behavior.

And (edited for length):

If you create a safe reference with lifetime 'a (either a &T or &mut T reference) that is accessible by safe code, then you must not access the data in any way that contradicts that reference for the remainder of 'a. For example, this means that if you take the *mut T from an UnsafeCell and cast it to an &T, then the data in T must remain immutable until that reference’s lifetime expires.

In effect, if there is a &PML4 or &mut PML4, the "volatile" property of PML4 can be considered a mutation through a "hidden" alias.

Depending on your needs, you either need to channel access to PML4 via a read_volatile (which is safe, because read_volatile always does a copy of underlying memory into what is then immutable memory); you are then free to give out references to that copy. Or you wrap the thing into an UnsafeCell<PML4>, in which case access still needs to be done by reading the value from the inner pointer (volatile or not), but at least you can pass around a &UnsafeCell<PML4>.

Upvotes: 5

Related Questions