Skip to content

Commit f2ebb3a

Browse files
author
Al Viro
committed
smarter propagate_mnt()
The current mainline has copies propagated to *all* nodes, then tears down the copies we made for nodes that do not contain counterparts of the desired mountpoint. That sets the right propagation graph for the copies (at teardown time we move the slaves of removed node to a surviving peer or directly to master), but we end up paying a fairly steep price in useless allocations. It's fairly easy to create a situation where N calls of mount(2) create exactly N bindings, with O(N^2) vfsmounts allocated and freed in process. Fortunately, it is possible to avoid those allocations/freeings. The trick is to create copies in the right order and find which one would've eventually become a master with the current algorithm. It turns out to be possible in O(nodes getting propagation) time and with no extra allocations at all. One part is that we need to make sure that eventual master will be created before its slaves, so we need to walk the propagation tree in a different order - by peer groups. And iterate through the peers before dealing with the next group. Another thing is finding the (earlier) copy that will be a master of one we are about to create; to do that we are (temporary) marking the masters of mountpoints we are attaching the copies to. Either we are in a peer of the last mountpoint we'd dealt with, or we have the following situation: we are attaching to mountpoint M, the last copy S_0 had been attached to M_0 and there are sequences S_0...S_n, M_0...M_n such that S_{i+1} is a master of S_{i}, S_{i} mounted on M{i} and we need to create a slave of the first S_{k} such that M is getting propagation from M_{k}. It means that the master of M_{k} will be among the sequence of masters of M. On the other hand, the nearest marked node in that sequence will either be the master of M_{k} or the master of M_{k-1} (the latter - in the case if M_{k-1} is a slave of something M gets propagation from, but in a wrong peer group). So we go through the sequence of masters of M until we find a marked one (P). Let N be the one before it. Then we go through the sequence of masters of S_0 until we find one (say, S) mounted on a node D that has P as master and check if D is a peer of N. If it is, S will be the master of new copy, if not - the master of S will be. That's it for the hard part; the rest is fairly simple. Iterator is in next_group(), handling of one prospective mountpoint is propagate_one(). It seems to survive all tests and gives a noticably better performance than the current mainline for setups that are seriously using shared subtrees. Cc: [email protected] Signed-off-by: Al Viro <[email protected]>
1 parent 38129a1 commit f2ebb3a

File tree

4 files changed

+133
-82
lines changed

4 files changed

+133
-82
lines changed

fs/namespace.c

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -885,7 +885,7 @@ static struct mount *clone_mnt(struct mount *old, struct dentry *root,
885885
goto out_free;
886886
}
887887

