The kernel has a lot of data structures that cannot be freely moved, e.g. Mutex and CondVar. Currently what we do is to first unsafely create instance of them, pin them, and then unsafely initialize them after having them pinned (currently the code does not require unsafe to initialize Mutex, which I believe is a bug, as it could be initialized while locked, which is definitely unsound).
For example, this snippet (related #286) from miscdev needs 3 unsafes (5 if we count both inits):
|
struct SharedState { |
|
state_changed: CondVar, |
|
inner: Mutex<SharedStateInner>, |
|
} |
|
|
|
impl SharedState { |
|
fn try_new() -> Result<Pin<Arc<Self>>> { |
|
// SAFETY: `state` is pinning `Arc`, which implements `Unpin`. |
|
let state = unsafe { |
|
Pin::new_unchecked(Arc::try_new(Self { |
|
// SAFETY: `condvar_init!` is called below. |
|
state_changed: CondVar::new(), |
|
// SAFETY: `mutex_init!` is called below. |
|
inner: Mutex::new(SharedStateInner { token_count: 0 }), |
|
})?) |
|
}; |
|
// SAFETY: `state_changed` is pinned behind `Arc`. |
|
let state_changed = unsafe { Pin::new_unchecked(&state.state_changed) }; |
|
kernel::condvar_init!(state_changed, "SharedState::state_changed"); |
|
// SAFETY: `inner` is pinned behind `Arc`. |
|
let inner = unsafe { Pin::new_unchecked(&state.inner) }; |
|
kernel::mutex_init!(inner, "SharedState::inner"); |
|
Ok(state) |
|
} |
|
} |
And this is a very common pattern. In the current state, essentially any use of CondVar, Mutex, Spinlock or any pinned types require a lot of unsafe to initialize. Of course we can have these types Box-ed internally but that's many unnecessary allocations.
So I spent the past few days designing and implementing a safe way to pin-initialized struct, pin-init (doc, repo). This design allows safe initialization and use of pthread mutex without any boxing: https://github.com/nbdd0121/pin-init/blob/trunk/examples/pthread_mutex.rs.
Kernel CondVar & Mutex could be implemented in a similar way. With pin-init, the above snippet could be written like this:
#[pin_init]
struct SharedState {
#[pin]
state_changed: CondVar,
#[pin]
inner: Mutex<SharedStateInner>,
}
impl SharedState {
fn try_new() -> Result<Pin<Arc<Self>>> {
try_new_arc(init_pin!(Self {
state_changed: |s| kernel::condvar_new!(s, "SharedState::state_changed"),
inner: |s| kernel::mutex_new!(s, "SharedState::inner", SharedStateInner { token_count: 0 }),
}))
}
}
Approaches like this make code more readable and safer; but it requires procedural macro (and parsing capability of syn) so it might not be easy to integrate at current stage.
The kernel has a lot of data structures that cannot be freely moved, e.g.
MutexandCondVar. Currently what we do is to firstunsafely create instance of them, pin them, and thenunsafely initialize them after having them pinned (currently the code does not requireunsafeto initializeMutex, which I believe is a bug, as it could be initialized while locked, which is definitely unsound).For example, this snippet (related #286) from miscdev needs 3 unsafes (5 if we count both inits):
linux/samples/rust/rust_miscdev.rs
Lines 35 to 59 in 1a73789
And this is a very common pattern. In the current state, essentially any use of
CondVar,Mutex,Spinlockor any pinned types require a lot ofunsafeto initialize. Of course we can have these typesBox-ed internally but that's many unnecessary allocations.So I spent the past few days designing and implementing a safe way to pin-initialized struct,
pin-init(doc, repo). This design allows safe initialization and use of pthread mutex without any boxing: https://github.com/nbdd0121/pin-init/blob/trunk/examples/pthread_mutex.rs.Kernel
CondVar&Mutexcould be implemented in a similar way. Withpin-init, the above snippet could be written like this:Approaches like this make code more readable and safer; but it requires procedural macro (and parsing capability of
syn) so it might not be easy to integrate at current stage.