Skip to content

Commit 844cac5

Browse files
committed
Merge branch '⚡️-sync-mutex' into 🦆
2 parents 8421c56 + cbc18bf commit 844cac5

File tree

7 files changed

+410
-34
lines changed

7 files changed

+410
-34
lines changed

README.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ Constance is a proof-of-concept of a static RTOS that utilizes Rust's compile-ti
2424
| System Topology | ![Uniprocessor: Supported] ![Homogeneous Multiprocessor: Under Consideration] ![Heterogeneous Multiprocessor: Not Considering] |
2525
| Kernel Core | ![Tasks: Supported] ![Hunks: Supported] ![Wait Objects: Supported] ![Timeouts: Supported] ![Timers: Supported] ![Interrupts: Supported] ![Startup Hooks: Supported] ![CPU Exceptions: Under Consideration] ![Panicking: Under Consideration] |
2626
| Kernel Synchronization | ![Semaphores: Supported] ![Event Groups: Supported] ![Mutexes: Under Consideration] |
27-
| Library | ![Mutex: Under Consideration] ![RwLock: Under Consideration] ![Once: Under Consideration] ![C API: Under Consideration] |
27+
| Library | ![Mutex: Supported] ![RwLock: Under Consideration] ![Once: Under Consideration] ![C API: Under Consideration] |
2828
| Port (Simulator) | ![POSIX: Supported] ![Windows: Under Consideration] |
2929
| Port (Arm M-Profile) | ![Armv8-M Mainline (no CMSE): Supported] ![Armv8-M Baseline (no CMSE): Supported] ![Armv7-M: Supported] ![Armv6-M: Supported] |
3030
| Port (Arm A-Profile) | ![Armv7-A (no FPU): Supported] |
@@ -47,7 +47,7 @@ Constance is a proof-of-concept of a static RTOS that utilizes Rust's compile-ti
4747
[CPU Exceptions: Under Consideration]: https://img.shields.io/badge/CPU%20Exceptions-Under%20Consideration-cc7070?style=flat-square
4848
[Panicking: Under Consideration]: https://img.shields.io/badge/Panicking-Under%20Consideration-cc7070?style=flat-square
4949

50-
[Mutex: Under Consideration]: https://img.shields.io/badge/Mutex-Under%20Consideration-cc7070?style=flat-square
50+
[Mutex: Supported]: https://img.shields.io/badge/Mutex-Supported-success?style=flat-square
5151
[RwLock: Under Consideration]: https://img.shields.io/badge/RwLock-Under%20Consideration-cc7070?style=flat-square
5252
[Once: Under Consideration]: https://img.shields.io/badge/Once-Under%20Consideration-cc7070?style=flat-square
5353
[C API: Under Consideration]: https://img.shields.io/badge/C%20API-Under%20Consideration-cc7070?style=flat-square

src/constance/src/lib.md

