Unix 在浏览文件系统时如何跟踪用户的工作目录?

Rea*_*ues 29 filesystems pwd

假设我登录到 unix 系统上的 shell 并开始窃听命令。我最初从用户的主目录开始~。我可能会从那里cd到目录Documents

这里更改工作目录的命令非常简单直观地理解:父节点有一个它可以访问的子节点列表,并且大概它使用搜索的(优化的)变体来定位子节点的存在输入用户的名称,然后“更改”工作目录以匹配此名称 - 如果我错了,请纠正我。更简单的是,shell 只是“天真地”尝试完全按照用户的意愿尝试访问目录,并且当文件系统返回某种类型的错误时,shell 会相应地显示响应。

然而,我感兴趣的是,当我向上导航一个目录时,相同的过程是如何工作的,即导航到父级或父级的父级。

鉴于我未知的,大概是“盲”位置Documents,整个文件系统树中可能有许多目录之一具有该名称,Unix 如何确定我接下来应该放置的位置?它是否参考pwd并检查了它?如果是,如何pwd跟踪当前导航状态?

Jde*_*eBP 77

其他答案过于简单化,每个答案都只展示了故事的一部分,并且在几点上是错误的。

两种跟踪工作目录的方式:

  • 对于每个进程,在代表该进程的内核空间数据结构中,内核存储两个对工作目录和该进程根目录的 vnode 的 vnode 引用。前者引用由chdir()fchdir()系统调用设置,后者由chroot()。人们可以/proc在 Linux 操作系统上或通过fstatFreeBSD 等上的命令间接看到它们:

    % fstat -p $$|head -n 5
    USER CMD PID FD MOUNT INUM MODE SZ|DV R/W
    JdeBP zsh 92648 文本 / 24958 -r-xr-xr-x 702360 r
    JdeBP zsh 92648 ctty /dev 148 crw--w---- pts/4 rw
    JdeBP zsh 92648 wd /usr/home/JdeBP 4 drwxr-xr-x 124 r
    JdeBP zsh 92648 根 / 4 drwxr-xr-x 35 r
    % 

    当路径名解析运行时,它从这些引用的 vnode 中的一个或另一个开始,具体取决于路径是相对的还是绝对的。(有一系列…at()系统调用允许路径名解析从打开(目录)文件描述符作为第三个选项引用的 vnode 开始。)

    在微内核 Unices 中,数据结构在应用程序空间中,但保持对这些目录的开放引用的原则保持不变。

  • 在内部,在诸如 Z、Korn、Bourne Again、C 和 Almquist shell 之类的 shell 中,shell使用内部字符串变量的字符串操作来跟踪工作目录。只要有理由调用 ,它就会执行此操作chdir()

    如果更改为相对路径名,它会操作字符串以附加该名称。如果更改为绝对路径名,它将用新名称替换该字符串。在这两种情况下,调整串删除...组件和追逐符号链接与其链接到的名称替换它们。(例如,这是 Z shell 的代码。)

    内部字符串变量中的名称由命名的shell 变量PWD(或cwd在 C shell 中)跟踪。这通常作为环境变量(名为PWD)导出到 shell 生成的程序。

这两种跟踪事物的方法是通过shell 内置命令的-P-L选项cd以及pwdshell 的内置pwd命令与/bin/pwd命令和内置命令之间的差异来揭示的pwd(等等) VIM 和 NeoVIM。

% mkdir a ; ln -sab 
% (cd b; pwd; /bin/pwd; printenv PWD)
/usr/home/JdeBP/b
/usr/home/JdeBP/a
/usr/home/JdeBP/b
% (cd b; pwd -P; /bin/pwd -P)
/usr/home/JdeBP/a
/usr/home/JdeBP/a
% (cd b; pwd -L; /bin/pwd -L)
/usr/home/JdeBP/b
/usr/home/JdeBP/b
% (cd -P b; pwd; /bin/pwd; printenv PWD)
/usr/home/JdeBP/a
/usr/home/JdeBP/a
/usr/home/JdeBP/a
% (cd b; PWD=/hello/there /bin/pwd -L)
/usr/home/JdeBP/a
% 

如您所见:获取“逻辑”工作目录是查看PWDshell变量(如果不是shell程序,则为环境变量)的问题;而获取“物理”工作目录是调用getcwd()库函数的问题。

