在旁边

在旁边

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

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

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

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

答案1

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

跟踪工作目录的方式:

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

    % fstat -p $$|head -n 5
    用户命令 PID FD 安装 INUM 模式 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 root / 4 drwxr-xr-x 35 r
    %

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

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

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

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

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

这两种跟踪事物的方法是通过和shell 内置命令的-P-L选项来揭示的,以及 shell 的内置命令与诸如(除其他外)之类的命令和内置命令之间的差异VIM 和 NeoVIM。cdpwdpwd/bin/pwdpwd

% mkdir a ;溶酶体
% (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
%

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

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

所以它所做的是(正如 POSIX 标准所说)检查 中给出的 name 是否PWD产生与 name 相同的东西.,如系统调用跟踪所示:

% 囊内
% (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以特定方式使用命令解释器的命令来看到它们。

进一步阅读

答案2

内核不跟踪目录或文件名;文件或目录在内核中由索引节点/设备对表示。chdir()、等系统调用open()将路径作为参数,该路径可以是绝对路径(例如/etc/passwd),也可以是相对于当前目录的路径(例如:Documents..)。当进程执行时chdir("Documents"),会在当前工作目录中进行查找Documents,并更新进程的工作目录以引用该目录。从内核的角度来看,名称“..”没有什么特别的,它只是文件系统中..引用父目录的约定。

getcwd()函数不是系统调用,而是一个库函数,它必须向上运行到根目录,并记录沿途路径组件的名称。

答案3

有趣的是,传统cd ..上比pwd.指定的目录..显式放置到文件系统中。系统跟踪当前目录的设备/索引节点,因此cd ..或更准确地说,系统调用chdir("..")只需要在属于当前目录索引节点的文件中查找名称“..”,并将当前目录的设备/索引节点更改为在那里发现的价值。

pwd(更准确地说/bin/pwd..连续跟踪链接并读取相应的目录,直到找到它来自的 inode,反向组装这些名称的列表,直到到达根目录(特别是不包含条目..)。

现在这就是原来的低级基本行为。实际的 shell 命令pwd反而依赖于缓存当前路径名的各种技术。但从本质上讲,真正已知的只是它的索引节点。这意味着一旦使用符号链接来导航目录,当前 shell 和系统的当前工作目录名称概念/bin/pwd可能会有所不同。

相关内容