Index: sys/kern/vfs_subr.c =================================================================== --- sys/kern/vfs_subr.c +++ sys/kern/vfs_subr.c @@ -2798,11 +2798,32 @@ /* * Decrement si_usecount of the associated device, if any. + * + * The caller is required to hold the interlock across transitioning use count + * to 0 and calling this function in order to prevent a race against + * devfs_reclaim_vchr (called by vgone). + * + * The race is: + * + * CPU1 CPU2 + * devfs_reclaim_vchr + * make v_usecount 0 + * VI_LOCK + * sees v_usecount == 0, no updates + * vp->v_rdev = NULL; + * ... + * VI_UNLOCK + * VI_LOCK + * v_decr_devcount + * sees v_rdev == NULL, no updates + * + * In this scenario si_devcount decrement is not performed. */ static void v_decr_devcount(struct vnode *vp) { + ASSERT_VOP_LOCKED(vp, __func__); ASSERT_VI_LOCKED(vp, __FUNCTION__); if (vp->v_type == VCHR && vp->v_rdev != NULL) { dev_lock(); @@ -3157,9 +3178,18 @@ enum vputx_op { VPUTX_VRELE, VPUTX_VPUT, VPUTX_VUNREF }; /* - * Decrement the use and hold counts for a vnode. + * Handle ->v_usecount transitioning to 0. * - * See an explanation near vget() as to why atomic operation is safe. + * By releasing the last usecount we take ownership of the hold count which + * provides liveness of the vnode, meaning we have to vdrop. + * + * If the vnode is of type VCHR we may need to decrement si_usecount, see + * v_decr_devcount for details. + * + * For all vnodes we may need to perform inactive processing. It requires an + * exclusive lock on the vnode, while it is legal to call here with only a + * shared lock (or no locks). If locking the vnode in an expected manner fails, + * inactive processing gets deferred to the syncer. * * XXX Some filesystems pass in an exclusively locked vnode and strongly depend * on the lock being held all the way until VOP_INACTIVE. This in particular @@ -3172,64 +3202,38 @@ int error; bool want_unlock; - KASSERT(vp != NULL, ("vputx: null vp")); - if (func == VPUTX_VUNREF) - ASSERT_VOP_LOCKED(vp, "vunref"); - else if (func == VPUTX_VPUT) - ASSERT_VOP_LOCKED(vp, "vput"); - ASSERT_VI_UNLOCKED(vp, __func__); - VNASSERT(vp->v_holdcnt > 0 && vp->v_usecount > 0, vp, - ("%s: wrong ref counts", __func__)); - CTR2(KTR_VFS, "%s: vp %p", __func__, vp); + VNPASS(vp->v_holdcnt > 0, vp); + + VI_LOCK(vp); + if (func != VPUTX_VRELE) + v_decr_devcount(vp); - /* - * We want to hold the vnode until the inactive finishes to - * prevent vgone() races. We drop the use count here and the - * hold count below when we're done. - * - * If we release the last usecount we take ownership of the hold - * count which provides liveness of the vnode, in which case we - * have to vdrop. - */ - if (__predict_false(vp->v_type == VCHR && func == VPUTX_VRELE)) { - if (refcount_release_if_not_last(&vp->v_usecount)) - return; - VI_LOCK(vp); - if (!refcount_release(&vp->v_usecount)) { - VI_UNLOCK(vp); - return; - } - } else { - if (!refcount_release(&vp->v_usecount)) { - if (func == VPUTX_VPUT) - VOP_UNLOCK(vp); - return; - } - VI_LOCK(vp); - } - v_decr_devcount(vp); /* * By the time we got here someone else might have transitioned * the count back to > 0. */ - if (vp->v_usecount > 0 || vp->v_iflag & VI_DOINGINACT) + if (vp->v_usecount > 0) goto out; /* - * Check if the fs wants to perform inactive processing. Note we - * may be only holding the interlock, in which case it is possible - * someone else called vgone on the vnode and ->v_data is now NULL. - * Since vgone performs inactive on its own there is nothing to do - * here but to drop our hold count. + * If the vnode is doomed vgone already performed inactive processing + * (if needed). */ - if (__predict_false(VN_IS_DOOMED(vp)) || - VOP_NEED_INACTIVE(vp) == 0) + if (VN_IS_DOOMED(vp)) + goto out; + + if (__predict_true(VOP_NEED_INACTIVE(vp) == 0)) + goto out; + + if (vp->v_iflag & VI_DOINGINACT) goto out; /* - * We must call VOP_INACTIVE with the node locked. Mark - * as VI_DOINGINACT to avoid recursion. + * Locking operations here will drop the interlock and possibly the + * vnode lock, opening a window where the vnode can get doomed all the + * while ->v_usecount is 0. Set VI_OWEINACT to let vgone know to + * perform inactive. */ vp->v_iflag |= VI_OWEINACT; want_unlock = false; @@ -3286,35 +3290,77 @@ } /* - * Vnode put/release. - * If count drops to zero, call inactive routine and return to freelist. + * Decrement ->v_usecount for a vnode. + * + * Releasing the last use count requires additional processing, see vputx above + * for details. + * + * Comment above each variant denotes lock state on entry and exit. + */ + +static void __noinline +vrele_vchr(struct vnode *vp) +{ + + if (refcount_release_if_not_last(&vp->v_usecount)) + return; + VI_LOCK(vp); + if (!refcount_release(&vp->v_usecount)) { + VI_UNLOCK(vp); + return; + } + v_decr_devcount(vp); + VI_UNLOCK(vp); + vputx(vp, VPUTX_VRELE); +} + +/* + * in: any + * out: same as passed in */ void vrele(struct vnode *vp) { + ASSERT_VI_UNLOCKED(vp, __func__); + if (__predict_false(vp->v_type == VCHR)) { + vrele_vchr(vp); + return; + } + if (!refcount_release(&vp->v_usecount)) + return; vputx(vp, VPUTX_VRELE); } /* - * Release an already locked vnode. This give the same effects as - * unlock+vrele(), but takes less time and avoids releasing and - * re-aquiring the lock (as vrele() acquires the lock internally.) + * in: locked + * out: unlocked */ void vput(struct vnode *vp) { + ASSERT_VOP_LOCKED(vp, __func__); + ASSERT_VI_UNLOCKED(vp, __func__); + if (!refcount_release(&vp->v_usecount)) { + VOP_UNLOCK(vp); + return; + } vputx(vp, VPUTX_VPUT); } /* - * Release an exclusively locked vnode. Do not unlock the vnode lock. + * in: locked + * out: locked */ void vunref(struct vnode *vp) { + ASSERT_VOP_LOCKED(vp, __func__); + ASSERT_VI_UNLOCKED(vp, __func__); + if (!refcount_release(&vp->v_usecount)) + return; vputx(vp, VPUTX_VUNREF); }