888-
mnt->mnt.mnt_flags = old->mnt.mnt_flags & ~MNT_WRITE_HOLD;
888+
mnt->mnt.mnt_flags = old->mnt.mnt_flags & ~(MNT_WRITE_HOLD|MNT_MARKED);
889889
/* Don't allow unprivileged users to change mount flags */
890890
if ((flag & CL_UNPRIVILEGED) && (mnt->mnt.mnt_flags & MNT_READONLY))
891891
mnt->mnt.mnt_flags |= MNT_LOCK_READONLY;
@@ -1661,9 +1661,9 @@ static int attach_recursive_mnt(struct mount *source_mnt,
16611661
if (err)
16621662
goto out;
16631663
err = propagate_mnt(dest_mnt, dest_mp, source_mnt, &tree_list);
1664+
lock_mount_hash();
16641665
if (err)
16651666
goto out_cleanup_ids;
1666-
lock_mount_hash();
16671667
for (p = source_mnt; p; p = next_mnt(p, source_mnt))
16681668
set_mnt_shared(p);
16691669
} else {
@@ -1690,6 +1690,11 @@ static int attach_recursive_mnt(struct mount *source_mnt,
16901690
return 0;
16911691

16921692
out_cleanup_ids:
1693+
while (!hlist_empty(&tree_list)) {
1694+
child = hlist_entry(tree_list.first, struct mount, mnt_hash);
1695+
umount_tree(child, 0);
1696+
}
1697+
unlock_mount_hash();
16931698
cleanup_group_ids(source_mnt, NULL);
16941699
out:
16951700
return err;
@@ -2044,7 +2049,7 @@ static int do_add_mount(struct mount *newmnt, struct path *path, int mnt_flags)
20442049
struct mount *parent;
20452050
int err;
20462051

2047-
mnt_flags &= ~(MNT_SHARED | MNT_WRITE_HOLD | MNT_INTERNAL | MNT_DOOMED | MNT_SYNC_UMOUNT);
2052+
mnt_flags &= ~MNT_INTERNAL_FLAGS;
20482053

20492054
mp = lock_mount(path);
20502055
if (IS_ERR(mp))

fs/pnode.c

Lines changed: 119 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -164,46 +164,94 @@ static struct mount *propagation_next(struct mount *m,
164164
}
165165
}
166166

167-
/*
168-
* return the source mount to be used for cloning
169-
*
170-
* @dest the current destination mount
171-
* @last_dest the last seen destination mount
172-
* @last_src the last seen source mount
173-
* @type return CL_SLAVE if the new mount has to be
174-
* cloned as a slave.
175-
*/
176-
static struct mount *get_source(struct mount *dest,
177-
struct mount *last_dest,
178-
struct mount *last_src,
179-
int *type)
167+
static struct mount *next_group(struct mount *m, struct mount *origin)
180168
{
181-
struct mount *p_last_src = NULL;
182-
struct mount *p_last_dest = NULL;
183-
184-
while (last_dest != dest->mnt_master) {
185-
p_last_dest = last_dest;
186-
p_last_src = last_src;
187-
last_dest = last_dest->mnt_master;
188-
last_src = last_src->mnt_master;
169+
while (1) {
170+
while (1) {
171+
struct mount *next;
172+
if (!IS_MNT_NEW(m) && !list_empty(&m->mnt_slave_list))
173+
return first_slave(m);
174+
next = next_peer(m);
175+
if (m->mnt_group_id == origin->mnt_group_id) {
176+
if (next == origin)
177+
return NULL;
178+
} else if (m->mnt_slave.next != &next->mnt_slave)
179+
break;
180+
m = next;
181+
}
182+
/* m is the last peer */
183+
while (1) {
184+
struct mount *master = m->mnt_master;
185+
if (m->mnt_slave.next != &master->mnt_slave_list)
186+
return next_slave(m);
187+
m = next_peer(master);
188+
if (master->mnt_group_id == origin->mnt_group_id)
189+
break;
190+
if (master->mnt_slave.next == &m->mnt_slave)
191+
break;
192+
m = master;
193+
}
194+
if (m == origin)
195+
return NULL;
189196
}
197+
}
190198

191-
if (p_last_dest) {
192-
do {
193-
p_last_dest = next_peer(p_last_dest);
194-
} while (IS_MNT_NEW(p_last_dest));
195-
/* is that a peer of the earlier? */
196-
if (dest == p_last_dest) {
197-
*type = CL_MAKE_SHARED;
198-
return p_last_src;
199+
/* all accesses are serialized by namespace_sem */
200+
static struct user_namespace *user_ns;
201+
static struct mount *last_dest, *last_source, *dest_master;
202+
static struct mountpoint *mp;
203+
static struct hlist_head *list;
204+
205+
static int propagate_one(struct mount *m)
206+
{
207+
struct mount *child;
208+
int type;
209+
/* skip ones added by this propagate_mnt() */
210+
if (IS_MNT_NEW(m))
211+
return 0;
212+
/* skip if mountpoint isn't covered by it */
213+
if (!is_subdir(mp->m_dentry, m->mnt.mnt_root))
214+
return 0;
215+
if (m->mnt_group_id == last_dest->mnt_group_id) {
216+
type = CL_MAKE_SHARED;
217+
} else {
218+
struct mount *n, *p;
219+
for (n = m; ; n = p) {
220+
p = n->mnt_master;
221+
if (p == dest_master || IS_MNT_MARKED(p)) {
222+
while (last_dest->mnt_master != p) {
223+
last_source = last_source->mnt_master;
224+
last_dest = last_source->mnt_parent;
225+
}
226+
if (n->mnt_group_id != last_dest->mnt_group_id) {
227+
last_source = last_source->mnt_master;
228+
last_dest = last_source->mnt_parent;
229+
}
230+
break;
231+
}
199232
}
233+
type = CL_SLAVE;
234+
/* beginning of peer group among the slaves? */
235+
if (IS_MNT_SHARED(m))
236+
type |= CL_MAKE_SHARED;
200237
}
201-
/* slave of the earlier, then */
202-
*type = CL_SLAVE;
203-
/* beginning of peer group among the slaves? */
204-
if (IS_MNT_SHARED(dest))
205-
*type |= CL_MAKE_SHARED;
206-
return last_src;
238+
239+
/* Notice when we are propagating across user namespaces */
240+
if (m->mnt_ns->user_ns != user_ns)
241+
type |= CL_UNPRIVILEGED;
242+
child = copy_tree(last_source, last_source->mnt.mnt_root, type);
243+
if (IS_ERR(child))
244+
return PTR_ERR(child);
245+
mnt_set_mountpoint(m, mp, child);
246+
last_dest = m;
247+
last_source = child;
248+
if (m->mnt_master != dest_master) {
249+
read_seqlock_excl(&mount_lock);
250+
SET_MNT_MARK(m->mnt_master);
251+
read_sequnlock_excl(&mount_lock);
252+
}
253+
hlist_add_head(&child->mnt_hash, list);
254+
return 0;
207255
}
208256

209257
/*
@@ -222,56 +270,48 @@ static struct mount *get_source(struct mount *dest,
222270
int propagate_mnt(struct mount *dest_mnt, struct mountpoint *dest_mp,
223271
struct mount *source_mnt, struct hlist_head *tree_list)
224272
{
225-
struct user_namespace *user_ns = current->nsproxy->mnt_ns->user_ns;
226-
struct mount *m, *child;
273+
struct mount *m, *n;
227274
int ret = 0;
228-
struct mount *prev_dest_mnt = dest_mnt;
229-
struct mount *prev_src_mnt = source_mnt;
230-
HLIST_HEAD(tmp_list);
231-
232-
for (m = propagation_next(dest_mnt, dest_mnt); m;
233-
m = propagation_next(m, dest_mnt)) {
234-
int type;
235-
struct mount *source;
236-
237-
if (IS_MNT_NEW(m))
238-
continue;
239-
240-
source = get_source(m, prev_dest_mnt, prev_src_mnt, &type);
241-
242-
/* Notice when we are propagating across user namespaces */
243-
if (m->mnt_ns->user_ns != user_ns)
244-
type |= CL_UNPRIVILEGED;
245-
246-
child = copy_tree(source, source->mnt.mnt_root, type);
247-
if (IS_ERR(child)) {
248-
ret = PTR_ERR(child);
249-
tmp_list = *tree_list;
250-
tmp_list.first->pprev = &tmp_list.first;
251-
INIT_HLIST_HEAD(tree_list);
275+
276+
/*
277+
* we don't want to bother passing tons of arguments to
278+
* propagate_one(); everything is serialized by namespace_sem,
279+
* so globals will do just fine.
280+
*/
281+
user_ns = current->nsproxy->mnt_ns->user_ns;
282+
last_dest = dest_mnt;
283+
last_source = source_mnt;
284+
mp = dest_mp;
285+
list = tree_list;
286+
dest_master = dest_mnt->mnt_master;
287+
288+
/* all peers of dest_mnt, except dest_mnt itself */
289+
for (n = next_peer(dest_mnt); n != dest_mnt; n = next_peer(n)) {
290+
ret = propagate_one(n);
291+
if (ret)
252292
goto out;
253-
}
293+
}
254294

255-
if (is_subdir(dest_mp->m_dentry, m->mnt.mnt_root)) {
256-
mnt_set_mountpoint(m, dest_mp, child);
257-
hlist_add_head(&child->mnt_hash, tree_list);
258-
} else {
259-
/*
260-
* This can happen if the parent mount was bind mounted
261-
* on some subdirectory of a shared/slave mount.
262-
*/
263-
hlist_add_head(&child->mnt_hash, &tmp_list);
264-
}
265-
prev_dest_mnt = m;
266-
prev_src_mnt = child;
295+
/* all slave groups */
296+
for (m = next_group(dest_mnt, dest_mnt); m;
297+
m = next_group(m, dest_mnt)) {
298+
/* everything in that slave group */
299+
n = m;
300+
do {
301+
ret = propagate_one(n);
302+
if (ret)
303+
goto out;
304+
n = next_peer(n);
305+
} while (n != m);
267306
}
268307
out:
269-
lock_mount_hash();
270-
while (!hlist_empty(&tmp_list)) {
271-
child = hlist_entry(tmp_list.first, struct mount, mnt_hash);
272-
umount_tree(child, 0);
308+
read_seqlock_excl(&mount_lock);
309+
hlist_for_each_entry(n, tree_list, mnt_hash) {
310+
m = n->mnt_parent;
311+
if (m->mnt_master != dest_mnt->mnt_master)
312+
CLEAR_MNT_MARK(m->mnt_master);
273313
}
274-
unlock_mount_hash();
314+
read_sequnlock_excl(&mount_lock);
275315
return ret;
276316
}
277317

fs/pnode.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@
1616
#define IS_MNT_NEW(m) (!(m)->mnt_ns)
1717
#define CLEAR_MNT_SHARED(m) ((m)->mnt.mnt_flags &= ~MNT_SHARED)
1818
#define IS_MNT_UNBINDABLE(m) ((m)->mnt.mnt_flags & MNT_UNBINDABLE)
19+
#define IS_MNT_MARKED(m) ((m)->mnt.mnt_flags & MNT_MARKED)
20+
#define SET_MNT_MARK(m) ((m)->mnt.mnt_flags |= MNT_MARKED)
21+
#define CLEAR_MNT_MARK(m) ((m)->mnt.mnt_flags &= ~MNT_MARKED)
1922

2023
#define CL_EXPIRE 0x01
2124
#define CL_SLAVE 0x02

include/linux/mount.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,13 +44,16 @@ struct mnt_namespace;
4444
#define MNT_SHARED_MASK (MNT_UNBINDABLE)
4545
#define MNT_PROPAGATION_MASK (MNT_SHARED | MNT_UNBINDABLE)
4646

47+
#define MNT_INTERNAL_FLAGS (MNT_SHARED | MNT_WRITE_HOLD | MNT_INTERNAL | \
48+
MNT_DOOMED | MNT_SYNC_UMOUNT | MNT_MARKED)
4749

4850
#define MNT_INTERNAL 0x4000
4951

5052
#define MNT_LOCK_READONLY 0x400000
5153
#define MNT_LOCKED 0x800000
5254
#define MNT_DOOMED 0x1000000
5355
#define MNT_SYNC_UMOUNT 0x2000000
56+
#define MNT_MARKED 0x4000000
5457

5558
struct vfsmount {
5659
struct dentry *mnt_root; /* root of the mounted tree */

0 commit comments

Comments
 (0)