forked from torvalds/linux
-
Notifications
You must be signed in to change notification settings - Fork 440
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Simple Rust driver that touches real hardware: PR 5 #381
Closed
Closed
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would the code be much more complicated if you had
Pin<Box<...>>
here?My impression is that it wouldn't be too different (after the simplification you're applying), and the compiler would give you a little bit of help in enforcing the invariant.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the review! I'm glad you are asking this question. I can use it as a vehicle to check and update my understanding of the whole
Pin
ning concept.As far as I know, using
Pin<Box<bindings::platform_driver>>
on its own here won't change a thing. It won't prevent theplatform_driver
from moving out of theBox
. Why? Becauseplatform_driver: Unpin
by default. So we can always callPin::into_inner()
safely, and the inner object will simply move out without complaints.If we really want to prevent moving out, we need to mark
platform_driver
as!Unpin
:But Rust doesn't currently allow that!
So we'd have to use
PhantomPinned
, which in turn requires us to use some wrapper type:But now we are storing a different structure, one we can't get an inner mutable reference to without using
unsafe
blocks. Yet the kernel'splatform_register()
/platform_unregister()
require mutable references !During registration, it's not so bad, because we can register first, then pin. Which kind of defeats the reason for using
Pin<>
in the first place.. But the unregistration function now becomes problematic: it needs a mutable reference to abindings::platform_driver
, but that structure is wrapped in a!Unpin
outer wrapper, so we need a bunch ofunsafe
acrobatics there to get the reference.As far as I can tell, it all becomes a huge unreadable, unsafe mess. IMHO using a "do not move out" Invariant works pretty well. But I am open to suggestions.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would not be an invariant, no? i.e. an invariant is something that is guaranteed (as long as one does not break preconditions on e.g. constructors).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would be an invariant, but not one the compiler will help enforcing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we want to avoid using
Pin<>
here (and I think that's probably the way to go, subject to further feedback), then we'll need a way to document / enforce that no code insideplatdev::Registration
movespdrv
out of itsBox
. We should not need to enforce this externally, aspdrv
is a private member field, and is never exposed externally. Except through a kernel function, but that doesn't count.What is the best way to accomplish that? Using an invariant? Or some other way?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the tip! I'm sorry that my confusion is so confusing :) I'd like to ask a few questions, to update my internal model.
When we register a miscdev for example, we pass a pointer to a
bindings::miscdevice
to the kernel on registration. I'm assuming we need two guarantees:miscdevice
andmiscdevice
The easiest way is probably like this: (a)
Yet the existing codebase does the following: (b)
So my question is: why does the current codebase choose (b) over (a)? What are the advantages of (b) over (a)?
According to the comments in the code,
but I am not sure that's a sufficient justification, because (a) is not
Pin
ned, yet also allows self-referential structures.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sounds correct to me. If one keeps the things that need to be pinned inside a
Box
, and the box remains private (i.e. others cannot mess with it), and the implementation of the abstraction takes care of never moving it, then it is OK. It is also OK toPin
the privateBox
to ensure it is not moved by mistake by the implementation.Though I do not recall why
Registration
is pinned here, rather than leaving it a detail of the implementation. @wedsonaf?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Option (b) isn't really what we have in the current codebase. What we have is (simplified):
Then as a helper we have:
And the reason is that we want to have a zero-cost abstraction. If we forced everyone to use
new_pinned
or option (a) above, we would require everyone to have an extra allocation. Here's an example (C Binder) on how miscdev is embedded in some outer structure: https://elixir.bootlin.com/linux/latest/source/drivers/android/binder_internal.h#L36 -- it would require an extra allocation if we were to requiremiscdev
to be always boxed; such a solution would preclude the zero-cost case, but the current implementation allows both: if you want zero-cost, you embedRegistration
in your outer structure and ensure that it's pinned (by pinning the outer structure and ensuring that the projection is also pinned), but if you don't care about zero-cost, just usenew_pinned()
and pay the cost of the extra allocation.Regarding Sven's example, that is perfectly fine: note that
pd1.set(pd2)
causes the destructor of whatever was pinned inpd1
to be called before it is overwritten bypd2
, that is, if what was initially pinned was aRegistration
, its destructor would unregister it first, then it would be overwritten with the new one.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@wedsonaf and @ojeda, thank you so much for your patience and willingness to explain, I really appreciate it !! Let’s see if I understand the underlying issues better now. I encourage you to update my understanding if something doesn’t sound quite right.
When we create an abstraction that uses a kernel C structure in Rust, e.g. via
misc_register()
or__mutex_init()
, there are a couple of “gotchas”:(1) is easy. Users from outside the kernel crate can’t see private fields, so we can always make the C structure private, and ensure our public functions use it as prescribed in the kernel.
(2) is trickier! If the C structure is just a straight-forward field inside our Rust struct, then our users could move it around in memory, which breaks the C API. There are two solutions to this problem:
Box
ed field of the Rust struct. Our users can’t touch it because it’s private. They also can’t move it around - moving the Rust structure in memory is fine, because it’s just holding a pointer to theBox
ed C struct.# Safety
requirements, or method functions with aself
type that’sPin<&Self>
orPin<&mut Self>
. All of these require our users to use unsafe blocks and make careful reasonings about pinning and safety.All are partially flawed. Solution (1) leads to multiple allocations and indirections, which is a non-zero cost abstraction - hey that’s not what Rust is supposed to be about! Solution (2) is true zero-cost, yet requires users to write tricky unsafe code just to use what are essentially very simple abstractions. They have a good chance of getting it wrong, and go down in flames - hey that’s also not what Rust is all about!
I am not sure what the group consensus is here. Are the unsafe blocks and tricky pin projections in driver code not seen as a problem?
For
platdev
I will go with solution (1) for now, which is easy to use for driver writers, yet leads to multiple allocations and indirections. For a stone-cold path such as registering a platform driver (not a device), that shouldn't be a problem.Edited to add: I'm assuming a solution along the lines of
pin-project
has been considered at some point, and rejected?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think your understanding is correct!
I think it is reasonable to favor the easiest solution for cold paths, although people may still complain because, in the case of allocations, they may lead to other issues outside the cold path (say, memory fragmentation with SLOB).
Then there is the problem of having to return more
Result
s. And, in general, the overall feeling that the language "forces" us to do allocations just to have a nicer API.I don't think what way would be best for us, but I tend to think that medium-/long-term we should invest in finding out the best ergonomic solution while keeping the zero-cost (via the
pin-*
-like crates, a new language feature, or something else).