使用/bin/pwd-L选项时程序的操作有些微妙。它不能信任PWD它继承的环境变量的值。毕竟,它不需要被 shell 调用,而且中间程序可能没有实现 shell 使PWD环境变量始终跟踪工作目录名称的机制。或者有人可能会做我刚才在那里做的事情。

所以它所做的是(如 POSIX 标准所说)检查给出的名称是否与 namePWD产生相同的内容.,如系统调用跟踪所示:

% ln -sac 
% (cd b; truss /bin/pwd -L 3>&1 1>&2 2>&3 | grep -E '^stat|__getcwd') 
stat("/usr/home/JdeBP/b",{ mode=drwxr-xr-x ,inode=120932,size=2,blksize=131072 }) = 0 (0x0) 
stat(".",{ mode=drwxr-xr-x ,inode=120932,size=2,blksize =131072}) = 0 (0x0)
/usr/home/JdeBP/b
% (cd b; PWD=/usr/local/etc truss /bin/pwd -L 3>&1 1>&2 2>&3 | grep -E '^stat|__getcwd') 
stat("/usr/local/etc" ,{ mode=drwxr-xr-x ,inode=14835,size=158,blksize=10240 }) = 0 (0x0) 
stat(".",{ mode=drwxr-xr-x ,inode=120932,size=2 ,blksize=131072 }) = 0 (0x0)
__getcwd("/usr/home/JdeBP/a",1024) = 0 (0x0)
/usr/home/JdeBP/a
% (cd b; PWD=/hello/there truss /bin/pwd -L 3>&1 1>&2 2>&3 | grep -E '^stat|__getcwd') 
stat("/hello/there",0x7fffffffe730) ERR #2 '没有
那个文件或目录' __getcwd("/usr/home/JdeBP/a",1024) = 0 (0x0)
/usr/home/JdeBP/a
% (cd b; PWD=/usr/home/JdeBP/c truss /bin/pwd -L 3>&1 1>&2 2>&3 | grep -E '^stat|__getcwd') 
stat("/usr/home/ jdeBP/c",{ mode=drwxr-xr-x ,inode=120932,size=2,blksize=131072 }) = 0 (0x0) 
stat(".",{ mode=drwxr-xr-x ,inode=120932 ,size=2,blksize=131072 }) = 0 (0x0)
/usr/home/JdeBP/c
%

如您所见:它仅getcwd()在检测到不匹配时才调用;并且可以通过设置PWD为确实命名相同目录的字符串来愚弄它,但是通过不同的路由。

getcwd()库函数是在自己的权利的主体。但具体来说:

  • 最初它纯粹是一个库函数,它通过反复尝试在目录中查找工作目录来构建从工作目录到根目录的路径名..。当它到达..与其工作目录相同的循环或尝试打开下一个时出现错误时,它停止..。这将是大量的系统调用。
  • 现在的情况稍微复杂一些。例如,在 FreeBSD 上(对于其他操作系统也是如此),它一个真正的系统调用,正如您在前面给出的系统调用跟踪中看到的那样。从工作目录 vnode 到根目录的所有遍历都在单个系统调用中完成,它利用内核模式代码直接访问目录条目缓存等功能来更有效地执行路径名组件查找。

    但是,请注意,即使在 FreeBSD 和其他操作系统上,内核也不会使用字符串跟踪工作目录。

导航到..本身又是一个主题。另一个原则:虽然传统上的目录(虽然,正如已经提到的,这不是必需的)..在磁盘上的目录数据结构中包含一个实际的目录,但内核会跟踪每个目录 vnode 本身的父目录,因此可以导航到..任何目录的 vnode工作目录。挂载点和更改的根机制使这有些复杂,这超出了本答案的范围。

在旁边

Windows NT 实际上做了类似的事情。每个进程有一个工作目录,由SetCurrentDirectory()API 调用设置并由内核通过该目录的(内部)打开文件句柄跟踪每个进程;Win32 程序(不仅仅是命令解释器,而是所有Win32 程序)使用一组环境变量来跟踪多个工作目录(每个驱动器一个)的名称,在它们更改目录时附加或覆盖它们。

通常,与 Unix 和 Linux 操作系统的情况不同,Win32 程序不会向用户显示这些环境变量。但是,有时可以在运行在 Windows NT 上的类 Unix 子系统中看到它们,也可以通过SET以特定方式使用命令解释器的命令来看到它们。

进一步阅读