Re: [TECH TOPIC] Rust for Linux

Greg KH
2021-07-07T17:20:48Z

On Wed, Jul 07, 2021 at 06:01:41PM +0100, Wedson Almeida Filho wrote:
> On Wed, Jul 07, 2021 at 05:44:41PM +0200, Greg KH wrote:
> > On Wed, Jul 07, 2021 at 05:15:01PM +0200, Miguel Ojeda wrote:
> > > For instance, we have a `Ref` type that is similar to `Arc` but reuses
> > > the `refcount_t` from C and introduces saturation instead of aborting
> > > [3]
> > >
> > > [3] https://github.com/Rust-for-Linux/linux/blob/rust/rust/kernel/sync/arc.rs
> >
> > This is interesting code in that I think you are missing the part where
> > you still need a lock on the object to prevent one thread from grabbing
> > a reference while another one is dropping the last reference. Or am I
> > missing something?
>
> You are missing something :)
>
> > The code here:
> >
> > fn drop(&mut self) {
> > // SAFETY: By the type invariant, there is necessarily a reference to the object. We cannot
> > // touch `refcount` after it's decremented to a non-zero value because another thread/CPU
> > // may concurrently decrement it to zero and free it. It is ok to have a raw pointer to
> > // freed/invalid memory as long as it is never dereferenced.
> > let refcount = unsafe { self.ptr.as_ref() }.refcount.get();
> >
> > // INVARIANT: If the refcount reaches zero, there are no other instances of `Ref`, and
> > // this instance is being dropped, so the broken invariant is not observable.
> > // SAFETY: Also by the type invariant, we are allowed to decrement the refcount.
> > let is_zero = unsafe { rust_helper_refcount_dec_and_test(refcount) };
> > if is_zero {
> > // The count reached zero, we must free the memory.
> > //
> > // SAFETY: The pointer was initialised from the result of `Box::leak`.
> > unsafe { Box::from_raw(self.ptr.as_ptr()) };
> > }
> > }
> >
> > Has a lot of interesting comments, and maybe just because I know nothing
> > about Rust, but why on the first line of the comment is there always
> > guaranteed a reference to the object at this point in time?
>
> It's an invariant of the `Ref<T>` type: if a `Ref<T>` exists, there necessarily
> is a non-zero reference count. You cannot have a `Ref<T>` with a zero refcount.

What enforces that? Where is the lock on the "back end" for `Ref<T>`
that one CPU from grabbing a reference at the same time the "last"
reference is dropped on a different CPU?

Does Rust provide "architecture-specific" locks like this somehow that
are "built in"? If so, what happens when we need to fix those locks?
Does that get fixed in the compiler, not the kernel code?

> `drop` is called when a `Ref<T>` is about to be destroyed. Since it is about to
> be destroyed, it still exists, therefore the ref-count is necessarily non-zero.

"about to", yes, but what keeps someone else from grabbing it?

> > And yes
> > it's ok to have a pointer to memory that is not dereferenced, but is
> > that what is happening here?
>
> `refcount` is a raw pointer. When it is declared and initialised, it points to
> valid memory. The comment is saying that we should be careful with the case
> where another thread ends up freeing the object (after this thread has
> decremented its share of the count); and that we're not violating any Rust
> aliasing rules by having this raw pointer (as long as we don't dereference it).
>
> > I feel you are trying to duplicate the logic of a "struct kref" here,
>
> `struct kref` would give us the ability to specify a `release` function when
> calling `refcount_dec_and_test`, but we don't need this round-trip to C code
> because we know at compile-time what the `release` function is: it's the `drop`
> implementation for the wrapped type (`T` in `Ref<T>`).

That's not what I meant by bringing up a kref. I was trying to ask
where the "real" lock here is. It has to be somewhere...

> > and that requires a lock to work properly. Where is the lock here?
>
> We don't need a lock. Once the refcount reaches zero, we know that nothing else
> has pointers to the memory block; the lifetime rules guarantee that if there is
> a reference to a `Ref<T>`, then it cannot outlive the `Ref<T>` itself.

Even with multiple CPUs? What enforces these lifetime rules?

thanks,

greg k-h