/* * This is very similar to vmntvnodescan() but it only scans the * vnodes on the syncer list. VFS's which support faster VFS_SYNC * operations use the VISDIRTY flag on the vnode to ensure that vnodes * with dirty inodes are added to the syncer in addition to vnodes * with dirty buffers, and can use this function instead of nmntvnodescan(). * * This is important when a system has millions of vnodes. */ int vsyncscan( struct mount *mp, int vmsc_flags, int (*slowfunc)(struct mount *mp, struct vnode *vp, void *data), void *data ) { struct syncer_ctx *ctx; struct synclist *slp; struct vnode *vp; int b; int i; int lkflags; if (vmsc_flags & VMSC_NOWAIT) lkflags = LK_NOWAIT; else lkflags = 0; /* * Syncer list context. This API requires a dedicated syncer thread. * (MNTK_THR_SYNC). */ KKASSERT(mp->mnt_kern_flag & MNTK_THR_SYNC); ctx = mp->mnt_syncer_ctx; lwkt_gettoken(&ctx->sc_token); /* * Setup for loop. Allow races against the syncer thread but * require that the syncer thread no be lazy if we were told * not to be lazy. */ b = ctx->syncer_delayno & ctx->syncer_mask; i = b; if ((vmsc_flags & VMSC_NOWAIT) == 0) ++ctx->syncer_forced; do { slp = &ctx->syncer_workitem_pending[i]; while ((vp = LIST_FIRST(slp)) != NULL) { KKASSERT(vp->v_mount == mp); if (vmsc_flags & VMSC_GETVP) { if (vget(vp, LK_EXCLUSIVE | lkflags) == 0) { slowfunc(mp, vp, data); vput(vp); } } else if (vmsc_flags & VMSC_GETVX) { vx_get(vp); slowfunc(mp, vp, data); vx_put(vp); } else { vhold(vp); slowfunc(mp, vp, data); vdrop(vp); } if (LIST_FIRST(slp) == vp) vn_syncer_add(vp, -(i + syncdelay)); } i = (i + 1) & ctx->syncer_mask; } while (i != b); if ((vmsc_flags & VMSC_NOWAIT) == 0) --ctx->syncer_forced; lwkt_reltoken(&ctx->sc_token); return(0); }
/* * Get the vnode associated with the given inode, allocating the vnode if * necessary. The vnode will be returned exclusively locked. * * The caller must lock the inode (shared or exclusive). * * Great care must be taken to avoid deadlocks and vnode acquisition/reclaim * races. */ struct vnode * hammer2_igetv(hammer2_inode_t *ip, int *errorp) { hammer2_inode_data_t *ipdata; hammer2_pfsmount_t *pmp; struct vnode *vp; ccms_state_t ostate; pmp = ip->pmp; KKASSERT(pmp != NULL); *errorp = 0; ipdata = &ip->chain->data->ipdata; for (;;) { /* * Attempt to reuse an existing vnode assignment. It is * possible to race a reclaim so the vget() may fail. The * inode must be unlocked during the vget() to avoid a * deadlock against a reclaim. */ vp = ip->vp; if (vp) { /* * Inode must be unlocked during the vget() to avoid * possible deadlocks, but leave the ip ref intact. * * vnode is held to prevent destruction during the * vget(). The vget() can still fail if we lost * a reclaim race on the vnode. */ vhold(vp); ostate = hammer2_inode_lock_temp_release(ip); if (vget(vp, LK_EXCLUSIVE)) { vdrop(vp); hammer2_inode_lock_temp_restore(ip, ostate); continue; } hammer2_inode_lock_temp_restore(ip, ostate); vdrop(vp); /* vp still locked and ref from vget */ if (ip->vp != vp) { kprintf("hammer2: igetv race %p/%p\n", ip->vp, vp); vput(vp); continue; } *errorp = 0; break; } /* * No vnode exists, allocate a new vnode. Beware of * allocation races. This function will return an * exclusively locked and referenced vnode. */ *errorp = getnewvnode(VT_HAMMER2, pmp->mp, &vp, 0, 0); if (*errorp) { kprintf("hammer2: igetv getnewvnode failed %d\n", *errorp); vp = NULL; break; } /* * Lock the inode and check for an allocation race. */ ostate = hammer2_inode_lock_upgrade(ip); if (ip->vp != NULL) { vp->v_type = VBAD; vx_put(vp); hammer2_inode_lock_downgrade(ip, ostate); continue; } switch (ipdata->type) { case HAMMER2_OBJTYPE_DIRECTORY: vp->v_type = VDIR; break; case HAMMER2_OBJTYPE_REGFILE: vp->v_type = VREG; vinitvmio(vp, ipdata->size, HAMMER2_LBUFSIZE, (int)ipdata->size & HAMMER2_LBUFMASK); break; case HAMMER2_OBJTYPE_SOFTLINK: /* * XXX for now we are using the generic file_read * and file_write code so we need a buffer cache * association. */ vp->v_type = VLNK; vinitvmio(vp, ipdata->size, HAMMER2_LBUFSIZE, (int)ipdata->size & HAMMER2_LBUFMASK); break; case HAMMER2_OBJTYPE_CDEV: vp->v_type = VCHR; /* fall through */ case HAMMER2_OBJTYPE_BDEV: vp->v_ops = &pmp->mp->mnt_vn_spec_ops; if (ipdata->type != HAMMER2_OBJTYPE_CDEV) vp->v_type = VBLK; addaliasu(vp, ipdata->rmajor, ipdata->rminor); break; case HAMMER2_OBJTYPE_FIFO: vp->v_type = VFIFO; vp->v_ops = &pmp->mp->mnt_vn_fifo_ops; break; default: panic("hammer2: unhandled objtype %d", ipdata->type); break; } if (ip == pmp->iroot) vsetflags(vp, VROOT); vp->v_data = ip; ip->vp = vp; hammer2_inode_ref(ip); /* vp association */ hammer2_inode_lock_downgrade(ip, ostate); break; } /* * Return non-NULL vp and *errorp == 0, or NULL vp and *errorp != 0. */ if (hammer2_debug & 0x0002) { kprintf("igetv vp %p refs 0x%08x aux 0x%08x\n", vp, vp->v_refcnt, vp->v_auxrefs); } return (vp); }
/* * Try to reuse a vnode from the free list. This function is somewhat * advisory in that NULL can be returned as a normal case, even if free * vnodes are present. * * The scan is limited because it can result in excessive CPU use during * periods of extreme vnode use. * * NOTE: The returned vnode is not completely initialized. */ static struct vnode * cleanfreevnode(int maxcount) { struct vnode *vp; int count; int trigger = (long)vmstats.v_page_count / (activevnodes * 2 + 1); /* * Try to deactivate some vnodes cached on the active list. */ if (countcachedvnodes(0) < inactivevnodes) goto skip; for (count = 0; count < maxcount * 2; count++) { spin_lock(&vfs_spin); vp = TAILQ_NEXT(&vnode_active_rover, v_list); TAILQ_REMOVE(&vnode_active_list, &vnode_active_rover, v_list); if (vp == NULL) { TAILQ_INSERT_HEAD(&vnode_active_list, &vnode_active_rover, v_list); } else { TAILQ_INSERT_AFTER(&vnode_active_list, vp, &vnode_active_rover, v_list); } if (vp == NULL) { spin_unlock(&vfs_spin); continue; } if ((vp->v_refcnt & VREF_MASK) != 0) { spin_unlock(&vfs_spin); vp->v_act += VACT_INC; if (vp->v_act > VACT_MAX) /* SMP race ok */ vp->v_act = VACT_MAX; continue; } /* * decrement by less if the vnode's object has a lot of * VM pages. XXX possible SMP races. */ if (vp->v_act > 0) { vm_object_t obj; if ((obj = vp->v_object) != NULL && obj->resident_page_count >= trigger) { vp->v_act -= 1; } else { vp->v_act -= VACT_INC; } if (vp->v_act < 0) vp->v_act = 0; spin_unlock(&vfs_spin); continue; } /* * Try to deactivate the vnode. */ if ((atomic_fetchadd_int(&vp->v_refcnt, 1) & VREF_MASK) == 0) atomic_add_int(&mycpu->gd_cachedvnodes, -1); atomic_set_int(&vp->v_refcnt, VREF_FINALIZE); spin_unlock(&vfs_spin); vrele(vp); } skip: /* * Loop trying to lock the first vnode on the free list. * Cycle if we can't. */ for (count = 0; count < maxcount; count++) { spin_lock(&vfs_spin); vp = TAILQ_FIRST(&vnode_inactive_list); if (vp == NULL) { spin_unlock(&vfs_spin); break; } /* * non-blocking vx_get will also ref the vnode on success. */ if (vx_get_nonblock(vp)) { KKASSERT(vp->v_state == VS_INACTIVE); TAILQ_REMOVE(&vnode_inactive_list, vp, v_list); TAILQ_INSERT_TAIL(&vnode_inactive_list, vp, v_list); spin_unlock(&vfs_spin); continue; } /* * Because we are holding vfs_spin the vnode should currently * be inactive and VREF_TERMINATE should still be set. * * Once vfs_spin is released the vnode's state should remain * unmodified due to both the lock and ref on it. */ KKASSERT(vp->v_state == VS_INACTIVE); spin_unlock(&vfs_spin); #ifdef TRACKVNODE if ((u_long)vp == trackvnode) kprintf("cleanfreevnode %p %08x\n", vp, vp->v_flag); #endif /* * Do not reclaim/reuse a vnode while auxillary refs exists. * This includes namecache refs due to a related ncp being * locked or having children, a VM object association, or * other hold users. * * Do not reclaim/reuse a vnode if someone else has a real * ref on it. This can occur if a filesystem temporarily * releases the vnode lock during VOP_RECLAIM. */ if (vp->v_auxrefs || (vp->v_refcnt & ~VREF_FINALIZE) != VREF_TERMINATE + 1) { failed: if (vp->v_state == VS_INACTIVE) { spin_lock(&vfs_spin); if (vp->v_state == VS_INACTIVE) { TAILQ_REMOVE(&vnode_inactive_list, vp, v_list); TAILQ_INSERT_TAIL(&vnode_inactive_list, vp, v_list); } spin_unlock(&vfs_spin); } vx_put(vp); continue; } /* * VINACTIVE and VREF_TERMINATE are expected to both be set * for vnodes pulled from the inactive list, and cannot be * changed while we hold the vx lock. * * Try to reclaim the vnode. */ KKASSERT(vp->v_flag & VINACTIVE); KKASSERT(vp->v_refcnt & VREF_TERMINATE); if ((vp->v_flag & VRECLAIMED) == 0) { if (cache_inval_vp_nonblock(vp)) goto failed; vgone_vxlocked(vp); /* vnode is still VX locked */ } /* * At this point if there are no other refs or auxrefs on * the vnode with the inactive list locked, and we remove * the vnode from the inactive list, it should not be * possible for anyone else to access the vnode any more. * * Since the vnode is in a VRECLAIMED state, no new * namecache associations could have been made and the * vnode should have already been removed from its mountlist. * * Since we hold a VX lock on the vnode it cannot have been * reactivated (moved out of the inactive list). */ KKASSERT(TAILQ_EMPTY(&vp->v_namecache)); spin_lock(&vfs_spin); if (vp->v_auxrefs || (vp->v_refcnt & ~VREF_FINALIZE) != VREF_TERMINATE + 1) { spin_unlock(&vfs_spin); goto failed; } KKASSERT(vp->v_state == VS_INACTIVE); TAILQ_REMOVE(&vnode_inactive_list, vp, v_list); --inactivevnodes; vp->v_state = VS_DYING; spin_unlock(&vfs_spin); /* * Nothing should have been able to access this vp. Only * our ref should remain now. */ atomic_clear_int(&vp->v_refcnt, VREF_TERMINATE|VREF_FINALIZE); KASSERT(vp->v_refcnt == 1, ("vp %p badrefs %08x", vp, vp->v_refcnt)); /* * Return a VX locked vnode suitable for reuse. */ return(vp); } return(NULL); }