Groups | Search | Server Info | Keyboard shortcuts | Login | Register [http] [https] [nntp] [nntps]


Groups > linux.kernel > #1500596

[RFC][PATCH] mount: In mark_umount_candidates and __propogate_umount visit each mount once

Path csiph.com!aioe.org!bofh.it!news.nic.it!robomod
From ebiederm@xmission.com (Eric W. Biederman)
Newsgroups linux.kernel
Subject [RFC][PATCH] mount: In mark_umount_candidates and __propogate_umount visit each mount once
Date Thu, 13 Oct 2016 23:50:02 +0200
Message-ID <srW30-AS-7@gated-at.bofh.it> (permalink)
References <sqSb7-7sX-9@gated-at.bofh.it> <srRPH-6uS-7@gated-at.bofh.it>
X-Original-To Andrei Vagin <avagin@openvz.org>
User-Agent Gnus/5.13 (Gnus v5.13) Emacs/24.4 (gnu/linux)
MIME-Version 1.0
Content-Type text/plain
X-Xm-Spf eid=1bum6V-0004m0-Dm;;;mid=<87pon458l1.fsf_-_@x220.int.ebiederm.org>;;;hst=in01.mta.xmission.com;;;ip=75.170.125.99;;;frm=ebiederm@xmission.com;;;spf=neutral
X-Xm-Aid U2FsdGVkX1819YCsVlJedt49DCxJkon6WC21hz0zMyU=
X-Sa-Exim-Connect-IP 75.170.125.99
X-Sa-Exim-Mail-From ebiederm@xmission.com
X-Spam-Report * -1.0 ALL_TRUSTED Passed through trusted hosts only via SMTP * 0.7 XMSubLong Long Subject * 1.5 TR_Symld_Words too many words that have symbols inside * 0.0 TVD_RCVD_IP Message was received from an IP address * 0.0 T_TM2_M_HEADER_IN_MSG BODY: No description available. * 0.8 BAYES_50 BODY: Bayes spam probability is 40 to 60% * [score: 0.5000] * -0.0 DCC_CHECK_NEGATIVE Not listed in DCC * [sa07 1397; Body=1 Fuz1=1 Fuz2=1]
X-Spam-Dcc XMission; sa07 1397; Body=1 Fuz1=1 Fuz2=1
X-Spam-Combo **;Andrei Vagin <avagin@openvz.org>
X-Spam-Timing total 460 ms - load_scoreonly_sql: 0.03 (0.0%), signal_user_changed: 3.3 (0.7%), b_tie_ro: 2.3 (0.5%), parse: 1.31 (0.3%), extract_message_metadata: 19 (4.2%), get_uri_detail_list: 4.3 (0.9%), tests_pri_-1000: 9 (1.9%), tests_pri_-950: 1.15 (0.3%), tests_pri_-900: 0.96 (0.2%), tests_pri_-400: 34 (7.5%), check_bayes: 33 (7.2%), b_tokenize: 13 (2.9%), b_tok_get_all: 10 (2.2%), b_comp_prob: 2.6 (0.6%), b_tok_touch_all: 5.0 (1.1%), b_finish: 0.68 (0.1%), tests_pri_0: 384 (83.4%), check_dkim_signature: 0.73 (0.2%), check_dkim_adsp: 2.7 (0.6%), tests_pri_500: 4.1 (0.9%), rewrite_mail: 0.00 (0.0%)
X-Spam-Flag No
X-Sa-Exim-Version 4.2.1 (built Thu, 05 May 2016 13:38:54 -0600)
X-Sa-Exim-Scanned Yes (on in01.mta.xmission.com)
Sender robomod@news.nic.it
List-ID <linux-kernel.vger.kernel.org>
X-Mailing-List linux-kernel@vger.kernel.org
Approved robomod@news.nic.it
Lines 241
Organization linux.* mail to news gateway
X-Original-Cc Alexander Viro <viro@zeniv.linux.org.uk>, containers@lists.linux-foundation.org, linux-fsdevel@vger.kernel.org, linux-kernel@vger.kernel.org
X-Original-Date Thu, 13 Oct 2016 14:53:46 -0500
X-Original-Message-ID <87pon458l1.fsf_-_@x220.int.ebiederm.org>
X-Original-References <1476141965-21429-1-git-send-email-avagin@openvz.org> <877f9c6ui8.fsf@x220.int.ebiederm.org>
X-Original-Sender linux-kernel-owner@vger.kernel.org
Xref csiph.com linux.kernel:1500596

