On Thu, Aug 07, 2025 at 03:38:52PM +0200, Paolo Bonzini wrote: > Date: Thu, 7 Aug 2025 15:38:52 +0200 > From: Paolo Bonzini <pbonz...@redhat.com> > Subject: Re: [RFC 13/26] rust: Add RCU bindings > > On 8/7/25 14:29, Manos Pitsidianakis wrote: > > > > +//! Bindings for `rcu_read_lock` and `rcu_read_unlock`. > > > +//! More details about RCU in QEMU, please refer docs/devel/rcu.rst. > > > + > > > > How about a RAII guard type? e.g. RCUGuard and runs `rcu_read_unlock` on > > Drop. > > Clippy says Rcu not RCU. :) > > You're right, not just because it's nice but also because it bounds the > dereference of the FlatView. Something like this build on top of the guard > object: > > pub struct RcuCell<T> { > data: AtomicPtr<T> > } > > impl<T> RcuCell { > pub fn raw_get(&self) -> *mut T { > self.data.load(Ordering::Acquire) > } > > pub fn get<'g>(&self, _: &'g RcuGuard) -> Option<&'g T> { > unsafe { > self.raw_get().as_ref() > } > } > }
I just implement a simple RcuGuard (but this doesn't consider the refer count or flag. I would like to talk more about this at the last of this reply.): pub struct RcuGuard; impl RcuGuard { pub fn new() -> Self { unsafe { bindings::rcu_read_lock() }; Self } } impl Drop for RcuGuard { fn drop(&mut self) { unsafe { bindings::rcu_read_unlock() }; } } > Using this is a bit ugly, because you need transmute, but it's isolated: > > impl AddressSpace { > pub fn get_flatview(&self, rcu: &'g Guard) -> &'g FlatView { > let flatp = unsafe { > std::mem::transmute::<&*mut FlatView, &RcuCell<FlatView>>( > &self.0.as_ptr().current_map) > }; > flatp.get(rcu) > } > } > > impl GuestAddressSpace for AddressSpace { > fn memory(&self) -> Self::T { > let rcu = RcuGuard::guard(); > FlatViewRefGuard::new(self.get_flatview(rcu)) > } > } Why not use a constructor RcuCell::new() to replace transmute()? Then we just need to play with the pointer without touching memory. impl<T> RcuCell<T> { pub fn new(p: *mut T) -> Self { Self { data: AtomicPtr::new(p), } } } Then we could : impl Deref for AddressSpace { type Target = bindings::AddressSpace; fn deref(&self) -> &Self::Target { unsafe { &*self.0.as_ptr() } } } impl AddressSpace { pub fn get_flatview<'g>(&self, rcu: &'g RcuGuard) -> &'g FlatView { let flatp = RcuCell::new(self.deref().current_map); unsafe { FlatView::from_raw(flatp.get(rcu).unwrap()) } } } impl GuestAddressSpace for AddressSpace { fn memory(&self) -> Self::T { let rcu = RcuGuard::new(); FlatViewRefGuard::new(self.get_flatview(&rcu)).unwrap() } } > > Destructors are not guaranteed to run or run only once, but the former > > should happen when things go wrong e.g. crashes/aborts. You can add a > > flag in the RCUGuard to make sure Drop runs unlock only once (since it > > takes &mut and not ownership) > > Yeah I think many things would go wrong if Arc could run its drop > implementation more than once. wait, isn't RCU be held at thread-local case? We shouldn't share RCU guard/cell at Arc<>. Furthermore, it seems necessary to introduce `NotThreadSafe` into QEMU from kernel. pub type NotThreadSafe = PhantomData<*mut ()>; Then we could have stronger restrictions on RCU stuff, just like kernel'rcu: pub struct RcuGuard(NotThreadSafe); Maybe we can also add `NotThreadSafe` to RcuCell. But the lifetime has already restrict its use. For another consideration about "guaranteeing to run" (for crashes/aborts case), QEMU program will stop and OS will clean every thing up. Then we don't need to care about the state of RCU, right? Thanks, Zhao