Commit Message

With this patch, we don't try to umount all mounts of a tree together.
Instead of this we umount them one by one. In this case, we see a significant
improvement in performance for the worsе case.
v2: create a sorted list of mounts, so that umount a child before its
parent.
The reason of this optimization is that umount() can hold namespace_sem
for a long time, this semaphore is global, so it affects all users.
Recently Eric W. Biederman added a per mount namespace limit on the
number of mounts. The default number of mounts allowed per mount
namespace at 100,000. Currently this value is allowed to construct a tree
which requires hours to be umounted.
In a worse case the current complexity of umount_tree() is O(n^3).
* Enumirate all mounts in a target tree (propagate_umount)
* Enumirate mounts to find where these changes have to
be propagated (mark_umount_candidates)
* Enumirate mounts to find a requered mount by parent and dentry
(__lookup_mnt). __lookup_mnt() searches a mount in m_hash, but
the number of mounts is much bigger than a size of the hash.
The worse case is when all mounts from the tree live in the same shared
group. In this case we have to enumirate all mounts on each step.
There is CVE-2016-6213 about this case.
Here are results for the kernel with this patch
$ for i in `seq 10 15`; do unshare -m sh ./run.sh $i; done
umount -l /mnt/1 -> 0:00.00
umount -l /mnt/1 -> 0:00.01
umount -l /mnt/1 -> 0:00.01
umount -l /mnt/1 -> 0:00.03
umount -l /mnt/1 -> 0:00.07
umount -l /mnt/1 -> 0:00.14
Here are results for the kernel without this patch
$ for i in `seq 10 15`; do unshare -m sh ./run.sh $i; done
umount -l /mnt/1 -> 0:00.04
umount -l /mnt/1 -> 0:00.17
umount -l /mnt/1 -> 0:00.75
umount -l /mnt/1 -> 0:05.96
umount -l /mnt/1 -> 0:34.40
umount -l /mnt/1 -> 3:46.27
And here is a test script:
$ cat run.sh
set -e -m
mount -t tmpfs zdtm /mnt
mkdir -p /mnt/1 /mnt/2
mount -t tmpfs zdtm /mnt/1
mount --make-shared /mnt/1
mkdir /mnt/1/1
for i in `seq $1`; do
./mount --bind /mnt/1/1 /mnt/1/1
done
echo -n "umount -l /mnt/1 -> "
/usr/bin/time -f '%E' ./umount -l /mnt/1
And we need these simple mount and umount tools, because the standard
ones read /proc/self/mountinfo, but this is extremely slow when we have
thousands of mounts.
$ cat mount.c
#include <sys/mount.h>
#include <stdlib.h>
int main(int argc, char **argv)
{
return mount(argv[2], argv[3], NULL, MS_BIND, NULL);
}
$ cat umount.c
#include <sys/mount.h>
int main(int argc, char **argv)
{
return umount2(argv[2], MNT_DETACH);
}
Here is a previous attempt to optimize this code:
https://lkml.org/lkml/2016/10/10/495
Signed-off-by: Andrei Vagin <avagin@openvz.org>
---
fs/namespace.c | 105 ++++++++++++++++++++++++++++++++++++---------------------
1 file changed, 67 insertions(+), 38 deletions(-)