Show key headers only | View raw


Adrei Vagin pointed out that time to executue propagate_umount can go
non-linear (and take a ludicrious amount of time) when the mount
propogation trees of the mounts to be unmunted by a lazy unmount
overlap.

Solve this in the most straight forward way possible, by adding a new
mount flag to mark parts of the mount propagation tree that have been
visited, and use that mark to skip parts of the mount propagation tree
that have already been visited during an unmount.  This guarantees
that each mountpoint in the possibly overlapping mount propagation
trees will be visited exactly once.

Add the functions propagation_visit_next and propagation_revisit_next
to coordinate setting and clearling the visited mount mark.

Here is a script to generate such mount tree:
$ cat run.sh
mount -t tmpfs test-mount /mnt
mount --make-shared /mnt
for i in `seq $1`; do
        mkdir /mnt/test.$i
        mount --bind /mnt /mnt/test.$i
done
cat /proc/mounts | grep test-mount | wc -l
time umount -l /mnt
$ for i in `seq 10 16`; do echo $i; unshare -Urm bash ./run.sh $i; done

Here are the performance numbers with and without the patch:

mounts | before | after (real sec)
-----------------------------
  1024 |  0.071 | 0.024
  2048 |  0.184 | 0.030
  4096 |  0.604 | 0.040
  8912 |  4.471 | 0.043
 16384 | 34.826 | 0.082
 32768 |        | 0.151
 65536 |        | 0.289
131072 |        | 0.659

Andrei Vagin fixing this performance problem is part of the
work to fix CVE-2016-6213.

Cc: stable@vger.kernel.org
Reported-by: Andrei Vagin <avagin@openvz.org>
Signed-off-by: "Eric W. Biederman" <ebiederm@xmission.com>
---

Andrei can you take a look at this patch and see if you can see any
problems.  My limited testing suggests this approach does a much better
job of solving the problem you were seeing.  With the time looking
almost linear in the number of mounts now.

 fs/pnode.c            | 125 ++++++++++++++++++++++++++++++++++++++++++++++++--
 fs/pnode.h            |   4 ++
 include/linux/mount.h |   2 +
 3 files changed, 126 insertions(+), 5 deletions(-)

diff --git a/fs/pnode.c b/fs/pnode.c
index 234a9ac49958..3acce0c75f94 100644
--- a/fs/pnode.c
+++ b/fs/pnode.c
@@ -164,6 +164,120 @@ static struct mount *propagation_next(struct mount *m,
 	}
 }
 
