hermit/synch/semaphore.rs
1#[cfg(feature = "smp")]
2use crossbeam_utils::Backoff;
3use hermit_sync::InterruptTicketMutex;
4
5use crate::arch::core_local::*;
6use crate::scheduler::PerCoreSchedulerExt;
7use crate::scheduler::task::TaskHandlePriorityQueue;
8
9struct SemaphoreState {
10 /// Resource available count
11 count: isize,
12 /// Priority queue of waiting tasks
13 queue: TaskHandlePriorityQueue,
14}
15
16/// A counting, blocking, semaphore.
17///
18/// Semaphores are a form of atomic counter where access is only granted if the
19/// counter is a positive value. Each acquisition will block the calling thread
20/// until the counter is positive, and each release will increment the counter
21/// and unblock any threads if necessary.
22///
23/// # Examples
24///
25/// ```
26///
27/// // Create a semaphore that represents 5 resources
28/// let sem = Semaphore::new(5);
29///
30/// // Acquire one of the resources
31/// sem.acquire();
32///
33/// // Acquire one of the resources for a limited period of time
34/// {
35/// let _guard = sem.access();
36/// // ...
37/// } // resources is released here
38///
39/// // Release our initially acquired resource
40/// sem.release();
41///
42/// Interface is derived from https://doc.rust-lang.org/1.7.0/src/std/sync/semaphore.rs.html
43/// ```
44pub struct Semaphore {
45 state: InterruptTicketMutex<SemaphoreState>,
46}
47
48impl Semaphore {
49 /// Creates a new semaphore with the initial count specified.
50 ///
51 /// The count specified can be thought of as a number of resources, and a
52 /// call to `acquire` or `access` will block until at least one resource is
53 /// available. It is valid to initialize a semaphore with a negative count.
54 pub const fn new(count: isize) -> Self {
55 Self {
56 state: InterruptTicketMutex::new(SemaphoreState {
57 count,
58 queue: TaskHandlePriorityQueue::new(),
59 }),
60 }
61 }
62
63 /// Acquires a resource of this semaphore, blocking the current thread until
64 /// it can do so or until the wakeup time (in ms) has elapsed.
65 ///
66 /// This method will block until the internal count of the semaphore is at
67 /// least 1.
68 pub fn acquire(&self, time: Option<u64>) -> bool {
69 #[cfg(feature = "smp")]
70 let backoff = Backoff::new();
71 let core_scheduler = core_scheduler();
72
73 let wakeup_time = time.map(|ms| crate::arch::processor::get_timer_ticks() + ms * 1000);
74
75 // Loop until we have acquired the semaphore.
76 loop {
77 let mut locked_state = self.state.lock();
78
79 if locked_state.count > 0 {
80 // Successfully acquired the semaphore.
81 locked_state.count -= 1;
82 return true;
83 } else if let Some(t) = wakeup_time {
84 if t < crate::arch::processor::get_timer_ticks() {
85 // We could not acquire the semaphore and we were woken up because the wakeup time has elapsed.
86 // Don't try again and return the failure status.
87 locked_state
88 .queue
89 .remove(core_scheduler.get_current_task_handle());
90 return false;
91 }
92 }
93
94 #[cfg(feature = "smp")]
95 if backoff.is_completed() {
96 // We couldn't acquire the semaphore.
97 // Block the current task and add it to the wakeup queue.
98 core_scheduler.block_current_task(wakeup_time);
99 locked_state
100 .queue
101 .push(core_scheduler.get_current_task_handle());
102 drop(locked_state);
103 // Switch to the next task.
104 core_scheduler.reschedule();
105 } else {
106 drop(locked_state);
107 backoff.snooze();
108 }
109
110 #[cfg(not(feature = "smp"))]
111 {
112 // We couldn't acquire the semaphore.
113 // Block the current task and add it to the wakeup queue.
114 core_scheduler.block_current_task(wakeup_time);
115 locked_state
116 .queue
117 .push(core_scheduler.get_current_task_handle());
118 drop(locked_state);
119 // Switch to the next task.
120 core_scheduler.reschedule();
121 }
122 }
123 }
124
125 pub fn try_acquire(&self) -> bool {
126 let mut locked_state = self.state.lock();
127
128 if locked_state.count > 0 {
129 locked_state.count -= 1;
130 true
131 } else {
132 false
133 }
134 }
135
136 /// Release a resource from this semaphore.
137 ///
138 /// This will increment the number of resources in this semaphore by 1 and
139 /// will notify any pending waiters in `acquire` or `access` if necessary.
140 pub fn release(&self) {
141 if let Some(task) = {
142 let mut locked_state = self.state.lock();
143 locked_state.count += 1;
144 locked_state.queue.pop()
145 } {
146 // Wake up any task that has been waiting for this semaphore.
147 core_scheduler().custom_wakeup(task);
148 };
149 }
150}