Rework the tmpfs unmount.
- Suspend filesystem for unmount. This prevents new tmpfs nodes from instantiating, and also ensures that only unmount thread can destroy nodes. - Do not start tmpfs node deletion until all vnodes are reclaimed, which guarantees that no thread can access tmpfs data. For this, call vflush() in the loop, until the mnt_nvnodelistsize is non-zero. Note that after mnt_nvnodelistsize becomes 0, insmntque() blocks insertion of a vnode germ into the mount list of vnodes. - Fail node allocation when the filesystem is being unmounted. This is race-free due to the vflush() call in loop. This is mostly cosmetic, avoiding some more work which might be done until suspension in unmount is started. Note that there is currently no way to prevent new vnode instantiation from readers during the unmount. Due to this, forced unmount might live-lock if vflush() loop cannot get to the zero vnode count due to races with readers. The unmount would proceed after the load is lifted. Tested by: pho Sponsored by: The FreeBSD Foundation MFC after: 2 weeks
This commit is contained in:
parent
b5b3326191
commit
4cda7f7ece
@ -384,7 +384,7 @@ struct tmpfs_fid {
|
||||
* Prototypes for tmpfs_subr.c.
|
||||
*/
|
||||
|
||||
int tmpfs_alloc_node(struct tmpfs_mount *, enum vtype,
|
||||
int tmpfs_alloc_node(struct mount *mp, struct tmpfs_mount *, enum vtype,
|
||||
uid_t uid, gid_t gid, mode_t mode, struct tmpfs_node *,
|
||||
char *, dev_t, struct tmpfs_node **);
|
||||
void tmpfs_free_node(struct tmpfs_mount *, struct tmpfs_node *);
|
||||
|
@ -159,7 +159,7 @@ tmpfs_pages_check_avail(struct tmpfs_mount *tmp, size_t req_pages)
|
||||
* Returns zero on success or an appropriate error code on failure.
|
||||
*/
|
||||
int
|
||||
tmpfs_alloc_node(struct tmpfs_mount *tmp, enum vtype type,
|
||||
tmpfs_alloc_node(struct mount *mp, struct tmpfs_mount *tmp, enum vtype type,
|
||||
uid_t uid, gid_t gid, mode_t mode, struct tmpfs_node *parent,
|
||||
char *target, dev_t rdev, struct tmpfs_node **node)
|
||||
{
|
||||
@ -169,6 +169,8 @@ tmpfs_alloc_node(struct tmpfs_mount *tmp, enum vtype type,
|
||||
/* If the root directory of the 'tmp' file system is not yet
|
||||
* allocated, this must be the request to do it. */
|
||||
MPASS(IMPLIES(tmp->tm_root == NULL, parent == NULL && type == VDIR));
|
||||
KASSERT(tmp->tm_root == NULL || mp->mnt_writeopcount > 0,
|
||||
("creating node not under vn_start_write"));
|
||||
|
||||
MPASS(IFF(type == VLNK, target != NULL));
|
||||
MPASS(IFF(type == VBLK || type == VCHR, rdev != VNOVAL));
|
||||
@ -178,6 +180,24 @@ tmpfs_alloc_node(struct tmpfs_mount *tmp, enum vtype type,
|
||||
if (tmpfs_pages_check_avail(tmp, 1) == 0)
|
||||
return (ENOSPC);
|
||||
|
||||
if ((mp->mnt_kern_flag & MNTK_UNMOUNT) != 0) {
|
||||
/*
|
||||
* When a new tmpfs node is created for fully
|
||||
* constructed mount point, there must be a parent
|
||||
* node, which vnode is locked exclusively. As
|
||||
* consequence, if the unmount is executing in
|
||||
* parallel, vflush() cannot reclaim the parent vnode.
|
||||
* Due to this, the check for MNTK_UNMOUNT flag is not
|
||||
* racy: if we did not see MNTK_UNMOUNT flag, then tmp
|
||||
* cannot be destroyed until node construction is
|
||||
* finished and the parent vnode unlocked.
|
||||
*
|
||||
* Tmpfs does not need to instantiate new nodes during
|
||||
* unmount.
|
||||
*/
|
||||
return (EBUSY);
|
||||
}
|
||||
|
||||
nnode = (struct tmpfs_node *)uma_zalloc_arg(
|
||||
tmp->tm_node_pool, tmp, M_WAITOK);
|
||||
|
||||
@ -687,7 +707,8 @@ tmpfs_alloc_file(struct vnode *dvp, struct vnode **vpp, struct vattr *vap,
|
||||
parent = NULL;
|
||||
|
||||
/* Allocate a node that represents the new file. */
|
||||
error = tmpfs_alloc_node(tmp, vap->va_type, cnp->cn_cred->cr_uid,
|
||||
error = tmpfs_alloc_node(dvp->v_mount, tmp, vap->va_type,
|
||||
cnp->cn_cred->cr_uid,
|
||||
dnode->tn_gid, vap->va_mode, parent, target, vap->va_rdev, &node);
|
||||
if (error != 0)
|
||||
return (error);
|
||||
|
@ -238,7 +238,7 @@ tmpfs_mount(struct mount *mp)
|
||||
tmp->tm_ronly = (mp->mnt_flag & MNT_RDONLY) != 0;
|
||||
|
||||
/* Allocate the root node. */
|
||||
error = tmpfs_alloc_node(tmp, VDIR, root_uid,
|
||||
error = tmpfs_alloc_node(mp, tmp, VDIR, root_uid,
|
||||
root_gid, root_mode & ALLPERMS, NULL, NULL,
|
||||
VNOVAL, &root);
|
||||
|
||||
@ -269,38 +269,49 @@ tmpfs_mount(struct mount *mp)
|
||||
static int
|
||||
tmpfs_unmount(struct mount *mp, int mntflags)
|
||||
{
|
||||
int error;
|
||||
int flags = 0;
|
||||
struct tmpfs_mount *tmp;
|
||||
struct tmpfs_node *node;
|
||||
int error, flags;
|
||||
|
||||
/* Handle forced unmounts. */
|
||||
if (mntflags & MNT_FORCE)
|
||||
flags |= FORCECLOSE;
|
||||
|
||||
/* Finalize all pending I/O. */
|
||||
error = vflush(mp, 0, flags, curthread);
|
||||
if (error != 0)
|
||||
return error;
|
||||
|
||||
flags = (mntflags & MNT_FORCE) != 0 ? FORCECLOSE : 0;
|
||||
tmp = VFS_TO_TMPFS(mp);
|
||||
|
||||
/* Free all associated data. The loop iterates over the linked list
|
||||
* we have containing all used nodes. For each of them that is
|
||||
* a directory, we free all its directory entries. Note that after
|
||||
* freeing a node, it will automatically go to the available list,
|
||||
* so we will later have to iterate over it to release its items. */
|
||||
node = LIST_FIRST(&tmp->tm_nodes_used);
|
||||
while (node != NULL) {
|
||||
struct tmpfs_node *next;
|
||||
/* Stop writers */
|
||||
error = vfs_write_suspend_umnt(mp);
|
||||
if (error != 0)
|
||||
return (error);
|
||||
/*
|
||||
* At this point, nodes cannot be destroyed by any other
|
||||
* thread because write suspension is started.
|
||||
*/
|
||||
|
||||
for (;;) {
|
||||
error = vflush(mp, 0, flags, curthread);
|
||||
if (error != 0) {
|
||||
vfs_write_resume(mp, VR_START_WRITE);
|
||||
return (error);
|
||||
}
|
||||
MNT_ILOCK(mp);
|
||||
if (mp->mnt_nvnodelistsize == 0) {
|
||||
MNT_IUNLOCK(mp);
|
||||
break;
|
||||
}
|
||||
MNT_IUNLOCK(mp);
|
||||
if ((mntflags & MNT_FORCE) == 0) {
|
||||
vfs_write_resume(mp, VR_START_WRITE);
|
||||
return (EBUSY);
|
||||
}
|
||||
}
|
||||
|
||||
TMPFS_LOCK(tmp);
|
||||
while ((node = LIST_FIRST(&tmp->tm_nodes_used)) != NULL) {
|
||||
TMPFS_UNLOCK(tmp);
|
||||
if (node->tn_type == VDIR)
|
||||
tmpfs_dir_destroy(tmp, node);
|
||||
|
||||
next = LIST_NEXT(node, tn_entries);
|
||||
tmpfs_free_node(tmp, node);
|
||||
node = next;
|
||||
TMPFS_LOCK(tmp);
|
||||
}
|
||||
TMPFS_UNLOCK(tmp);
|
||||
|
||||
uma_zdestroy(tmp->tm_dirent_pool);
|
||||
uma_zdestroy(tmp->tm_node_pool);
|
||||
@ -313,11 +324,13 @@ tmpfs_unmount(struct mount *mp, int mntflags)
|
||||
/* Throw away the tmpfs_mount structure. */
|
||||
free(mp->mnt_data, M_TMPFSMNT);
|
||||
mp->mnt_data = NULL;
|
||||
vfs_write_resume(mp, VR_START_WRITE);
|
||||
|
||||
MNT_ILOCK(mp);
|
||||
mp->mnt_flag &= ~MNT_LOCAL;
|
||||
MNT_IUNLOCK(mp);
|
||||
return 0;
|
||||
|
||||
return (0);
|
||||
}
|
||||
|
||||
static int
|
||||
@ -401,6 +414,18 @@ tmpfs_statfs(struct mount *mp, struct statfs *sbp)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
tmpfs_sync(struct mount *mp, int waitfor)
|
||||
{
|
||||
|
||||
if (waitfor == MNT_SUSPEND) {
|
||||
MNT_ILOCK(mp);
|
||||
mp->mnt_kern_flag |= MNTK_SUSPEND2 | MNTK_SUSPENDED;
|
||||
MNT_IUNLOCK(mp);
|
||||
}
|
||||
return (0);
|
||||
}
|
||||
|
||||
/*
|
||||
* tmpfs vfs operations.
|
||||
*/
|
||||
@ -411,5 +436,6 @@ struct vfsops tmpfs_vfsops = {
|
||||
.vfs_root = tmpfs_root,
|
||||
.vfs_statfs = tmpfs_statfs,
|
||||
.vfs_fhtovp = tmpfs_fhtovp,
|
||||
.vfs_sync = tmpfs_sync,
|
||||
};
|
||||
VFS_SET(tmpfs_vfsops, tmpfs, VFCF_JAIL);
|
||||
|
Loading…
Reference in New Issue
Block a user