Home | History | Annotate | Line # | Download | only in kern
subr_localcount.c revision 1.7.2.2
      1  1.7.2.2  jdolecek /*	$NetBSD: subr_localcount.c,v 1.7.2.2 2017/12/03 11:38:45 jdolecek Exp $	*/
      2  1.7.2.2  jdolecek 
      3  1.7.2.2  jdolecek /*-
      4  1.7.2.2  jdolecek  * Copyright (c) 2016 The NetBSD Foundation, Inc.
      5  1.7.2.2  jdolecek  * All rights reserved.
      6  1.7.2.2  jdolecek  *
      7  1.7.2.2  jdolecek  * This code is derived from software contributed to The NetBSD Foundation
      8  1.7.2.2  jdolecek  * by Taylor R. Campbell.
      9  1.7.2.2  jdolecek  *
     10  1.7.2.2  jdolecek  * Redistribution and use in source and binary forms, with or without
     11  1.7.2.2  jdolecek  * modification, are permitted provided that the following conditions
     12  1.7.2.2  jdolecek  * are met:
     13  1.7.2.2  jdolecek  * 1. Redistributions of source code must retain the above copyright
     14  1.7.2.2  jdolecek  *    notice, this list of conditions and the following disclaimer.
     15  1.7.2.2  jdolecek  * 2. Redistributions in binary form must reproduce the above copyright
     16  1.7.2.2  jdolecek  *    notice, this list of conditions and the following disclaimer in the
     17  1.7.2.2  jdolecek  *    documentation and/or other materials provided with the distribution.
     18  1.7.2.2  jdolecek  *
     19  1.7.2.2  jdolecek  * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
     20  1.7.2.2  jdolecek  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
     21  1.7.2.2  jdolecek  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
     22  1.7.2.2  jdolecek  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
     23  1.7.2.2  jdolecek  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
     24  1.7.2.2  jdolecek  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
     25  1.7.2.2  jdolecek  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
     26  1.7.2.2  jdolecek  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
     27  1.7.2.2  jdolecek  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
     28  1.7.2.2  jdolecek  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
     29  1.7.2.2  jdolecek  * POSSIBILITY OF SUCH DAMAGE.
     30  1.7.2.2  jdolecek  */
     31  1.7.2.2  jdolecek 
     32  1.7.2.2  jdolecek /*
     33  1.7.2.2  jdolecek  * CPU-local reference counts
     34  1.7.2.2  jdolecek  *
     35  1.7.2.2  jdolecek  *	localcount(9) is a reference-counting scheme that involves no
     36  1.7.2.2  jdolecek  *	interprocessor synchronization most of the time, at the cost of
     37  1.7.2.2  jdolecek  *	eight bytes of memory per CPU per object and at the cost of
     38  1.7.2.2  jdolecek  *	expensive interprocessor synchronization to drain references.
     39  1.7.2.2  jdolecek  *
     40  1.7.2.2  jdolecek  *	localcount(9) references may be held across sleeps, may be
     41  1.7.2.2  jdolecek  *	transferred from CPU to CPU or thread to thread: they behave
     42  1.7.2.2  jdolecek  *	semantically like typical reference counts, with different
     43  1.7.2.2  jdolecek  *	pragmatic performance characteristics.
     44  1.7.2.2  jdolecek  */
     45  1.7.2.2  jdolecek 
     46  1.7.2.2  jdolecek #include <sys/cdefs.h>
     47  1.7.2.2  jdolecek __KERNEL_RCSID(0, "$NetBSD: subr_localcount.c,v 1.7.2.2 2017/12/03 11:38:45 jdolecek Exp $");
     48  1.7.2.2  jdolecek 
     49  1.7.2.2  jdolecek #include <sys/param.h>
     50  1.7.2.2  jdolecek #include <sys/localcount.h>
     51  1.7.2.2  jdolecek #include <sys/types.h>
     52  1.7.2.2  jdolecek #include <sys/condvar.h>
     53  1.7.2.2  jdolecek #include <sys/errno.h>
     54  1.7.2.2  jdolecek #include <sys/mutex.h>
     55  1.7.2.2  jdolecek #include <sys/percpu.h>
     56  1.7.2.2  jdolecek #include <sys/xcall.h>
     57  1.7.2.2  jdolecek #if defined(DEBUG) && defined(LOCKDEBUG)
     58  1.7.2.2  jdolecek #include <sys/atomic.h>
     59  1.7.2.2  jdolecek #endif
     60  1.7.2.2  jdolecek 
     61  1.7.2.2  jdolecek static void localcount_xc(void *, void *);
     62  1.7.2.2  jdolecek 
     63  1.7.2.2  jdolecek /*
     64  1.7.2.2  jdolecek  * localcount_init(lc)
     65  1.7.2.2  jdolecek  *
     66  1.7.2.2  jdolecek  *	Initialize a localcount object.  Returns 0 on success, error
     67  1.7.2.2  jdolecek  *	code on failure.  May fail to allocate memory for percpu(9).
     68  1.7.2.2  jdolecek  *
     69  1.7.2.2  jdolecek  *	The caller must call localcount_drain and then localcount_fini
     70  1.7.2.2  jdolecek  *	when done with lc.
     71  1.7.2.2  jdolecek  */
     72  1.7.2.2  jdolecek void
     73  1.7.2.2  jdolecek localcount_init(struct localcount *lc)
     74  1.7.2.2  jdolecek {
     75  1.7.2.2  jdolecek 
     76  1.7.2.2  jdolecek 	lc->lc_totalp = NULL;
     77  1.7.2.2  jdolecek 	lc->lc_percpu = percpu_alloc(sizeof(int64_t));
     78  1.7.2.2  jdolecek }
     79  1.7.2.2  jdolecek 
     80  1.7.2.2  jdolecek /*
     81  1.7.2.2  jdolecek  * localcount_drain(lc, cv, interlock)
     82  1.7.2.2  jdolecek  *
     83  1.7.2.2  jdolecek  *	Wait for all acquired references to lc to drain.  Caller must
     84  1.7.2.2  jdolecek  *	hold interlock; localcount_drain releases it during cross-calls
     85  1.7.2.2  jdolecek  *	and waits on cv.  The cv and interlock passed here must be the
     86  1.7.2.2  jdolecek  *	same as are passed to localcount_release for this lc.
     87  1.7.2.2  jdolecek  *
     88  1.7.2.2  jdolecek  *	Caller must guarantee that no new references can be acquired
     89  1.7.2.2  jdolecek  *	with localcount_acquire before calling localcount_drain.  For
     90  1.7.2.2  jdolecek  *	example, any object that may be found in a list and acquired
     91  1.7.2.2  jdolecek  *	must be removed from the list before localcount_drain.
     92  1.7.2.2  jdolecek  *
     93  1.7.2.2  jdolecek  *	The localcount object lc may be used only with localcount_fini
     94  1.7.2.2  jdolecek  *	after this, unless reinitialized after localcount_fini with
     95  1.7.2.2  jdolecek  *	localcount_init.
     96  1.7.2.2  jdolecek  */
     97  1.7.2.2  jdolecek void
     98  1.7.2.2  jdolecek localcount_drain(struct localcount *lc, kcondvar_t *cv, kmutex_t *interlock)
     99  1.7.2.2  jdolecek {
    100  1.7.2.2  jdolecek 	int64_t total = 0;
    101  1.7.2.2  jdolecek 
    102  1.7.2.2  jdolecek 	KASSERT(mutex_owned(interlock));
    103  1.7.2.2  jdolecek 	KASSERT(lc->lc_totalp == NULL);
    104  1.7.2.2  jdolecek 
    105  1.7.2.2  jdolecek 	/* Mark it draining.  */
    106  1.7.2.2  jdolecek 	lc->lc_totalp = &total;
    107  1.7.2.2  jdolecek 
    108  1.7.2.2  jdolecek 	/*
    109  1.7.2.2  jdolecek 	 * Count up all references on all CPUs.
    110  1.7.2.2  jdolecek 	 *
    111  1.7.2.2  jdolecek 	 * This serves as a global memory barrier: after xc_wait, all
    112  1.7.2.2  jdolecek 	 * CPUs will have witnessed the nonnull value of lc->lc_totalp,
    113  1.7.2.2  jdolecek 	 * so that it is safe to wait on the cv for them.
    114  1.7.2.2  jdolecek 	 */
    115  1.7.2.2  jdolecek 	mutex_exit(interlock);
    116  1.7.2.2  jdolecek 	xc_wait(xc_broadcast(0, &localcount_xc, lc, interlock));
    117  1.7.2.2  jdolecek 	mutex_enter(interlock);
    118  1.7.2.2  jdolecek 
    119  1.7.2.2  jdolecek 	/* Wait for remaining references to drain.  */
    120  1.7.2.2  jdolecek 	while (total != 0) {
    121  1.7.2.2  jdolecek 		/*
    122  1.7.2.2  jdolecek 		 * At this point, now that we have added up all
    123  1.7.2.2  jdolecek 		 * references on all CPUs, the total had better be
    124  1.7.2.2  jdolecek 		 * nonnegative.
    125  1.7.2.2  jdolecek 		 */
    126  1.7.2.2  jdolecek 		KASSERTMSG((0 < total),
    127  1.7.2.2  jdolecek 		    "negatively referenced localcount: %p, %"PRId64,
    128  1.7.2.2  jdolecek 		    lc, total);
    129  1.7.2.2  jdolecek 		cv_wait(cv, interlock);
    130  1.7.2.2  jdolecek 	}
    131  1.7.2.2  jdolecek 
    132  1.7.2.2  jdolecek 	/* Paranoia: Cause any further use of lc->lc_totalp to crash.  */
    133  1.7.2.2  jdolecek 	lc->lc_totalp = (void *)(uintptr_t)1;
    134  1.7.2.2  jdolecek }
    135  1.7.2.2  jdolecek 
    136  1.7.2.2  jdolecek /*
    137  1.7.2.2  jdolecek  * localcount_fini(lc)
    138  1.7.2.2  jdolecek  *
    139  1.7.2.2  jdolecek  *	Finalize a localcount object, releasing any memory allocated
    140  1.7.2.2  jdolecek  *	for it.  The localcount object must already have been drained.
    141  1.7.2.2  jdolecek  */
    142  1.7.2.2  jdolecek void
    143  1.7.2.2  jdolecek localcount_fini(struct localcount *lc)
    144  1.7.2.2  jdolecek {
    145  1.7.2.2  jdolecek 
    146  1.7.2.2  jdolecek 	KASSERT(lc->lc_totalp == (void *)(uintptr_t)1);
    147  1.7.2.2  jdolecek 	percpu_free(lc->lc_percpu, sizeof(uint64_t));
    148  1.7.2.2  jdolecek }
    149  1.7.2.2  jdolecek 
    150  1.7.2.2  jdolecek /*
    151  1.7.2.2  jdolecek  * localcount_xc(cookie0, cookie1)
    152  1.7.2.2  jdolecek  *
    153  1.7.2.2  jdolecek  *	Accumulate and transfer the per-CPU reference counts to a
    154  1.7.2.2  jdolecek  *	global total, resetting the per-CPU counter to zero.  Once
    155  1.7.2.2  jdolecek  *	localcount_drain() has started, we only maintain the total
    156  1.7.2.2  jdolecek  *	count in localcount_release().
    157  1.7.2.2  jdolecek  */
    158  1.7.2.2  jdolecek static void
    159  1.7.2.2  jdolecek localcount_xc(void *cookie0, void *cookie1)
    160  1.7.2.2  jdolecek {
    161  1.7.2.2  jdolecek 	struct localcount *lc = cookie0;
    162  1.7.2.2  jdolecek 	kmutex_t *interlock = cookie1;
    163  1.7.2.2  jdolecek 	int64_t *localp;
    164  1.7.2.2  jdolecek 
    165  1.7.2.2  jdolecek 	mutex_enter(interlock);
    166  1.7.2.2  jdolecek 	localp = percpu_getref(lc->lc_percpu);
    167  1.7.2.2  jdolecek 	*lc->lc_totalp += *localp;
    168  1.7.2.2  jdolecek 	*localp -= *localp;		/* ie, *localp = 0; */
    169  1.7.2.2  jdolecek 	percpu_putref(lc->lc_percpu);
    170  1.7.2.2  jdolecek 	mutex_exit(interlock);
    171  1.7.2.2  jdolecek }
    172  1.7.2.2  jdolecek 
    173  1.7.2.2  jdolecek /*
    174  1.7.2.2  jdolecek  * localcount_adjust(lc, delta)
    175  1.7.2.2  jdolecek  *
    176  1.7.2.2  jdolecek  *	Add delta -- positive or negative -- to the local CPU's count
    177  1.7.2.2  jdolecek  *	for lc.
    178  1.7.2.2  jdolecek  */
    179  1.7.2.2  jdolecek static void
    180  1.7.2.2  jdolecek localcount_adjust(struct localcount *lc, int delta)
    181  1.7.2.2  jdolecek {
    182  1.7.2.2  jdolecek 	int64_t *localp;
    183  1.7.2.2  jdolecek 
    184  1.7.2.2  jdolecek 	localp = percpu_getref(lc->lc_percpu);
    185  1.7.2.2  jdolecek 	*localp += delta;
    186  1.7.2.2  jdolecek 	percpu_putref(lc->lc_percpu);
    187  1.7.2.2  jdolecek }
    188  1.7.2.2  jdolecek 
    189  1.7.2.2  jdolecek /*
    190  1.7.2.2  jdolecek  * localcount_acquire(lc)
    191  1.7.2.2  jdolecek  *
    192  1.7.2.2  jdolecek  *	Acquire a reference to lc.
    193  1.7.2.2  jdolecek  *
    194  1.7.2.2  jdolecek  *	The reference may be held across sleeps and may be migrated
    195  1.7.2.2  jdolecek  *	from CPU to CPU, or even thread to thread -- it is only
    196  1.7.2.2  jdolecek  *	counted, not associated with a particular concrete owner.
    197  1.7.2.2  jdolecek  *
    198  1.7.2.2  jdolecek  *	Involves no interprocessor synchronization.  May be used in any
    199  1.7.2.2  jdolecek  *	context: while a lock is held, within a pserialize(9) read
    200  1.7.2.2  jdolecek  *	section, in hard interrupt context (provided other users block
    201  1.7.2.2  jdolecek  *	hard interrupts), in soft interrupt context, in thread context,
    202  1.7.2.2  jdolecek  *	&c.
    203  1.7.2.2  jdolecek  *
    204  1.7.2.2  jdolecek  *	Caller must guarantee that there is no concurrent
    205  1.7.2.2  jdolecek  *	localcount_drain.  For example, any object that may be found in
    206  1.7.2.2  jdolecek  *	a list and acquired must be removed from the list before
    207  1.7.2.2  jdolecek  *	localcount_drain.
    208  1.7.2.2  jdolecek  */
    209  1.7.2.2  jdolecek void
    210  1.7.2.2  jdolecek localcount_acquire(struct localcount *lc)
    211  1.7.2.2  jdolecek {
    212  1.7.2.2  jdolecek 
    213  1.7.2.2  jdolecek 	KASSERT(lc->lc_totalp == NULL);
    214  1.7.2.2  jdolecek 	localcount_adjust(lc, +1);
    215  1.7.2.2  jdolecek #if defined(DEBUG) && defined(LOCKDEBUG)
    216  1.7.2.2  jdolecek 	if (atomic_inc_32_nv(&lc->lc_refcnt) == 0)
    217  1.7.2.2  jdolecek 		panic("counter overflow");
    218  1.7.2.2  jdolecek #endif
    219  1.7.2.2  jdolecek }
    220  1.7.2.2  jdolecek 
    221  1.7.2.2  jdolecek /*
    222  1.7.2.2  jdolecek  * localcount_release(lc, cv, interlock)
    223  1.7.2.2  jdolecek  *
    224  1.7.2.2  jdolecek  *	Release a reference to lc.  If there is a concurrent
    225  1.7.2.2  jdolecek  *	localcount_drain and this may be the last reference, notify
    226  1.7.2.2  jdolecek  *	localcount_drain by acquiring interlock, waking cv, and
    227  1.7.2.2  jdolecek  *	releasing interlock.  The cv and interlock passed here must be
    228  1.7.2.2  jdolecek  *	the same as are passed to localcount_drain for this lc.
    229  1.7.2.2  jdolecek  *
    230  1.7.2.2  jdolecek  *	Involves no interprocessor synchronization unless there is a
    231  1.7.2.2  jdolecek  *	concurrent localcount_drain in progress.
    232  1.7.2.2  jdolecek  */
    233  1.7.2.2  jdolecek void
    234  1.7.2.2  jdolecek localcount_release(struct localcount *lc, kcondvar_t *cv, kmutex_t *interlock)
    235  1.7.2.2  jdolecek {
    236  1.7.2.2  jdolecek 
    237  1.7.2.2  jdolecek 	/*
    238  1.7.2.2  jdolecek 	 * Block xcall so that if someone begins draining after we see
    239  1.7.2.2  jdolecek 	 * lc->lc_totalp as null, then they won't start cv_wait until
    240  1.7.2.2  jdolecek 	 * after they have counted this CPU's contributions.
    241  1.7.2.2  jdolecek 	 *
    242  1.7.2.2  jdolecek 	 * Otherwise, localcount_drain may notice an extant reference
    243  1.7.2.2  jdolecek 	 * from this CPU and cv_wait for it, but having seen
    244  1.7.2.2  jdolecek 	 * lc->lc_totalp as null, this CPU will not wake
    245  1.7.2.2  jdolecek 	 * localcount_drain.
    246  1.7.2.2  jdolecek 	 */
    247  1.7.2.2  jdolecek 	kpreempt_disable();
    248  1.7.2.2  jdolecek 
    249  1.7.2.2  jdolecek 	KDASSERT(mutex_ownable(interlock));
    250  1.7.2.2  jdolecek 	if (__predict_false(lc->lc_totalp != NULL)) {
    251  1.7.2.2  jdolecek 		/*
    252  1.7.2.2  jdolecek 		 * Slow path -- wake localcount_drain in case this is
    253  1.7.2.2  jdolecek 		 * the last reference.
    254  1.7.2.2  jdolecek 		 */
    255  1.7.2.2  jdolecek 		mutex_enter(interlock);
    256  1.7.2.2  jdolecek 		if (--*lc->lc_totalp == 0)
    257  1.7.2.2  jdolecek 			cv_broadcast(cv);
    258  1.7.2.2  jdolecek 		mutex_exit(interlock);
    259  1.7.2.2  jdolecek 		goto out;
    260  1.7.2.2  jdolecek 	}
    261  1.7.2.2  jdolecek 
    262  1.7.2.2  jdolecek 	localcount_adjust(lc, -1);
    263  1.7.2.2  jdolecek #if defined(DEBUG) && defined(LOCKDEBUG)
    264  1.7.2.2  jdolecek 	if (atomic_dec_32_nv(&lc->lc_refcnt) == UINT_MAX)
    265  1.7.2.2  jdolecek 		panic("counter underflow");
    266  1.7.2.2  jdolecek #endif
    267  1.7.2.2  jdolecek  out:	kpreempt_enable();
    268  1.7.2.2  jdolecek }
    269  1.7.2.2  jdolecek 
    270  1.7.2.2  jdolecek /*
    271  1.7.2.2  jdolecek  * localcount_debug_refcnt(lc)
    272  1.7.2.2  jdolecek  *
    273  1.7.2.2  jdolecek  *	Return a total reference count of lc.  It returns a correct value
    274  1.7.2.2  jdolecek  *	only if DEBUG and LOCKDEBUG enabled.  Otherwise always return 0.
    275  1.7.2.2  jdolecek  */
    276  1.7.2.2  jdolecek uint32_t
    277  1.7.2.2  jdolecek localcount_debug_refcnt(const struct localcount *lc)
    278  1.7.2.2  jdolecek {
    279  1.7.2.2  jdolecek 
    280  1.7.2.2  jdolecek #if defined(DEBUG) && defined(LOCKDEBUG)
    281  1.7.2.2  jdolecek 	return lc->lc_refcnt;
    282  1.7.2.2  jdolecek #else
    283  1.7.2.2  jdolecek 	return 0;
    284  1.7.2.2  jdolecek #endif
    285  1.7.2.2  jdolecek }
    286