Skip to content

Safe initialization of pinned structs #290

Closed
@nbdd0121

Description

@nbdd0121

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    • libRelated to the `rust/` library.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions