为什么umount -R
需要 0.2 秒才能卸载这个绑定挂载的子树?挂载子树只需要 0.02 秒,更改其传播标志只需要 0.00 秒。
(我正在考虑将当前的安装集克隆到一个子目录,更改它们,然后使用 切换到它们pivot_mount
。但我观察到的延迟对于此目的来说实际上是不可接受的)。
本练习假设/
子挂载是共享挂载。 Linux 默认情况下并没有这样做,但是系统做。
# mkdir /mnt/a
# mount --bind /mnt/a /mnt/a --make-private
# time mount --rbind / /mnt/a
0.00user 0.00system 0:00.02elapsed 9%CPU (0avgtext+0avgdata 3020maxresident)k
0inputs+0outputs (0major+135minor)pagefaults 0swaps
# time mount --make-rprivate /mnt/a
0.00user 0.00system 0:00.00elapsed 100%CPU (0avgtext+0avgdata 3184maxresident)k
0inputs+0outputs (0major+136minor)pagefaults 0swaps
# time umount -R /mnt/a
0.00user 0.00system 0:00.19elapsed 9%CPU (0avgtext+0avgdata 3392maxresident)k
0inputs+0outputs (0major+194minor)pagefaults 0swaps
进一步测试
strace -cw
在表演下运行
% time seconds usecs/call calls errors syscall
------ ----------- ----------- --------- --------- ----------------
90.44 0.180113 5297 34 umount2
...
因此,除了指出最后一个操作需要 34 次单独umount2()
调用,而其他操作仅包含一次mount()
使用 MS_REC(递归)标志的对 的调用之外,这并不是非常具有启发性。与 的标题数据一样time
,现在是挂钟时间。 strace -c
显示系统时间(即内核花费的cpu时间),总共只有0.009s。
尽管它确实指出了一些有趣的事情。改为使用umount -l /mnt/a
,可将总时间减少至 0.02 秒。这使用单个umount2()
调用来分离子树/mnt/a
(并在后台进行任何清理)。
从 的单次调用来看strace -ttt -T -e trace=umount2 umount -R /mnt/a
,单次调用的次数分布比较均匀;它们的范围从 0.002s 到 0.012s,但没有明显的模式,如果我重复,模式看起来也不一致。
umount -R
运行后perf record -a
,perf report
显示gsd-housekeeping
、gvfs-udisks2-volume-monitor
、 、中的几个热点systemd
。进程umount
根本就没有显示出来。这可以解释为什么在内核或用户空间中花费的 CPU 时间可以忽略不计time
。umount
(如果有人有更全面的方法来聚合测试期间每个进程的 cpu 使用情况,我会非常感兴趣:)。
其他进程可能正在执行一些处理以响应每个安装事件。
例如,在一次耗时 0.4 秒的运行中,systemd 似乎负责使用我的四个 cpu 之一的 0.13 秒:
# systemctl set-property init.scope CPUAccounting=yes
# systemctl show --property CPUUsageNSec init.scope; time umount -R /mnt/a ; systemctl show --property CPUUsageNSec init.scope
CPUUsageNSec=2403124481
real 0m0.408s
user 0m0.015s
sys 0m0.020s
CPUUsageNSec=2534058385
# echo $(( 2534058385 - 2403124481 ))
130933904
但这似乎不是正确的解释,因为在私有挂载命名空间中运行时也会发生相同的延迟。在这种情况下,perf record -a
不显示其他进程,仅显示umount
进程(和性能本身)。
# unshare -m
# time mount --rbind / /mnt/a
real 0m0.005s
user 0m0.003s
sys 0m0.002s
# time mount --make-rprivate /mnt/a
real 0m0.005s
user 0m0.003s
sys 0m0.002s
# systemctl show --property CPUUsageNSec init.scope; time umount -R /mnt/a ; systemctl show --property CPUUsageNSec init.scope
CPUUsageNSec=3637792026
real 0m0.381s
user 0m0.026s
sys 0m0.018s
CPUUsageNSec=3645973005
# echo $((3645973005-3637792026))
8180979
在这种情况下,cpu 似乎并不相关。我有 4 个能够以 2.3Ghz 运行的 cpu 核心,但perf stat -a
总体显示 cpu 使用率低于 5%。 (忽略“使用的CPU,我认为它总是显示-a
使用时的完整值)。
# time perf stat -a umount -R /mnt/a
Performance counter stats for 'system wide':
2079.333650 cpu-clock (msec) # 3.998 CPUs utilized
635 context-switches # 0.305 K/sec
23 cpu-migrations # 0.011 K/sec
333 page-faults # 0.160 K/sec
198,278,822 cycles # 0.095 GHz
138,734,277 instructions # 0.70 insn per cycle
31,401,067 branches # 15.102 M/sec
934,327 branch-misses # 2.98% of all branches
0.520083596 seconds time elapsed
real 0m0.543s
user 0m0.038s
sys 0m0.043s
然而,仍有一些进程响应此事件... umount 仍会在系统日志中触发 78 行消息。
Feb 07 10:34:26 alan-laptop systemd[1]: proc-sys-fs-binfmt_misc.automount: Got automount request for /proc/sys/fs/binfmt_misc, triggered by 6040 (umount)
Feb 07 10:34:26 alan-laptop systemd[1]: proc-sys-fs-binfmt_misc.automount: Automount point already active?
Feb 07 10:34:26 alan-laptop systemd[1]: proc-sys-fs-binfmt_misc.automount: Got automount request for /proc/sys/fs/binfmt_misc, triggered by 6040 (umount)
Feb 07 10:34:26 alan-laptop systemd[1]: proc-sys-fs-binfmt_misc.automount: Automount point already active?
注意findmnt
显示我避免创建任何可怕的类似递归的传播,例如,如果我在以下命令之后运行它--make-rprivate
:
findmnt -o TARGET,PROPAGATION
TARGET PROPAGATION
/ shared
├─/sys shared
│ ├─/sys/kernel/security shared
│ ├─/sys/fs/cgroup shared
│ │ ├─/sys/fs/cgroup/unified shared
│ │ ├─/sys/fs/cgroup/systemd shared
│ │ ├─/sys/fs/cgroup/net_cls,net_prio shared
│ │ ├─/sys/fs/cgroup/cpu,cpuacct shared
│ │ ├─/sys/fs/cgroup/devices shared
│ │ ├─/sys/fs/cgroup/freezer shared
│ │ ├─/sys/fs/cgroup/perf_event shared
│ │ ├─/sys/fs/cgroup/hugetlb shared
│ │ ├─/sys/fs/cgroup/memory shared
│ │ ├─/sys/fs/cgroup/blkio shared
│ │ ├─/sys/fs/cgroup/cpuset shared
│ │ └─/sys/fs/cgroup/pids shared
│ ├─/sys/fs/pstore shared
│ ├─/sys/fs/selinux shared
│ ├─/sys/kernel/debug shared
│ └─/sys/kernel/config shared
├─/proc shared
│ └─/proc/sys/fs/binfmt_misc shared
├─/dev shared
│ ├─/dev/shm shared
│ ├─/dev/pts shared
│ ├─/dev/mqueue shared
│ └─/dev/hugepages shared
├─/run shared
│ ├─/run/user/1000 shared
│ └─/run/user/42 shared
├─/usr shared
├─/tmp shared
├─/boot shared
└─/mnt/a private
└─/mnt/a private
├─/mnt/a/usr private
├─/mnt/a/sys private
│ ├─/mnt/a/sys/kernel/security private
│ ├─/mnt/a/sys/fs/cgroup private
│ │ ├─/mnt/a/sys/fs/cgroup/unified private
│ │ ├─/mnt/a/sys/fs/cgroup/systemd private
│ │ ├─/mnt/a/sys/fs/cgroup/net_cls,net_prio private
│ │ ├─/mnt/a/sys/fs/cgroup/cpu,cpuacct private
│ │ ├─/mnt/a/sys/fs/cgroup/devices private
│ │ ├─/mnt/a/sys/fs/cgroup/freezer private
│ │ ├─/mnt/a/sys/fs/cgroup/perf_event private
│ │ ├─/mnt/a/sys/fs/cgroup/hugetlb private
│ │ ├─/mnt/a/sys/fs/cgroup/memory private
│ │ ├─/mnt/a/sys/fs/cgroup/blkio private
│ │ ├─/mnt/a/sys/fs/cgroup/cpuset private
│ │ └─/mnt/a/sys/fs/cgroup/pids private
│ ├─/mnt/a/sys/fs/pstore private
│ ├─/mnt/a/sys/kernel/config private
│ ├─/mnt/a/sys/fs/selinux private
│ └─/mnt/a/sys/kernel/debug private
├─/mnt/a/dev private
│ ├─/mnt/a/dev/shm private
│ ├─/mnt/a/dev/pts private
│ ├─/mnt/a/dev/mqueue private
│ └─/mnt/a/dev/hugepages private
├─/mnt/a/run private
│ ├─/mnt/a/run/user/1000 private
│ └─/mnt/a/run/user/42 private
├─/mnt/a/proc private
│ └─/mnt/a/proc/sys/fs/binfmt_misc private
├─/mnt/a/tmp private
├─/mnt/a/boot private
└─/mnt/a/mnt/a private
答案1
所以你认为umount
花时间等待某事(因为它在 或 中花费很少的CPU时间user
)sys
。让我们找出它为什么等待......
# perf trace -g -e sched:* umount2 -R /mnt/a
perf record
向我们展示了几个调度程序跟踪点;事实证明,最能说明问题的是sched:sched_switch
。
Samples: 21 of event 'sched:sched_switch', Event count (approx.): 21
Children Self Trace output ▒
- 100.00% 100.00% umount:1888 [120] D ==> swapper/3:0 [120] ▒
0 ▒
__umount2 ▒
entry_SYSCALL_64_fastpath ▒
sys_umount ▒
do_umount ▒
namespace_unlock ▒
synchronize_sched ▒
__wait_rcu_gp ▒
wait_for_completion ▒
schedule_timeout ▒
schedule ▒
__schedule ▒
__schedule
__wait_rcu_gp()
指 RCU 宽限期。 namespace_unlock()
infs/namespace.c
是某种形式的全局同步,其中包括synchronize_rcu()
.它等到全部“当前执行的 RCU 读端关键部分已完成”。 “RCU 宽限期延长了数毫秒……这种情况是在以读取为主的情况下使用 RCU 的经验法则的主要原因”。我认为挂载命名空间被认为是“主要读取”。
看来这“几毫秒”占了 34 次调用中每次平均等待时间 5 毫秒的原因umount2()
。