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}