1
2
Fork
You've already forked spin
0

Implementation of a Irq_Mutex (see Issue#160) #162

Open
Shinribo wants to merge 5 commits from Shinribo/Implemented-IrqMutex into master
pull from: Shinribo/Implemented-IrqMutex
merge into: zesterer:master
zesterer:master
zesterer:fixes
zesterer:fix-ub
zesterer:revert-127-ci
zesterer:spinlock
Shinribo commented 2024年04月13日 00:55:20 +02:00 (Migrated from github.com)
Copy link

The IrqMutex is based on the SpinMutex with Mask/Restore Interrupts Functions added.
As the Interrupt Mask/Restore is architecture dependant, i moved them into a seperate file so make it easier to find them and add support for other architectures in the future.
I hope i properly fixed/changed affected docs.
Docs for the arch dependant functions are rather short, but should be sufficient.
Crate compiled successfully with the IrqMutex feature enabled.
Testing failed when it hit the Interrupt Functions as they require Ring0 on x86_64, testing in a Kernel/Ring0 Component required

The IrqMutex is based on the SpinMutex with Mask/Restore Interrupts Functions added. As the Interrupt Mask/Restore is architecture dependant, i moved them into a seperate file so make it easier to find them and add support for other architectures in the future. I hope i properly fixed/changed affected docs. Docs for the arch dependant functions are rather short, but should be sufficient. Crate compiled successfully with the IrqMutex feature enabled. Testing failed when it hit the Interrupt Functions as they require Ring0 on x86_64, testing in a Kernel/Ring0 Component required
zesterer (Migrated from github.com) reviewed 2024年04月13日 00:55:20 +02:00
zesterer (Migrated from github.com) requested changes 2024年04月13日 03:42:58 +02:00
zesterer (Migrated from github.com) left a comment
Copy link

I am having second thoughts about the implementation of this type in spin.

After looking closer, it seems that critical-section already provides its own Mutex implementation. The only disadvantage is that it doesn't supply lock-api support, but that's probably better done as a feature request on that crate.

An IRQ mutex is a fairly irregular thing to have in a crate that caters primarily to spin-driven locking. I know I was initially open to the idea, but I'm finding it difficult to justify when simply wrapping the crate could achieve exactly the same thing (i.e: one could write a crate that wraps spin but hooks interrupt masking/restoring too).

I am having second thoughts about the implementation of this type in `spin`. After looking closer, it seems that `critical-section` already [provides its own `Mutex` implementation](https://docs.rs/critical-section/1.1.2/critical_section/struct.Mutex.html). The only disadvantage is that it doesn't supply `lock-api` support, but that's probably better done as a feature request on that crate. An IRQ mutex is a fairly irregular thing to have in a crate that caters primarily to spin-driven locking. I know I was initially open to the idea, but I'm finding it difficult to justify when simply wrapping the crate could achieve exactly the same thing (i.e: one could write a crate that wraps `spin` but hooks interrupt masking/restoring too).
zesterer (Migrated from github.com) commented 2024年04月13日 03:27:59 +02:00
Copy link

This isn't portable, I think the critical-section crate should be used instead.

This isn't portable, I think the [`critical-section`](https://crates.io/crates/critical-section) crate should be used instead.
zesterer (Migrated from github.com) commented 2024年04月13日 03:31:47 +02:00
Copy link

I don't think that the IRQ mutex should be used as the default implementation. Spin mutexes are supported on every platform, and are still useful on those platforms (even and perhaps especially embedded platforms), and the IRQ mutex has different behaviour to the other mutexes when invoked in an interrupt. For this reason, it should just be its own stand-alone mutex and never accidentally the default implementation.

I don't think that the IRQ mutex should be used as the default implementation. Spin mutexes are supported on every platform, and are still useful on those platforms (even and perhaps especially embedded platforms), and the IRQ mutex has different behaviour to the other mutexes when invoked in an interrupt. For this reason, it should just be its own stand-alone mutex and never accidentally the default implementation.
zesterer (Migrated from github.com) reviewed 2024年04月13日 03:44:17 +02:00
zesterer (Migrated from github.com) commented 2024年04月13日 03:44:17 +02:00
Copy link

It's worth noting that if this approach is every used for a RwLock-like primitive, it will be incorrect since one can multiple read guards in arbitrary orders.

It's worth noting that if this approach is every used for a `RwLock`-like primitive, it will be incorrect since one can multiple read guards in arbitrary orders.
zesterer commented 2024年04月13日 12:30:38 +02:00 (Migrated from github.com)
Copy link

So, allow me to propose an alternative design to you that I've been thinking about overnight.

A crate, called irq-safe-spin (or something). It pulls in spin-rs as a dependency.

It provides a type that looks like

pubstruct Mutex<T,M: IsMutex<T>=spin::Mutex<T>>{...}

This mutex type implements all of the methods from spin's mutex, but allows you to swap out the implementation (i.e: so you can use SpinMutex, TicketMutex, or FairMutex internally, or just 'whatever the default is' with Mutex).

Its guard types automatically enable/disable interrupts using the critical-section crate.

So, you can pull this crate in and use it as a drop-in replacement for the mutex types in spin-rs, but you get interrupt safety 'for free'.

What do you think?

Alternatively, we could have a go at implementing such a wrapper in spin-rs, although as I say: it does feel somewhat outside the domain of spin-rs, but I'm less opposed to this idea because it can be made a feature that doesn't grate poorly against the rest of the crate.

So, allow me to propose an alternative design to you that I've been thinking about overnight. A crate, called `irq-safe-spin` (or something). It pulls in `spin-rs` as a dependency. It provides a type that looks like ```rs pub struct Mutex<T, M: IsMutex<T> = spin::Mutex<T>> { ... } ``` This mutex type implements all of the methods from spin's mutex, but allows you to swap out the implementation (i.e: so you can use `SpinMutex`, `TicketMutex`, or `FairMutex` internally, or just 'whatever the default is' with `Mutex`). Its guard types automatically enable/disable interrupts using the `critical-section` crate. So, you can pull this crate in and use it as a drop-in replacement for the mutex types in `spin-rs`, but you get interrupt safety 'for free'. What do you think? *Alternatively, we could have a go at implementing such a wrapper in `spin-rs`, although as I say: it does feel *somewhat* outside the domain of `spin-rs`, but I'm less opposed to this idea because it can be made a feature that doesn't grate poorly against the rest of the crate.*
Shinribo commented 2024年04月14日 13:34:10 +02:00 (Migrated from github.com)
Copy link

I am open to both ideas.
If we create a wrapper crate then i will definetly need some assistance as it would be my first crate.

I am open to both ideas. If we create a wrapper crate then i will definetly need some assistance as it would be my first crate.
Shinribo commented 2024年04月26日 09:33:29 +02:00 (Migrated from github.com)
Copy link

Sorry for the delay
I thought about both ways and IMHO it would be better to add the wrapper to the spin crate for now

Sorry for the delay I thought about both ways and IMHO it would be better to add the wrapper to the spin crate for now
Shinribo commented 2024年04月26日 09:40:24 +02:00 (Migrated from github.com)
Copy link

If you don't disagree i would start with a wrapper for Mutex and RwLock and add critical section as a optional dependency

If you don't disagree i would start with a wrapper for Mutex and RwLock and add critical section as a optional dependency
zesterer commented 2024年04月26日 12:24:41 +02:00 (Migrated from github.com)
Copy link

I would just start with Mutex for now. RwLock has more complicated properties, so I think it's better to demonstrate the concept first.

I would just start with `Mutex` for now. `RwLock` has more complicated properties, so I think it's better to demonstrate the concept first.
Shinribo commented 2024年05月01日 15:12:31 +02:00 (Migrated from github.com)
Copy link

So i changed the implementation but i cant get IrqMutexGuard::leak() to work properly, i currently dont know how to fix it.
Also if we keep IrqMutexGuard::leak() i need to add a critical_section::release() in it which i forgot to add

So i changed the implementation but i cant get IrqMutexGuard::leak() to work properly, i currently dont know how to fix it. Also if we keep IrqMutexGuard::leak() i need to add a critical_section::release() in it which i forgot to add
Shinribo commented 2024年05月01日 21:32:56 +02:00 (Migrated from github.com)
Copy link

I thew out lock-api for now as i am currently not sufficiently familiar with it, i would readd it once i am sure that it can work with the interrupt side effects of the irqmutex

I thew out lock-api for now as i am currently not sufficiently familiar with it, i would readd it once i am sure that it can work with the interrupt side effects of the irqmutex
Shinribo commented 2024年05月01日 21:45:33 +02:00 (Migrated from github.com)
Copy link

I am having second thoughts about the implementation of this type in spin.

After looking closer, it seems that critical-section already provides its own Mutex implementation. The only disadvantage is that it doesn't supply lock-api support, but that's probably better done as a feature request on that crate.

An IRQ mutex is a fairly irregular thing to have in a crate that caters primarily to spin-driven locking. I know I was initially open to the idea, but I'm finding it difficult to justify when simply wrapping the crate could achieve exactly the same thing (i.e: one could write a crate that wraps spin but hooks interrupt masking/restoring too).

I dont see IrqMutex that much out of place in this crate, its more like a platform specific variation of a Mutex variant same as spin/ticket/fair variations for Mutex. I also looked at cs' Mutex and for me it looks like it just calls aquire/restore when accessing the inner value without atomic locking.

> I am having second thoughts about the implementation of this type in `spin`. > > After looking closer, it seems that `critical-section` already [provides its own `Mutex` implementation](https://docs.rs/critical-section/1.1.2/critical_section/struct.Mutex.html). The only disadvantage is that it doesn't supply `lock-api` support, but that's probably better done as a feature request on that crate. > > An IRQ mutex is a fairly irregular thing to have in a crate that caters primarily to spin-driven locking. I know I was initially open to the idea, but I'm finding it difficult to justify when simply wrapping the crate could achieve exactly the same thing (i.e: one could write a crate that wraps `spin` but hooks interrupt masking/restoring too). I dont see IrqMutex that much out of place in this crate, its more like a platform specific variation of a `Mutex` variant same as `spin`/`ticket`/`fair` variations for `Mutex`. I also looked at cs' Mutex and for me it looks like it just calls aquire/restore when accessing the inner value without atomic locking.
zesterer (Migrated from github.com) reviewed 2024年05月09日 10:19:50 +02:00
zesterer (Migrated from github.com) left a comment
Copy link

Implementation-wise, this all looks fine, a very solid springboard to build upon. See my other comment for a concern I have though.

Implementation-wise, this all looks fine, a very solid springboard to build upon. See my other comment for a concern I have though.
@ -0,0 +1,291 @@
usecritical_section::RestoreState;
zesterer (Migrated from github.com) commented 2024年05月09日 10:19:10 +02:00
Copy link

I think this violates the safety invariants of critical-section. From the docs:

acquire/release pairs must be "properly nested", ie it’s not OK to do a=acquire(); b=acquire(); release(a); release(b);.

Right now, this API permits us to do the following:

letx=Mutex::new(1);lety=Mutex::new(2);letx_guard=x.lock();lety_guard=y.lock();drop(x_guard);// `y_guard` is still live here

It seems like the Mutex implementation here gets away with it by using a critical section token generated via a closure which is, to my knowledge, the only way to safely guarantee proper ordering in Rust.

Initially I thought that an option might be to also store a counter along with the token and just panic if the token is out of order, but sadly that's unsound because you'd need to have some sort of global static counter, and there's no guarantee that the user isn't compiling several versions of spin into the same executable, resulting in multiple statics.

Eurgh, this is indeed a tough one. My feeling is that we might need to do something a bit funky API-wise and have lock accept a closure, like so:

letx=Mutex::new(1);x.lock(|guard|{*guard=2;});

It's a bit hairy, but at least it gets us safety back again, and it's probably not the worst crime imaginable.

Of course, there's another factor at play here: the critical-section's safety requirements are probably overzealous given that it's making the assumption that within a critical section you're going to run off and do some unsynchronised data access.

The fact that we don't ever do that (because we're still using a mutex internally) means that - for practical cases - this is still perfectly safe provided you don't use critical-section elsewhere, polluting that state. This is one of those rare cases where, due to specific and non-local hardware behaviour, the safety of this abstraction does not compose, I think.

I'm really in two minds on what to do here. Although I do think that Mutex::lock(&self, impl Fn) is a safe abstraction in isolation (as is the implementation you already have here, incidentally) the net effect is one of unsafety unless we are very careful to document exactly what the abstraction does to the global interrupt state and when.

I'd be interested to know your thoughts!

I think this violates the safety invariants of `critical-section`. From [the docs](https://docs.rs/critical-section/1.1.2/critical_section/fn.acquire.html): > acquire/release pairs must be "properly nested", ie it’s not OK to do `a=acquire(); b=acquire(); release(a); release(b);`. Right now, this API permits us to do the following: ```rs let x = Mutex::new(1); let y = Mutex::new(2); let x_guard = x.lock(); let y_guard = y.lock(); drop(x_guard); // `y_guard` is still live here ``` It seems like the `Mutex` implementation here gets away with it by using a critical section token generated [via a closure](https://docs.rs/critical-section/1.1.2/critical_section/struct.Mutex.html) which is, to my knowledge, the only way to safely guarantee proper ordering in Rust. Initially I thought that an option might be to also store a counter along with the token and just panic if the token is out of order, but sadly that's unsound because you'd need to have some sort of global static counter, and there's no guarantee that the user isn't compiling several versions of `spin` into the same executable, resulting in multiple statics. Eurgh, this is indeed a tough one. My feeling is that we might need to do something a bit funky API-wise and have `lock` accept a closure, like so: ```rs let x = Mutex::new(1); x.lock(|guard| { *guard = 2; }); ``` It's a bit hairy, but at least it gets us safety back again, and it's probably not the worst crime imaginable. Of course, there's another factor at play here: the `critical-section`'s safety requirements are *probably* overzealous given that it's making the assumption that within a critical section you're going to run off and do some unsynchronised data access. The fact that we don't ever do that (because we're still using a mutex internally) means that - for practical cases - this is still perfectly safe provided you don't use `critical-section` elsewhere, polluting that state. This is one of those rare cases where, due to specific and non-local hardware behaviour, the safety of this abstraction *does not* compose, I think. I'm really in two minds on what to do here. Although I do think that `Mutex::lock(&self, impl Fn)` is a safe abstraction in isolation (as is the implementation you already have here, incidentally) the net effect is one of unsafety unless we are very careful to document exactly what the abstraction does to the global interrupt state and when. I'd be interested to know your thoughts!
le-jzr (Migrated from github.com) reviewed 2024年05月09日 10:36:56 +02:00
@ -0,0 +1,291 @@
usecritical_section::RestoreState;
le-jzr (Migrated from github.com) commented 2024年05月09日 10:36:56 +02:00
Copy link

I think the ordering requirement might be because you have to remember the original interrupt state. So releasing the first acquire first would immediately reenable interrupts, and the next release would disable them again.

I think the ordering requirement might be because you have to remember the original interrupt state. So releasing the first acquire first would immediately reenable interrupts, and the next release would disable them again.
Shinribo (Migrated from github.com) reviewed 2024年05月12日 17:20:01 +02:00
@ -0,0 +1,291 @@
usecritical_section::RestoreState;
Shinribo (Migrated from github.com) commented 2024年05月12日 17:20:01 +02:00
Copy link

I think the ordering requirement might be because you have to remember the original interrupt state. So releasing the first acquire first would immediately reenable interrupts, and the next release would disable them again.

Depending on the Critical-Section implementation its also possible that the second mutex doesnt change the interrupt state as it hasnt changed the interrupt state on entry. Nontheless the content of the second Mutex could be accessed without interrupt masking probably causing random UB.

The only solution i currently see is to make the IrqMutex unsafe and document that when nesting them the inner MutexGuard should never outlive the outer MutexGuard

> I think the ordering requirement might be because you have to remember the original interrupt state. So releasing the first acquire first would immediately reenable interrupts, and the next release would disable them again. Depending on the Critical-Section implementation its also possible that the second mutex doesnt change the interrupt state as it hasnt changed the interrupt state on entry. Nontheless the content of the second Mutex could be accessed without interrupt masking probably causing random UB. The only solution i currently see is to make the IrqMutex unsafe and document that when nesting them the inner MutexGuard should never outlive the outer MutexGuard
zesterer (Migrated from github.com) reviewed 2024年05月31日 09:45:59 +02:00
@ -0,0 +1,291 @@
usecritical_section::RestoreState;
zesterer (Migrated from github.com) commented 2024年05月31日 09:45:59 +02:00
Copy link

I think only lock needs to be unsafe, it would be possible to provide a safe closure-based API similar to critical-section. Arguably, this is the API that std::sync::Mutex really should have had from the very start...

I think only `lock` needs to be `unsafe`, it would be possible to provide a safe closure-based API similar to `critical-section`. Arguably, this is the API that `std::sync::Mutex` really *should* have had from the very start...
Shinribo (Migrated from github.com) reviewed 2024年05月31日 17:59:23 +02:00
@ -0,0 +1,291 @@
usecritical_section::RestoreState;
Shinribo (Migrated from github.com) commented 2024年05月31日 17:59:23 +02:00
Copy link

Moved the Keyword and corresponding Comment

Moved the Keyword and corresponding Comment
This pull request has changes conflicting with the target branch.
  • Cargo.toml
  • README.md
View command line instructions

Manual merge helper

Use this merge commit message when completing the merge manually.

Checkout

From your project repository, check out a new branch and test the changes.
git fetch -u origin Shinribo/Implemented-IrqMutex:Shinribo/Implemented-IrqMutex
git switch Shinribo/Implemented-IrqMutex

Merge

Merge the changes and update on Forgejo.

Warning: The "Autodetect manual merge" setting is not enabled for this repository, you will have to mark this pull request as manually merged afterwards.

git switch master
git merge --no-ff Shinribo/Implemented-IrqMutex
git switch Shinribo/Implemented-IrqMutex
git rebase master
git switch master
git merge --ff-only Shinribo/Implemented-IrqMutex
git switch Shinribo/Implemented-IrqMutex
git rebase master
git switch master
git merge --no-ff Shinribo/Implemented-IrqMutex
git switch master
git merge --squash Shinribo/Implemented-IrqMutex
git switch master
git merge --ff-only Shinribo/Implemented-IrqMutex
git switch master
git merge Shinribo/Implemented-IrqMutex
git push origin master
Sign in to join this conversation.
No reviewers
Milestone
Clear milestone
No items
No milestone
Projects
Clear projects
No items
No project
Assignees
Clear assignees
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
zesterer/spin!162
Reference in a new issue
zesterer/spin
No description provided.
Delete branch "Shinribo/Implemented-IrqMutex"

Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?