V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
Distributions
Ubuntu
Fedora
CentOS
中文资源站
网易开源镜像站
rev1si0n
V2EX  ›  Linux

tmpfs 挂载后之前占用目录的进程无法发现刚 mount 目录中的文件

  •  
  •   rev1si0n · 172 天前 · 886 次点击
    这是一个创建于 172 天前的主题,其中的信息可能已经有所发展或是发生改变。

    问题:在终端 A 中 ls -l 只能发现之前创建的两个文件,并不能发现 file03 ,请问,是否有办法在不结束终端 A 且不切换目录的情况下,在终端 A 中显示 file03 ?如果不行,是因为什么?或者,是否有其他替代方法?谢谢各位大佬解答。

    过程:开启两个终端

    终端 A:

    mkdir -p /tmp/workdir
    cd /tmp/workdir
    touch file01
    touch file02
    ls -la
    

    终端 B:

    sudo mount -t tmpfs tmpfs /tmp/workdir
    cd /tmp/workdir
    
    touch file03
    ls -la
    
    27 条回复    2023-11-08 12:41:20 +08:00
    maybeonly
        1
    maybeonly  
       172 天前
    可能需要在挂载点变动后重新 cd 进去一下
    timewarp
        2
    timewarp  
       172 天前
    尽管终端 B 在 workdir 上挂了个文件系统,遮蔽了原来的目录视图,但是终端 A 的 shell 的 cwd 仍然指向原始视图,所以 A 只能看到原来的文件列表,你只需要做一次 cd ../ && cd -就能重新做一次路径查找,cwd 会遵循新的视图而设定。
    rev1si0n
        3
    rev1si0n  
    OP
       172 天前
    @maybeonly 感谢解答。更新了一下问题,且不能重新 cd 🤦🏻‍♀️
    zhlxsh
        4
    zhlxsh  
       172 天前 via iPhone
    1. 重新 cd 一下
    2. b 操作在后的话,mount 会把 a 创建的文件屏蔽掉,umount 才恢复
    ho121
        5
    ho121  
       172 天前
    直接 `bash` 重开一个子 shell
    timewarp
        6
    timewarp  
       172 天前
    @rev1si0n 那就直接敲命令 ls /tmp/workdir 就行了,核心就是你当前这个 A 的 bash 进程的 cwd 已经和原始 workdir 的目录项绑定了,尽管这个目录项现在成了挂载点,但是不重新做一次路径查找的话内核是无法知道这个变动的,要想进行一次路径查找,又不想 cd 目录,那就只能让其他进程代劳了,比如 ls 一下/tmp/workdir
    mokiki
        7
    mokiki  
       172 天前
    你先说出原始需求,才有可能给出替代方法
    zbinlin
        8
    zbinlin  
       172 天前
    感觉是 X-Y 问题,你原来的问题是什么?
    rev1si0n
        9
    rev1si0n  
    OP
       172 天前
    @maybeonly
    @timewarp
    @zhlxsh

    感谢解答。这是实际运行程序中的一个问题,我试一下在主进程重新直接 chdir 是否可行,因为有些对象可能已经占有了该目录的**引用**
    timewarp
        10
    timewarp  
       172 天前
    @rev1si0n 无须担心其他进程对此目录的“占用”,这个在内核层面只是加了个引用计数,效果只是删除目录时内核里的数据结构不释放。对路径查找没有影响的,只要重新做查找就会看到新内容。
    rev1si0n
        11
    rev1si0n  
    OP
       172 天前
    @mokiki
    @zbinlin
    @timewarp

    就是重新 cd 这一步可能做不到,因为那部分在第三方 sdk 里,第三方 sdk 在程序开始时就 opendir 了(作为某个静态的对象),所以可能一直使用的都是我 mount 之前的目录结构。刚浅试了一下似乎主进程去 chdir 到 mount 的目录再回来不会影响到这个已经占有 dir 的对象但是又不能重启主进程...
    timewarp
        12
    timewarp  
       172 天前
    @rev1si0n 所以你的需求是 sdk 看到的是旧内容,但是主进程看到的是新内容,你也想让 sdk 现在看到主进程看到的新内容吗
    rev1si0n
        13
    rev1si0n  
    OP
       172 天前
    @timewarp 对的
    BlackHole1
        14
    BlackHole1  
       172 天前
    尝试挂载成 overlayfs 呢
    julyclyde
        15
    julyclyde  
       172 天前
    @timewarp ls 不可能有效吧
    timewarp
        16
    timewarp  
       172 天前 via Android
    @julyclyde 有效的,只要重新做路径查找就能看到新的
    timewarp
        17
    timewarp  
       172 天前 via Android
    @rev1si0n 你一定要在目录上挂载一个文件系统吗
    julyclyde
        18
    julyclyde  
       172 天前
    @timewarp 这一定是你的幻觉
    timewarp
        19
    timewarp  
       172 天前 via Android
    为今之计建议再建立一层目录,形成/tmp/common/workdir 的结构,主进程和 sdk 都切换到 common 这一层,然后主进程在 workdir 挂载文件系统,sdk 每次读取时去子目录 workdir 下找文件。这样不管 workdir 怎么被遮盖,sdk 每次都要做目录查找,看到的一定是最新的
    timewarp
        20
    timewarp  
       172 天前 via Android
    @julyclyde `ls . `这个命令不会做路径查找,而是直接获取进程 cwd ,所以看到的是老内容,但是 ls /tmp/workdir 需要解析路径,在 open 系统调用时会做 walk path ,路径分量解析到 workdir 这一层发现 dentry 设置了挂载标志,表示这是个挂载点,于是路径查找下降到子文件系统,就看到了新内容。
    julyclyde
        21
    julyclyde  
       172 天前
    @timewarp 建议了解一下/proc/pid/mounts
    你可以单独发帖子说一下你的过程,我去那边指出你的错误。在这里有点跑题了
    LindsayZhou
        22
    LindsayZhou  
       172 天前
    @rev1si0n 不太了解内核,如果答错请指正

    如果只是说 cd 这个命令,那只要能调用 chdir syscall 的其他命令都一样的。如果说 chdir 系统调用都不能用,大概率是无解的吧。

    bash 启动子进程的时候,会继承 bash 进程的工作路径。
    这个数据存储在 bash 进程的 current(struct task_struct *) -> fs(struct fs_struct *) -> pwd(struct path) 里,struct path 有两个成员 vfsmount 和 dentry ,都是和文件系统强相关的。
    vfsmount 直接就是文件系统的挂载信息,而 dentry 里有 inode 之类各个文件系统独立的信息,不修改 pwd 对象大概是不行的。
    timewarp
        23
    timewarp  
       172 天前   ❤️ 2
    @julyclyde 你真的是.....那我来给你讲讲代码吧...

    首先,你讲的/proc/pid/mounts 是 pid 这个进程所在的命名空间里挂载的所有文件系统列表,跟题主的问题没有任何关系,题主没有涉及 namespace 的切换,主进程和 sdk (子进程之类的)处于同一个 namespace 。

    其次,让我们看一下 vfs 层的代码,

    path_lookupat 函数负责解析路径分量,path_init 负责初始化路径分量的解析起点,对于 ls . 这个命令来讲,我们把起点设定为 fs->pwd ,也就是/proc/pid/cwd 的值。放在题主的环境里起点就是 workdir 这个父目录
    然后函数进入 link_path_walk 开始正式解析路径分量,由于我们的入参 name=".",所以此函数一个循环直接结束,不会进一步进入 walk_component 函数了。link_path_walk 返回 0 ,此时入参 nd 直接把父目录 workdir 带回了。上层函数 path_lookupat 直接调用 lookup_last 把父目录相关的 dentry 和 inode 准备好,然后层层返回,路径查找结束。

    那么再来看看 ls /tmp/workdir 的情况,路径分量解析起点是/,即父文件系统的根目录。然后 name="/tmp/workdir"被 link_path_walk 逐段解析,由于/后是 tmp 字符串,所以调用 walk_component 首先解析 tmp ,这个环节无事发生,再然后使用 walk_component 继续进入 workdir 这个子目录,此时发现 workdir 的 dentry 上有个标志位 DCACHE_MOUNTED(mount 系统调用是给 workdir 这个挂载点设置的,参见函数 d_set_mounted),这说明了什么?说明这是个挂载点,于是 lookup_mnt 被调用,路径查找流程开始“下降”到子文件系统,所以我们要解析的下一个分量不再是父文件系统的 workdir 目录,而是子文件系统的根目录。于是我们就看到了新的内容。


    对比以上两个过程,我们会发现当 ls . 的时候,由于. 是个特殊的分量,内核会特殊的处理,所以不会走 walk_component ,也就没机会检测到当前目录上的 DCACHE_MOUNTED 标志。
    而我们 ls 一个/tmp/workdir 的时候,迫使内核重新走一遍路径分量解析,它就能发现 DCACHE_MOUNTED 标志。

    这就是为什么 ls . 永远看到旧内容,而 ls ../workdir 或者 ls /tmp/workdir 却可以看到新内容
    LindsayZhou
        24
    LindsayZhou  
       172 天前
    @LindsayZhou 续 #22
    挂个 eBPF 程序进到内核空间把程序的 pwd 都改了 (狗头
    rev1si0n
        25
    rev1si0n  
    OP
       172 天前
    @julyclyde
    @timewarp
    @LindsayZhou

    好了谢谢各位佬,学的也太扎实了也。我觉得这可能是不大可能了😂,佬们不要吵了吧
    julyclyde
        26
    julyclyde  
       171 天前
    @timewarp 经实验,你说的是对的
    我忘记全路径这个事了

    cwd 的情况(也就是早已打开了旧目录的情况)确实无解。ls 相当于另外打开一次,是可以走全流程的
    julyclyde
        27
    julyclyde  
       171 天前
    @timewarp 关键问题是
    虽然 ls 完整路径可以看到新内容
    但是 cwd 依然还是旧路径啊。这个方法并不能解决 OP 的提问“不结束终端 A 且不切换目录的情况下“

    如果程序里写死了”本地目录下某文件“那就无论如何也不可能让它访问到新的 mount 了
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   1160 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 30ms · UTC 18:00 · PVG 02:00 · LAX 11:00 · JFK 14:00
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.