unshare新建挂载命名空间下Tmux会话bind挂载偶发失效问题
问题核心原因
- Tmux的C/S架构是触发问题的核心:Tmux采用客户端-服务端设计,你在命令行输入
tmux启动的只是轻量客户端进程,所有终端窗口、会话实际由后台长驻的tmux server进程承载,窗口内运行的所有命令都是tmux server的子进程,和你启动的tmux客户端进程没有父子关系。
客户端启动时会优先连接当前用户已经在运行的tmux server,只有检测到无存活server时才会拉起新的server进程;客户端主动退出时默认不会终止server,会话会一直保留在后台。 - unshare默认的挂载命名空间行为:执行
unshare -r --mount时确实会为目标进程创建独立的挂载命名空间,但默认不会修改挂载树的传播属性,新命名空间默认从父命名空间(也就是你当前登录用户的默认命名空间)克隆所有挂载点,且挂载事件会按照systemd默认配置的MS_SHARED标记在关联命名空间间传递。 - 第一次执行脚本的异常链路:
- 脚本先在unshare创建的临时挂载命名空间内执行
mount --bind ~/a ~/b,此时绑定挂载仅在当前脚本进程所在的临时命名空间生效 - 脚本启动tmux客户端,因为第一次运行无现存server,客户端尝试拉起新的tmux server。server启动时会执行双fork完成daemonize(脱离终端、脱离当前进程组),最终被systemd用户实例收养,脱离了你刚创建的临时挂载命名空间,回到用户默认的原始挂载命名空间运行
- 你进入tmux会话后看到的所有进程都运行在tmux server所在的原始命名空间,自然看不到临时命名空间里的绑定挂载
- 此时你在tmux内手动执行mount绑定命令,实际是在用户原始命名空间里完成了挂载;退出tmux客户端后server不会关闭,这个绑定挂载会一直留在原始命名空间里
- 下次再执行
unshare -r --mount ~/run时,新的临时挂载命名空间会从已经存在绑定挂载的原始命名空间克隆,所以你会看到绑定挂载“正常生效” - 当你移动
~/a目录,原始命名空间里的绑定挂载因为源路径失效被内核自动卸载,下次再执行脚本就会回到初始的异常状态,问题复现
- 脚本先在unshare创建的临时挂载命名空间内执行
- 为什么替换为/bin/bash就完全正常:bash是单进程前台程序,不会执行daemonize操作脱离当前进程的命名空间,你在bash交互环境中看到的所有挂载状态,都属于run脚本所在的临时unshare命名空间,自然能看到脚本里提前执行的绑定挂载。
可落地的修复方案
你只需要保证tmux server运行在你创建的隔离挂载命名空间内,同时阻止挂载事件向外泄露到原始命名空间即可,两种修改方式选一个就行:
- 修改unshare启动参数,创建命名空间时直接将挂载传播属性设为私有:
同时修改run脚本,强制tmux启动时不连接已存在的全局server,在当前命名空间内启动独立server:unshare -r --mount --propagation private ~/run#!/bin/bash mount --bind ~/a ~/b # 指定独立socket文件启动新server,避免连接后台已存在的全局tmux server tmux -L isolated_unshare new-session - 如果你不想修改unshare参数,也可以直接在run脚本开头手动设置挂载传播属性:
#!/bin/bash # 将当前命名空间内所有挂载点设为私有,阻止挂载事件传递到父命名空间 mount --make-rprivate / mount --bind ~/a ~/b tmux -L isolated_unshare new-session
内容的提问来源于stack exchange,提问作者Hufflet
相关产品推荐
相关产品推荐

