xref: /illumos-gate/usr/src/lib/libc/port/threads/sema.c (revision 8119dad84d6416f13557b0ba8e2aaf9064cbcfd3)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License (the "License").
6  * You may not use this file except in compliance with the License.
7  *
8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9  * or http://www.opensolaris.org/os/licensing.
10  * See the License for the specific language governing permissions
11  * and limitations under the License.
12  *
13  * When distributing Covered Code, include this CDDL HEADER in each
14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15  * If applicable, add the following below this CDDL HEADER, with the
16  * fields enclosed by brackets "[]" replaced with your own identifying
17  * information: Portions Copyright [yyyy] [name of copyright owner]
18  *
19  * CDDL HEADER END
20  */
21 
22 /*
23  * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
24  * Use is subject to license terms.
25  * Copyright 2024 Oxide Computer Company
26  */
27 
28 #include "lint.h"
29 #include "thr_uberdata.h"
30 
31 static uint32_t _semvaluemax;
32 
33 /*
34  * Check to see if anyone is waiting for this semaphore.
35  */
36 #pragma weak _sema_held = sema_held
37 int
38 sema_held(sema_t *sp)
39 {
40 	return (sp->count == 0);
41 }
42 
43 #pragma weak _sema_init = sema_init
44 int
45 sema_init(sema_t *sp, unsigned int count, int type, void *arg __unused)
46 {
47 	if (_semvaluemax == 0)
48 		_semvaluemax = (uint32_t)_sysconf(_SC_SEM_VALUE_MAX);
49 	if ((type != USYNC_THREAD && type != USYNC_PROCESS) ||
50 	    (count > _semvaluemax))
51 		return (EINVAL);
52 	(void) memset(sp, 0, sizeof (*sp));
53 	sp->count = count;
54 	sp->type = (uint16_t)type;
55 	sp->magic = SEMA_MAGIC;
56 
57 	/*
58 	 * This should be at the beginning of the function,
59 	 * but for the sake of old broken applications that
60 	 * do not have proper alignment for their semaphores
61 	 * (and don't check the return code from sema_init),
62 	 * we put it here, after initializing the semaphore regardless.
63 	 */
64 	if (((uintptr_t)sp & (_LONG_LONG_ALIGNMENT - 1)) &&
65 	    curthread->ul_misaligned == 0)
66 		return (EINVAL);
67 
68 	return (0);
69 }
70 
71 #pragma weak _sema_destroy = sema_destroy
72 int
73 sema_destroy(sema_t *sp)
74 {
75 	sp->magic = 0;
76 	tdb_sync_obj_deregister(sp);
77 	return (0);
78 }
79 
80 static int
81 sema_wait_impl(sema_t *sp, timespec_t *tsp)
82 {
83 	lwp_sema_t *lsp = (lwp_sema_t *)sp;
84 	ulwp_t *self = curthread;
85 	uberdata_t *udp = self->ul_uberdata;
86 	tdb_sema_stats_t *ssp = SEMA_STATS(sp, udp);
87 	hrtime_t begin_sleep = 0;
88 	uint_t count;
89 	int error = 0;
90 
91 	/*
92 	 * All variations of sema_wait() are cancellation points.
93 	 */
94 	_cancelon();
95 
96 	if (ssp)
97 		tdb_incr(ssp->sema_wait);
98 
99 	self->ul_sp = stkptr();
100 	self->ul_wchan = lsp;
101 	if (__td_event_report(self, TD_SLEEP, udp)) {
102 		self->ul_td_evbuf.eventnum = TD_SLEEP;
103 		self->ul_td_evbuf.eventdata = lsp;
104 		tdb_event(TD_SLEEP, udp);
105 	}
106 	/* just a guess, but it looks like we will sleep */
107 	if (ssp && lsp->count == 0) {
108 		begin_sleep = gethrtime();
109 		if (lsp->count == 0)	/* still looks like sleep */
110 			tdb_incr(ssp->sema_wait_sleep);
111 		else			/* we changed our mind */
112 			begin_sleep = 0;
113 	}
114 
115 	if (lsp->type == USYNC_PROCESS) {		/* kernel-level */
116 		set_parking_flag(self, 1);
117 		if (self->ul_cursig != 0 ||
118 		    (self->ul_cancelable && self->ul_cancel_pending))
119 			set_parking_flag(self, 0);
120 		/* the kernel always does FIFO queueing */
121 		error = ___lwp_sema_timedwait(lsp, tsp, 1);
122 		set_parking_flag(self, 0);
123 	} else if (!udp->uberflags.uf_mt &&		/* single threaded */
124 	    lsp->count != 0) {				/* and non-blocking */
125 		/*
126 		 * Since we are single-threaded, we don't need the
127 		 * protection of queue_lock().  However, we do need
128 		 * to block signals while modifying the count.
129 		 */
130 		sigoff(self);
131 		lsp->count--;
132 		sigon(self);
133 	} else {				/* multithreaded or blocking */
134 		queue_head_t *qp;
135 		ulwp_t *ulwp;
136 		lwpid_t lwpid = 0;
137 
138 		qp = queue_lock(lsp, CV);
139 		while (error == 0 && lsp->count == 0) {
140 			/*
141 			 * SUSV3 requires FIFO queueing for semaphores,
142 			 * at least for SCHED_FIFO and SCHED_RR scheduling.
143 			 */
144 			enqueue(qp, self, 1);
145 			lsp->sema_waiters = 1;
146 			set_parking_flag(self, 1);
147 			queue_unlock(qp);
148 			/*
149 			 * We may have received SIGCANCEL before we
150 			 * called queue_lock().  If so and we are
151 			 * cancelable we should return EINTR.
152 			 */
153 			if (self->ul_cursig != 0 ||
154 			    (self->ul_cancelable && self->ul_cancel_pending))
155 				set_parking_flag(self, 0);
156 			error = __lwp_park(tsp, 0);
157 			set_parking_flag(self, 0);
158 			qp = queue_lock(lsp, CV);
159 			if (self->ul_sleepq)	/* timeout or spurious wakeup */
160 				lsp->sema_waiters = dequeue_self(qp);
161 		}
162 		if (error == 0)
163 			lsp->count--;
164 		if (lsp->count != 0 && lsp->sema_waiters) {
165 			int more;
166 			if ((ulwp = dequeue(qp, &more)) != NULL) {
167 				no_preempt(self);
168 				lwpid = ulwp->ul_lwpid;
169 			}
170 			lsp->sema_waiters = more;
171 		}
172 		queue_unlock(qp);
173 		if (lwpid) {
174 			(void) __lwp_unpark(lwpid);
175 			preempt(self);
176 		}
177 	}
178 
179 	self->ul_wchan = NULL;
180 	self->ul_sp = 0;
181 	if (ssp) {
182 		if (error == 0) {
183 			/* we just decremented the count */
184 			count = lsp->count;
185 			if (ssp->sema_min_count > count)
186 				ssp->sema_min_count = count;
187 		}
188 		if (begin_sleep)
189 			ssp->sema_wait_sleep_time += gethrtime() - begin_sleep;
190 	}
191 
192 	if (error == EINTR)
193 		_canceloff();
194 	else
195 		_canceloff_nocancel();
196 	return (error);
197 }
198 
199 #pragma weak _sema_wait = sema_wait
200 int
201 sema_wait(sema_t *sp)
202 {
203 	ASSERT(!curthread->ul_critical || curthread->ul_bindflags);
204 	return (sema_wait_impl(sp, NULL));
205 }
206 
207 /*
208  * sema_relcockwait() and sema_clockwait() are currently only internal to libc
209  * to aid with implementing the POSIX versions of these functions.
210  */
211 int
212 sema_relclockwait(sema_t *sp, clockid_t clock, const timespec_t *reltime)
213 {
214 	timespec_t tslocal = *reltime;
215 
216 	ASSERT(!curthread->ul_critical || curthread->ul_bindflags);
217 	switch (clock) {
218 	case CLOCK_REALTIME:
219 	case CLOCK_HIGHRES:
220 		break;
221 	default:
222 		return (EINVAL);
223 	}
224 	return (sema_wait_impl(sp, &tslocal));
225 }
226 
227 int
228 sema_clockwait(sema_t *sp, clockid_t clock, const timespec_t *abstime)
229 {
230 	timespec_t tslocal;
231 
232 	ASSERT(!curthread->ul_critical || curthread->ul_bindflags);
233 	switch (clock) {
234 	case CLOCK_REALTIME:
235 	case CLOCK_HIGHRES:
236 		break;
237 	default:
238 		return (EINVAL);
239 	}
240 	abstime_to_reltime(clock, abstime, &tslocal);
241 	return (sema_wait_impl(sp, &tslocal));
242 }
243 
244 int
245 sema_reltimedwait(sema_t *sp, const timespec_t *reltime)
246 {
247 	return (sema_relclockwait(sp, CLOCK_REALTIME, reltime));
248 }
249 
250 int
251 sema_timedwait(sema_t *sp, const timespec_t *abstime)
252 {
253 	return (sema_clockwait(sp, CLOCK_REALTIME, abstime));
254 }
255 
256 #pragma weak _sema_trywait = sema_trywait
257 int
258 sema_trywait(sema_t *sp)
259 {
260 	lwp_sema_t *lsp = (lwp_sema_t *)sp;
261 	ulwp_t *self = curthread;
262 	uberdata_t *udp = self->ul_uberdata;
263 	tdb_sema_stats_t *ssp = SEMA_STATS(sp, udp);
264 	uint_t count;
265 	int error = 0;
266 
267 	ASSERT(!curthread->ul_critical || curthread->ul_bindflags);
268 
269 	if (ssp)
270 		tdb_incr(ssp->sema_trywait);
271 
272 	if (lsp->type == USYNC_PROCESS) {	/* kernel-level */
273 		error = _lwp_sema_trywait(lsp);
274 	} else if (!udp->uberflags.uf_mt) {	/* single threaded */
275 		sigoff(self);
276 		if (lsp->count == 0)
277 			error = EBUSY;
278 		else
279 			lsp->count--;
280 		sigon(self);
281 	} else {				/* multithreaded */
282 		queue_head_t *qp;
283 		ulwp_t *ulwp;
284 		lwpid_t lwpid = 0;
285 
286 		qp = queue_lock(lsp, CV);
287 		if (lsp->count == 0)
288 			error = EBUSY;
289 		else if (--lsp->count != 0 && lsp->sema_waiters) {
290 			int more;
291 			if ((ulwp = dequeue(qp, &more)) != NULL) {
292 				no_preempt(self);
293 				lwpid = ulwp->ul_lwpid;
294 			}
295 			lsp->sema_waiters = more;
296 		}
297 		queue_unlock(qp);
298 		if (lwpid) {
299 			(void) __lwp_unpark(lwpid);
300 			preempt(self);
301 		}
302 	}
303 
304 	if (error == 0) {
305 		if (ssp) {
306 			/* we just decremented the count */
307 			count = lsp->count;
308 			if (ssp->sema_min_count > count)
309 				ssp->sema_min_count = count;
310 		}
311 	} else {
312 		if (ssp)
313 			tdb_incr(ssp->sema_trywait_fail);
314 		if (__td_event_report(self, TD_LOCK_TRY, udp)) {
315 			self->ul_td_evbuf.eventnum = TD_LOCK_TRY;
316 			tdb_event(TD_LOCK_TRY, udp);
317 		}
318 	}
319 
320 	return (error);
321 }
322 
323 #pragma weak _sema_post = sema_post
324 int
325 sema_post(sema_t *sp)
326 {
327 	lwp_sema_t *lsp = (lwp_sema_t *)sp;
328 	ulwp_t *self = curthread;
329 	uberdata_t *udp = self->ul_uberdata;
330 	tdb_sema_stats_t *ssp = SEMA_STATS(sp, udp);
331 	uint_t count;
332 	int error = 0;
333 
334 	if (ssp)
335 		tdb_incr(ssp->sema_post);
336 	if (_semvaluemax == 0)
337 		_semvaluemax = (uint32_t)_sysconf(_SC_SEM_VALUE_MAX);
338 
339 	if (lsp->type == USYNC_PROCESS) {	/* kernel-level */
340 		error = _lwp_sema_post(lsp);
341 	} else if (!udp->uberflags.uf_mt) {	/* single threaded */
342 		sigoff(self);
343 		if (lsp->count >= _semvaluemax)
344 			error = EOVERFLOW;
345 		else
346 			lsp->count++;
347 		sigon(self);
348 	} else {				/* multithreaded */
349 		queue_head_t *qp;
350 		ulwp_t *ulwp;
351 		lwpid_t lwpid = 0;
352 
353 		qp = queue_lock(lsp, CV);
354 		if (lsp->count >= _semvaluemax)
355 			error = EOVERFLOW;
356 		else if (lsp->count++ == 0 && lsp->sema_waiters) {
357 			int more;
358 			if ((ulwp = dequeue(qp, &more)) != NULL) {
359 				no_preempt(self);
360 				lwpid = ulwp->ul_lwpid;
361 			}
362 			lsp->sema_waiters = more;
363 		}
364 		queue_unlock(qp);
365 		if (lwpid) {
366 			(void) __lwp_unpark(lwpid);
367 			preempt(self);
368 		}
369 	}
370 
371 	if (error == 0) {
372 		if (ssp) {
373 			/* we just incremented the count */
374 			count = lsp->count;
375 			if (ssp->sema_max_count < count)
376 				ssp->sema_max_count = count;
377 		}
378 	}
379 
380 	return (error);
381 }
382