+2-1
Original file line numberDiff line numberDiff line change
@@ -203,7 +203,7 @@ fn task_body(_: usize) {
203203
[`KernelCfg2`]: crate::kernel::KernelCfg2
204204
[`Task`]: crate::kernel::Task
205205

206-
Configuration functions are highly composable as they can call other configuration functions in turn. In some sense, this is a way to attribute a certain semantics to a group of kernel objects, making them behave in a meaningful way as a whole, and expose a whole new, higher-level interface. For example, a mutex object similar to `std::sync::Mutex` can be created by combining a low-level mutex object (not implemented yet) and a [`Hunk`]`<System, UnsafeCell<T>>`.
206+
Configuration functions are highly composable as they can call other configuration functions in turn. In some sense, this is a way to attribute a certain semantics to a group of kernel objects, making them behave in a meaningful way as a whole, and expose a whole new, higher-level interface. For example, a [mutex object] similar to `std::sync::Mutex` can be created by combining a low-level mutex object (not implemented yet) and a [`Hunk`]`<System, UnsafeCell<T>>`.
207207

208208
```rust
209209
# #![feature(const_fn)]
@@ -230,6 +230,7 @@ mod m {
230230
```
231231

232232
[`Hunk`]: crate::kernel::Hunk
233+
[mutex object]: crate::sync::Mutex
233234

234235
The constructors of kernel objects are configuration functions by themselves, but they are different from normal configuration functions in that they can actually mutate the contents of `CfgBuilder` (which `build!` will use to create kernel structures in the final form), ultimately shaping the outcome of the configuration process. Therefore, they are the smallest building blocks of configuration functions.
235236

src/constance/src/sync.rs

+3-31
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,4 @@
11
//! Safe synchronization primitives.
2-
use core::{cell::UnsafeCell, fmt, marker::PhantomData};
3-
4-
use crate::{
5-
kernel::{cfg::CfgBuilder, Hunk},
6-
prelude::*,
7-
};
8-
9-
pub struct Mutex<System, T> {
10-
hunk: Hunk<System, UnsafeCell<T>>,
11-
_phantom: PhantomData<(System, T)>,
12-
}
13-
14-
impl<System: Kernel, T: fmt::Debug + 'static> fmt::Debug for Mutex<System, T> {
15-
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
16-
// TODO: Display the contents if unlocked
17-
f.debug_struct("Mutex").field("hunk", &self.hunk).finish()
18-
}
19-
}
20-
21-
impl<System: Kernel, T: 'static + Init> Mutex<System, T> {
22-
/// Construct a `Mutex`.
23-
///
24-
/// This is a configuration function. Call this method from your app's
25-
/// configuration function.
26-
pub const fn new(b: &mut CfgBuilder<System>) -> Self {
27-
Self {
28-
hunk: Hunk::<_, UnsafeCell<T>>::build().finish(b),
29-
_phantom: PhantomData,
30-
}
31-
}
32-
}
2+
pub mod mutex;
3+
#[doc(no_inline)]
4+
pub use self::mutex::Mutex;

src/constance/src/sync/mutex.rs

