Home | History | Annotate | Download | only in os
      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  * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
     23  * Use is subject to license terms.
     24  */
     25 
     26 #include <sys/param.h>
     27 #include <sys/systm.h>
     28 #include <sys/thread.h>
     29 #include <sys/class.h>
     30 #include <sys/debug.h>
     31 #include <sys/cpuvar.h>
     32 #include <sys/waitq.h>
     33 #include <sys/cmn_err.h>
     34 #include <sys/time.h>
     35 #include <sys/dtrace.h>
     36 #include <sys/sdt.h>
     37 #include <sys/zone.h>
     38 
     39 /*
     40  * Wait queue implementation.
     41  */
     42 
     43 void
     44 waitq_init(waitq_t *wq)
     45 {
     46 	DISP_LOCK_INIT(&wq->wq_lock);
     47 	wq->wq_first = NULL;
     48 	wq->wq_count = 0;
     49 	wq->wq_blocked = B_TRUE;
     50 }
     51 
     52 void
     53 waitq_fini(waitq_t *wq)
     54 {
     55 	ASSERT(wq->wq_count == 0);
     56 	ASSERT(wq->wq_first == NULL);
     57 	ASSERT(wq->wq_blocked == B_TRUE);
     58 	ASSERT(!DISP_LOCK_HELD(&wq->wq_lock));
     59 
     60 	DISP_LOCK_DESTROY(&wq->wq_lock);
     61 }
     62 
     63 /*
     64  * Operations on waitq_t structures.
     65  *
     66  * A wait queue is a singly linked NULL-terminated list with doubly
     67  * linked circular sublists.  The singly linked list is in descending
     68  * priority order and FIFO for threads of the same priority.  It links
     69  * through the t_link field of the thread structure.  The doubly linked
     70  * sublists link threads of the same priority.  They use the t_priforw
     71  * and t_priback fields of the thread structure.
     72  *
     73  * Graphically (with priorities in parens):
     74  *
     75  *         ________________           _______                   _______
     76  *        /                \         /       \                 /       \
     77  *        |                |         |       |                 |       |
     78  *        v                v         v       v                 v       v
     79  *     t1(60)-->t2(60)-->t3(60)-->t4(50)-->t5(50)-->t6(30)-->t7(0)-->t8(0)
     80  *        ^      ^  ^      ^         ^       ^       ^  ^      ^       ^
     81  *        |      |  |      |         |       |       |  |      |       |
     82  *        \______/  \______/         \_______/       \__/      \_______/
     83  *
     84  * There are three interesting operations on a waitq list: inserting
     85  * a thread into the proper position according to priority; removing a
     86  * thread given a pointer to it; and walking the list, possibly
     87  * removing threads along the way.  This design allows all three
     88  * operations to be performed efficiently and easily.
     89  *
     90  * To insert a thread, traverse the list looking for the sublist of
     91  * the same priority as the thread (or one of a lower priority,
     92  * meaning there are no other threads in the list of the same
     93  * priority).  This can be done without touching all threads in the
     94  * list by following the links between the first threads in each
     95  * sublist.  Given a thread t that is the head of a sublist (the first
     96  * thread of that priority found when following the t_link pointers),
     97  * t->t_priback->t_link points to the head of the next sublist.  It's
     98  * important to do this since a waitq may contain thousands of
     99  * threads.
    100  *
    101  * Removing a thread from the list is also efficient.  First, the
    102  * t_waitq field contains a pointer to the waitq on which a thread
    103  * is waiting (or NULL if it's not on a waitq).  This is used to
    104  * determine if the given thread is on the given waitq without
    105  * searching the list.  Assuming it is, if it's not the head of a
    106  * sublist, just remove it from the sublist and use the t_priback
    107  * pointer to find the thread that points to it with t_link.  If it is
    108  * the head of a sublist, search for it by walking the sublist heads,
    109  * similar to searching for a given priority level when inserting a
    110  * thread.
    111  *
    112  * To walk the list, simply follow the t_link pointers.  Removing
    113  * threads along the way can be done easily if the code maintains a
    114  * pointer to the t_link field that pointed to the thread being
    115  * removed.
    116  */
    117 
    118 static void
    119 waitq_link(waitq_t *wq, kthread_t *t)
    120 {
    121 	kthread_t *next_tp;
    122 	kthread_t *last_tp;
    123 	kthread_t **tpp;
    124 	pri_t tpri, next_pri, last_pri = -1;
    125 
    126 	ASSERT(DISP_LOCK_HELD(&wq->wq_lock));
    127 
    128 	tpri = DISP_PRIO(t);
    129 	tpp = &wq->wq_first;
    130 	while ((next_tp = *tpp) != NULL) {
    131 		next_pri = DISP_PRIO(next_tp);
    132 		if (tpri > next_pri)
    133 			break;
    134 		last_tp = next_tp->t_priback;
    135 		last_pri = next_pri;
    136 		tpp = &last_tp->t_link;
    137 	}
    138 	*tpp = t;
    139 	t->t_link = next_tp;
    140 	if (last_pri == tpri) {
    141 		/* last_tp points to the last thread of this priority */
    142 		t->t_priback = last_tp;
    143 		t->t_priforw = last_tp->t_priforw;
    144 		last_tp->t_priforw->t_priback = t;
    145 		last_tp->t_priforw = t;
    146 	} else {
    147 		t->t_priback = t->t_priforw = t;
    148 	}
    149 	wq->wq_count++;
    150 	t->t_waitq = wq;
    151 }
    152 
    153 static void
    154 waitq_unlink(waitq_t *wq, kthread_t *t)
    155 {
    156 	kthread_t *nt;
    157 	kthread_t **ptl;
    158 
    159 	ASSERT(THREAD_LOCK_HELD(t));
    160 	ASSERT(DISP_LOCK_HELD(&wq->wq_lock));
    161 	ASSERT(t->t_waitq == wq);
    162 
    163 	ptl = &t->t_priback->t_link;
    164 	/*
    165 	 * Is it the head of a priority sublist?  If so, need to walk
    166 	 * the priorities to find the t_link pointer that points to it.
    167 	 */
    168 	if (*ptl != t) {
    169 		/*
    170 		 * Find the right priority level.
    171 		 */
    172 		ptl = &t->t_waitq->wq_first;
    173 		while ((nt = *ptl) != t)
    174 			ptl = &nt->t_priback->t_link;
    175 	}
    176 	/*
    177 	 * Remove thread from the t_link list.
    178 	 */
    179 	*ptl = t->t_link;
    180 
    181 	/*
    182 	 * Take it off the priority sublist if there's more than one
    183 	 * thread there.
    184 	 */
    185 	if (t->t_priforw != t) {
    186 		t->t_priback->t_priforw = t->t_priforw;
    187 		t->t_priforw->t_priback = t->t_priback;
    188 	}
    189 	t->t_link = NULL;
    190 
    191 	wq->wq_count--;
    192 	t->t_waitq = NULL;
    193 	t->t_priforw = NULL;
    194 	t->t_priback = NULL;
    195 }
    196 
    197 /*
    198  * Put specified thread to specified wait queue without dropping thread's lock.
    199  * Returns 1 if thread was successfully placed on project's wait queue, or
    200  * 0 if wait queue is blocked.
    201  */
    202 int
    203 waitq_enqueue(waitq_t *wq, kthread_t *t)
    204 {
    205 	ASSERT(THREAD_LOCK_HELD(t));
    206 	ASSERT(t->t_sleepq == NULL);
    207 	ASSERT(t->t_waitq == NULL);
    208 	ASSERT(t->t_link == NULL);
    209 
    210 	disp_lock_enter_high(&wq->wq_lock);
    211 
    212 	/*
    213 	 * Can't enqueue anything on a blocked wait queue
    214 	 */
    215 	if (wq->wq_blocked) {
    216 		disp_lock_exit_high(&wq->wq_lock);
    217 		return (0);
    218 	}
    219 
    220 	/*
    221 	 * Mark the time when thread is placed on wait queue. The microstate
    222 	 * accounting code uses this timestamp to determine wait times.
    223 	 */
    224 	t->t_waitrq = gethrtime_unscaled();
    225 
    226 	/*
    227 	 * Mark thread as not swappable.  If necessary, it will get
    228 	 * swapped out when it returns to the userland.
    229 	 */
    230 	t->t_schedflag |= TS_DONT_SWAP;
    231 	DTRACE_SCHED1(cpucaps__sleep, kthread_t *, t);
    232 	waitq_link(wq, t);
    233 
    234 	THREAD_WAIT(t, &wq->wq_lock);
    235 	return (1);
    236 }
    237 
    238 /*
    239  * Change thread's priority while on the wait queue.
    240  * Dequeue and equeue it again so that it gets placed in the right place.
    241  */
    242 void
    243 waitq_change_pri(kthread_t *t, pri_t new_pri)
    244 {
    245 	waitq_t *wq = t->t_waitq;
    246 
    247 	ASSERT(THREAD_LOCK_HELD(t));
    248 	ASSERT(ISWAITING(t));
    249 	ASSERT(wq != NULL);
    250 
    251 	waitq_unlink(wq, t);
    252 	t->t_pri = new_pri;
    253 	waitq_link(wq, t);
    254 }
    255 
    256 static void
    257 waitq_dequeue(waitq_t *wq, kthread_t *t)
    258 {
    259 	ASSERT(THREAD_LOCK_HELD(t));
    260 	ASSERT(t->t_waitq == wq);
    261 	ASSERT(ISWAITING(t));
    262 
    263 	waitq_unlink(wq, t);
    264 	DTRACE_SCHED1(cpucaps__wakeup, kthread_t *, t);
    265 
    266 	/*
    267 	 * Change thread to transition state and drop the wait queue lock. The
    268 	 * thread will remain locked since its t_lockp points to the
    269 	 * transition_lock.
    270 	 */
    271 	THREAD_TRANSITION(t);
    272 }
    273 
    274 /*
    275  * Return True iff there are any threads on the specified wait queue.
    276  * The check is done **without holding any locks**.
    277  */
    278 boolean_t
    279 waitq_isempty(waitq_t *wq)
    280 {
    281 	return (wq->wq_count == 0);
    282 }
    283 
    284 /*
    285  * Take thread off its wait queue and make it runnable.
    286  * Returns with thread lock held.
    287  */
    288 void
    289 waitq_setrun(kthread_t *t)
    290 {
    291 	waitq_t *wq = t->t_waitq;
    292 
    293 	ASSERT(THREAD_LOCK_HELD(t));
    294 
    295 	ASSERT(ISWAITING(t));
    296 	if (wq == NULL)
    297 		panic("waitq_setrun: thread %p is not on waitq", (void *)t);
    298 	waitq_dequeue(wq, t);
    299 	CL_SETRUN(t);
    300 }
    301 
    302 /*
    303  * Take the first thread off the wait queue and return pointer to it.
    304  */
    305 static kthread_t *
    306 waitq_takeone(waitq_t *wq)
    307 {
    308 	kthread_t *t;
    309 
    310 	disp_lock_enter(&wq->wq_lock);
    311 	/*
    312 	 * waitq_dequeue drops wait queue lock but leaves the CPU at high PIL.
    313 	 */
    314 	if ((t = wq->wq_first) != NULL)
    315 		waitq_dequeue(wq, wq->wq_first);
    316 	else
    317 		disp_lock_exit(&wq->wq_lock);
    318 	return (t);
    319 }
    320 
    321 /*
    322  * Take the first thread off the wait queue and make it runnable.
    323  * Return the pointer to the thread or NULL if waitq is empty
    324  */
    325 static kthread_t *
    326 waitq_runfirst(waitq_t *wq)
    327 {
    328 	kthread_t *t;
    329 
    330 	t = waitq_takeone(wq);
    331 	if (t != NULL) {
    332 		/*
    333 		 * t should have transition lock held.
    334 		 * CL_SETRUN() will replace it with dispq lock and keep it held.
    335 		 * thread_unlock() will drop dispq lock and restore PIL.
    336 		 */
    337 		ASSERT(THREAD_LOCK_HELD(t));
    338 		CL_SETRUN(t);
    339 		thread_unlock(t);
    340 	}
    341 	return (t);
    342 }
    343 
    344 /*
    345  * Take the first thread off the wait queue and make it runnable.
    346  */
    347 void
    348 waitq_runone(waitq_t *wq)
    349 {
    350 	(void) waitq_runfirst(wq);
    351 }
    352 
    353 /*
    354  * Take all threads off the wait queue and make them runnable.
    355  */
    356 static void
    357 waitq_runall(waitq_t *wq)
    358 {
    359 	while (waitq_runfirst(wq) != NULL)
    360 		;
    361 }
    362 
    363 /*
    364  * Prevent any new threads from entering wait queue and make all threads
    365  * currently on the wait queue runnable. After waitq_block() completion, no
    366  * threads should ever appear on the wait queue untill it is unblocked.
    367  */
    368 void
    369 waitq_block(waitq_t *wq)
    370 {
    371 	ASSERT(!wq->wq_blocked);
    372 	disp_lock_enter(&wq->wq_lock);
    373 	wq->wq_blocked = B_TRUE;
    374 	disp_lock_exit(&wq->wq_lock);
    375 	waitq_runall(wq);
    376 	ASSERT(waitq_isempty(wq));
    377 }
    378 
    379 /*
    380  * Allow threads to be placed on the wait queue.
    381  */
    382 void
    383 waitq_unblock(waitq_t *wq)
    384 {
    385 	disp_lock_enter(&wq->wq_lock);
    386 
    387 	ASSERT(waitq_isempty(wq));
    388 	ASSERT(wq->wq_blocked);
    389 
    390 	wq->wq_blocked = B_FALSE;
    391 
    392 	disp_lock_exit(&wq->wq_lock);
    393 }
    394