+/*
+ * get the next mount in the propagation tree (that has not been visited)
+ * @m: the mount seen last
+ * @origin: the original mount from where the tree walk initiated
+ *
+ * Note that peer groups form contiguous segments of slave lists.
+ * We rely on that in get_source() to be able to find out if
+ * vfsmount found while iterating with propagation_next() is
+ * a peer of one we'd found earlier.
+ */
+static struct mount *propagation_visit_next(struct mount *m,
+					    struct mount *origin)
+{
+	/* Has this part of the propgation tree already been visited? */
+	if (IS_MNT_VISITED(m))
+		return NULL;
+
+	SET_MNT_VISITED(m);
+
+	/* are there any slaves of this mount? */
+	if (!list_empty(&m->mnt_slave_list)) {
+		struct mount *slave = first_slave(m);
+		while (1) {
+			if (!IS_MNT_VISITED(slave))
+				return slave;
+			if (slave->mnt_slave.next == &m->mnt_slave_list)
+				break;
+			slave = next_slave(slave);
+		}
+	}
+	while (1) {
+		struct mount *master = m->mnt_master;
+
+		if (master == origin->mnt_master) {
+			struct mount *next = next_peer(m);
+			while (1) {
+				if (next == origin)
+					return NULL;
+				if (!IS_MNT_VISITED(next))
+					return next;
+				next = next_peer(next);
+			}
+		} else {
+			while (1) {
+				if (m->mnt_slave.next == &master->mnt_slave_list)
+					break;
+				m = next_slave(m);
+				if (!IS_MNT_VISITED(m))
+					return m;
+			}
+		}
+
+		/* back at master */
+		m = master;
+	}
+}
+
+/*
+ * get the next mount in the propagation tree (that has not been revisited)
+ * @m: the mount seen last
+ * @origin: the original mount from where the tree walk initiated
+ *
+ * Note that peer groups form contiguous segments of slave lists.
+ * We rely on that in get_source() to be able to find out if
+ * vfsmount found while iterating with propagation_next() is
+ * a peer of one we'd found earlier.
+ */
+static struct mount *propagation_revisit_next(struct mount *m,
+					      struct mount *origin)
+{
+	/* Has this part of the propgation tree already been revisited? */
+	if (!IS_MNT_VISITED(m))
+		return NULL;
+
+	CLEAR_MNT_VISITED(m);
+
+	/* are there any slaves of this mount? */
+	if (!list_empty(&m->mnt_slave_list)) {
+		struct mount *slave = first_slave(m);
+		while (1) {
+			if (IS_MNT_VISITED(slave))
+				return slave;
+			if (slave->mnt_slave.next == &m->mnt_slave_list)
+				break;
+			slave = next_slave(slave);
+		}
+	}
+	while (1) {
+		struct mount *master = m->mnt_master;
+
+		if (master == origin->mnt_master) {
+			struct mount *next = next_peer(m);
+			while (1) {
+				if (next == origin)
+					return NULL;
+				if (IS_MNT_VISITED(next))
+					return next;
+				next = next_peer(next);
+			}
+		} else {
+			while (1) {
+				if (m->mnt_slave.next == &master->mnt_slave_list)
+					break;
+				m = next_slave(m);
+				if (IS_MNT_VISITED(m))
+					return m;
+			}
+		}
+
+		/* back at master */
+		m = master;
+	}
+}
+
 static struct mount *next_group(struct mount *m, struct mount *origin)
 {
 	while (1) {
@@ -399,11 +513,12 @@ static void mark_umount_candidates(struct mount *mnt)
 
 	BUG_ON(parent == mnt);
 
-	for (m = propagation_next(parent, parent); m;
-			m = propagation_next(m, parent)) {
+	for (m = propagation_visit_next(parent, parent); m;
+			m = propagation_visit_next(m, parent)) {
 		struct mount *child = __lookup_mnt_last(&m->mnt,
 						mnt->mnt_mountpoint);
-		if (child && (!IS_MNT_LOCKED(child) || IS_MNT_MARKED(m))) {
+		if (child && (!IS_MNT_LOCKED(child) ||
+			      IS_MNT_MARKED(m))) {
 			SET_MNT_MARK(child);
 		}
 	}
@@ -420,8 +535,8 @@ static void __propagate_umount(struct mount *mnt)
 
 	BUG_ON(parent == mnt);
 
-	for (m = propagation_next(parent, parent); m;
-			m = propagation_next(m, parent)) {
+	for (m = propagation_revisit_next(parent, parent); m;
+			m = propagation_revisit_next(m, parent)) {
 
 		struct mount *child = __lookup_mnt_last(&m->mnt,
 						mnt->mnt_mountpoint);
diff --git a/fs/pnode.h b/fs/pnode.h
index 550f5a8b4fcf..988ea4945764 100644
--- a/fs/pnode.h
+++ b/fs/pnode.h
@@ -21,6 +21,10 @@
 #define CLEAR_MNT_MARK(m) ((m)->mnt.mnt_flags &= ~MNT_MARKED)
 #define IS_MNT_LOCKED(m) ((m)->mnt.mnt_flags & MNT_LOCKED)
 
+#define IS_MNT_VISITED(m) ((m)->mnt.mnt_flags & MNT_VISITED)
+#define SET_MNT_VISITED(m) ((m)->mnt.mnt_flags |= MNT_VISITED)
+#define CLEAR_MNT_VISITED(m) ((m)->mnt.mnt_flags &= ~MNT_VISITED)
+
 #define CL_EXPIRE    		0x01
 #define CL_SLAVE     		0x02
 #define CL_COPY_UNBINDABLE	0x04
diff --git a/include/linux/mount.h b/include/linux/mount.h
index 9227b190fdf2..6048045b96c3 100644
--- a/include/linux/mount.h
+++ b/include/linux/mount.h
@@ -52,6 +52,8 @@ struct mnt_namespace;
 
 #define MNT_INTERNAL	0x4000
 
+#define MNT_VISITED		0x010000
+
 #define MNT_LOCK_ATIME		0x040000
 #define MNT_LOCK_NOEXEC		0x080000
 #define MNT_LOCK_NOSUID		0x100000
-- 
2.8.3

Back to linux.kernel | Previous | NextPrevious in thread | Next in thread | Find similar | Unroll thread


Thread

Re: [PATCH] [v3] mount: dont execute propagate_umount() many times for same mounts ebiederm@xmission.com (Eric W. Biederman) - 2016-10-13 19:20 +0200
  [RFC][PATCH] mount: In mark_umount_candidates and __propogate_umount visit each mount once ebiederm@xmission.com (Eric W. Biederman) - 2016-10-13 23:50 +0200
    Re: [RFC][PATCH] mount: In mark_umount_candidates and  __propogate_umount visit each mount once Andrey Vagin <avagin@openvz.org> - 2016-10-14 04:40 +0200
      Re: [RFC][PATCH] mount: In mark_umount_candidates and __propogate_umount visit each mount once ebiederm@xmission.com (Eric W. Biederman) - 2016-10-14 04:50 +0200
        [RFC][PATCH v2] mount: In mark_umount_candidates and __propogate_umount visit each mount once ebiederm@xmission.com (Eric W. Biederman) - 2016-10-14 20:40 +0200
          Re: [RFC][PATCH v2] mount: In mark_umount_candidates and __propogate_umount visit each mount once ebiederm@xmission.com (Eric W. Biederman) - 2016-10-18 09:00 +0200
            [REVIEW][PATCH] mount: In propagate_umount handle overlapping mount propagation trees ebiederm@xmission.com (Eric W. Biederman) - 2016-10-19 05:50 +0200
              Re: [REVIEW][PATCH] mount: In propagate_umount handle overlapping mount propagation trees ebiederm@xmission.com (Eric W. Biederman) - 2016-10-21 21:30 +0200
                [RFC][PATCH v2] mount: In propagate_umount handle overlapping mount propagation trees ebiederm@xmission.com (Eric W. Biederman) - 2016-10-22 21:50 +0200
                Re: [RFC][PATCH v2] mount: In propagate_umount handle overlapping mount propagation trees ebiederm@xmission.com (Eric W. Biederman) - 2016-10-25 23:50 +0200
                Re: [RFC][PATCH v2] mount: In propagate_umount handle overlapping mount propagation trees ebiederm@xmission.com (Eric W. Biederman) - 2016-10-26 03:50 +0200

csiph-web