+199
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
1+
use core::{cell::UnsafeCell, fmt, marker::PhantomData};
2+
3+
use crate::{
4+
kernel::{cfg::CfgBuilder, Hunk, PollSemaphoreError, Semaphore, WaitSemaphoreError},
5+
prelude::*,
6+
};
7+
8+
// TODO: Upgrade `Mutex` to use a real mutex. And then remove the paragraph
9+
// in `Mutex` regarding this
10+
11+
/// A mutual exclusion primitive useful for protecting shared data from
12+
/// concurrent access.
13+
///
14+
/// This type is currently implemented using [`Semaphore`]. It will be
15+
/// upgraded to a real mutex (with priority inversion prevention) in a
16+
/// future version of Constance.
17+
///
18+
/// [`Semaphore`]: crate::kernel::Semaphore
19+
pub struct Mutex<System, T> {
20+
hunk: Hunk<System, UnsafeCell<T>>,
21+
sem: Semaphore<System>,
22+
_phantom: PhantomData<(System, T)>,
23+
}
24+
25+
unsafe impl<System: Kernel, T: 'static + Send> Send for Mutex<System, T> {}
26+
unsafe impl<System: Kernel, T: 'static + Send> Sync for Mutex<System, T> {}
27+
28+
/// An RAII implementation of a "scoped lock" of a mutex. When this structure
29+
/// is dropped, the lock will be released.
30+
///
31+
/// This structure is created by the [`lock`] and [`try_lock`] methods of
32+
/// [`Mutex`].
33+
///
34+
/// [`lock`]: Mutex::lock
35+
/// [`try_lock`]: Mutex::try_lock
36+
#[must_use = "if unused the Mutex will immediately unlock"]
37+
pub struct MutexGuard<'a, System: Kernel, T: 'static> {
38+
mutex: &'a Mutex<System, T>,
39+
_no_send_sync: PhantomData<*mut ()>,
40+
}
41+
42+
unsafe impl<System: Kernel, T: 'static + Sync> Sync for MutexGuard<'_, System, T> {}
43+
44+
/// Error type of [`Mutex::lock`].
45+
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
46+
#[repr(i8)]
47+
pub enum LockError {
48+
/// CPU Lock is active, the current context is not [waitable], or the
49+
/// current context is not [a task context].
50+
///
51+
/// [waitable]: crate#contexts
52+
/// [a task context]: crate#contexts
53+
BadContext = WaitSemaphoreError::BadContext as i8,
54+
/// The wait operation was interrupted by [`Task::interrupt`].
55+
///
56+
/// [`Task::interrupt`]: crate::kernel::Task::interrupt
57+
Interrupted = WaitSemaphoreError::Interrupted as i8,
58+
}
59+
60+
/// Error type of [`Mutex::try_lock`].
61+
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
62+
#[repr(i8)]
63+
pub enum TryLockError {
64+
/// CPU Lock is active.
65+
BadContext = PollSemaphoreError::BadContext as i8,
66+
/// The lock could not be acquire at this time because the operation would
67+
/// otherwise block.
68+
WouldBlock = PollSemaphoreError::Timeout as i8,
69+
}
70+
71+
impl<System: Kernel, T: 'static + Init> Mutex<System, T> {
72+
/// Construct a `Mutex`. The content is initialized with [`Init`].
73+
///
74+
/// This is a configuration function. Call this method from your app's
75+
/// configuration function.
76+
pub const fn new(b: &mut CfgBuilder<System>) -> Self {
77+
Self {
78+
hunk: Hunk::<_, UnsafeCell<T>>::build().finish(b),
79+
sem: Semaphore::build().initial(1).maximum(1).finish(b),
80+
_phantom: PhantomData,
81+
}
82+
}
83+
}
84+
85+
impl<System: Kernel, T: 'static> Mutex<System, T> {
86+
/// Acquire the mutex, blocking the current thread until it is able to do
87+
/// so.
88+
pub fn lock(&self) -> Result<MutexGuard<'_, System, T>, LockError> {
89+
self.sem.wait_one().map_err(|e| match e {
90+
WaitSemaphoreError::BadId => unreachable!(),
91+
WaitSemaphoreError::BadContext => LockError::BadContext,
92+
WaitSemaphoreError::Interrupted => LockError::Interrupted,
93+
})?;
94+
Ok(MutexGuard {
95+
mutex: self,
96+
_no_send_sync: PhantomData,
97+
})
98+
}
99+
100+
/// Attempt to acquire the mutex.
101+
pub fn try_lock(&self) -> Result<MutexGuard<'_, System, T>, TryLockError> {
102+
// A real mutex can't be locked by an interrupt handler. We emulate it
103+
// by a semaphore at this time, so we need to check whether this
104+
// condition is violated
105+
if !System::is_task_context() {
106+
return Err(TryLockError::BadContext);
107+
}
108+
109+
self.sem.poll_one().map_err(|e| match e {
110+
PollSemaphoreError::BadId => unreachable!(),
111+
PollSemaphoreError::BadContext => TryLockError::BadContext,
112+
PollSemaphoreError::Timeout => TryLockError::WouldBlock,
113+
})?;
114+
Ok(MutexGuard {
115+
mutex: self,
116+
_no_send_sync: PhantomData,
117+
})
118+
}
119+
120+
/// Get a raw pointer to the contained data.
121+
#[inline]
122+
pub fn get_ptr(&self) -> *mut T {
123+
self.hunk.get()
124+
}
125+
}
126+
127+
impl<System: Kernel, T: fmt::Debug + 'static> fmt::Debug for Mutex<System, T> {
128+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
129+
match self.try_lock() {
130+
Ok(guard) => f.debug_struct("Mutex").field("data", &&*guard).finish(),
131+
Err(TryLockError::BadContext) => {
132+
struct BadContextPlaceholder;
133+
impl fmt::Debug for BadContextPlaceholder {
134+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
135+
f.write_str("<CPU context active>")
136+
}
137+
}
138+
139+
f.debug_struct("Mutex")
140+
.field("data", &BadContextPlaceholder)
141+
.finish()
142+
}
143+
Err(TryLockError::WouldBlock) => {
144+
struct LockedPlaceholder;
145+
impl fmt::Debug for LockedPlaceholder {
146+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
147+
f.write_str("<locked>")
148+
}
149+
}
150+
151+
f.debug_struct("Mutex")
152+
.field("data", &LockedPlaceholder)
153+
.finish()
154+
}
155+
}
156+
}
157+
}
158+
159+
impl<System: Kernel, T: fmt::Debug + 'static> fmt::Debug for MutexGuard<'_, System, T> {
160+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
161+
fmt::Debug::fmt(&**self, f)
162+
}
163+
}
164+
165+
impl<System: Kernel, T: fmt::Display + 'static> fmt::Display for MutexGuard<'_, System, T> {
166+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
167+
fmt::Display::fmt(&**self, f)
168+
}
169+
}
170+
171+
/// The destructor of `MutexGuard` that releases the lock. It will panic if
172+
/// CPU Lock is active.
173+
impl<System: Kernel, T: 'static> Drop for MutexGuard<'_, System, T> {
174+
#[inline]
175+
fn drop(&mut self) {
176+
self.mutex.sem.signal_one().unwrap();
177+
}
178+
}
179+
180+
impl<System: Kernel, T: 'static> core::ops::Deref for MutexGuard<'_, System, T> {
181+
type Target = T;
182+
#[inline]
183+
fn deref(&self) -> &Self::Target {
184+
// Safety: `MutexGuard` represents a permit acquired from the semaphore,
185+
// which grants the bearer an exclusive access to the underlying
186+
// data
187+
unsafe { &*self.mutex.hunk.get() }
188+
}
189+
}
190+
191+
impl<System: Kernel, T: 'static> core::ops::DerefMut for MutexGuard<'_, System, T> {
192+
#[inline]
193+
fn deref_mut(&mut self) -> &mut Self::Target {
194+
// Safety: `MutexGuard` represents a permit acquired from the semaphore,
195+
// which grants the bearer an exclusive access to the underlying
196+
// data
197+
unsafe { &mut *self.mutex.hunk.get() }
198+
}
199+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
//! Checks miscellaneous properties of [`constance::sync::Mutex`].
2+
use constance::{
3+
kernel::{cfg::CfgBuilder, Hunk, Task},
4+
prelude::*,
5+
sync::mutex::Mutex,
6+
};
7+
8+
use super::Driver;
9+
use crate::utils::SeqTracker;
10+
11+
pub struct App<System> {
12+
task2: Task<System>,
13+
mutex: Mutex<System, u32>,
14+
seq: Hunk<System, SeqTracker>,
15+
}
16+
17+
impl<System: Kernel> App<System> {
18+
pub const fn new<D: Driver<Self>>(b: &mut CfgBuilder<System>) -> Self {
19+
Task::build()
20+
.start(task1_body::<System, D>)
21+
.priority(2)
22+
.active(true)
23+
.finish(b);
24+
let task2 = Task::build()
25+
.start(task2_body::<System, D>)
26+
.priority(1)
27+
.active(false)
28+
.finish(b);
29+
30+
let mutex = Mutex::new(b);
31+
32+
let seq = Hunk::<_, SeqTracker>::build().finish(b);
33+
34+
App { task2, mutex, seq }
35+
}
36+
}
37+
38+
fn task1_body<System: Kernel, D: Driver<App<System>>>(_: usize) {
39+
let app = D::app();
40+
41+
app.seq.expect_and_replace(0, 1);
42+
43+
{
44+
let mut lock = app.mutex.lock().unwrap();
45+
app.task2.activate().unwrap(); // giving the control to `task2`
46+
47+
// back from `task2`, which is being blocked...
48+
app.seq.expect_and_replace(2, 3);
49+
*lock = 42;
50+
51+
// release the lock and let `task2` continue. the control will return to
52+
// here when `task2` completes
53+
}
54+
55+
app.seq.expect_and_replace(5, 6);
56+
57+
assert_eq!(*app.mutex.lock().unwrap(), 56);
58+
59+
D::success();
60+
}
61+
62+
fn task2_body<System: Kernel, D: Driver<App<System>>>(_: usize) {
63+
let app = D::app();
64+
65+
app.seq.expect_and_replace(1, 2);
66+
67+
{
68+
let mut lock = app.mutex.lock().unwrap(); // blocks because `task1` has lock
69+
70+
// preempts `task1` when it releases the lock
71+
app.seq.expect_and_replace(3, 4);
72+
assert_eq!(*lock, 42);
73+
*lock = 56;
74+
}
75+
76+
app.seq.expect_and_replace(4, 5);
77+
}

0 commit comments

Comments
 (0)