ファイルシステムをナビゲートするときに、Unixはユーザーの作業ディレクトリをどのように追跡しますか?


29

UNIXシステムのシェルにログインして、コマンドをタップし始めたとします。最初はユーザーのホームディレクトリで開始します~。そこからcdディレクトリまで行くかもしれませんDocuments

ここで作業ディレクトリを変更するコマンドは、直感的に理解するのが非常に簡単です:親ノードには、アクセスできる子ノードのリストがあり、おそらく(最適化された)検索のバリアントを使用して、子ノードの存在を見つけますユーザーが入力した名前を入力すると、作業ディレクトリがこれに合わせて「変更」されます。間違っている場合は修正してください。シェルが単純に「単純に」ユーザーの希望どおりにディレクトリへのアクセスを試行し、ファイルシステムが何らかのタイプのエラーを返すと、シェルはそれに応じて応答を表示する方が簡単かもしれません。

しかし、私が興味を持っているのは、ディレクトリ、つまり親、または親の親を上に移動するときに同じプロセスがどのように機能するかです。

私の未知の、おそらく「ブラインド」ロケーションを考えると、Documentsその名前のファイルシステムツリー全体にある可能性のある多くのディレクトリの1つで、Unixは次に配置する場所をどのように決定しますか?それを参照しpwdて調べますか?はいの場合pwd、現在のナビゲーション状態をどのように追跡しますか?


回答:


76

他の答えは単純化であり、それぞれがストーリーの一部のみを示しており、いくつかの点で間違っています。

作業ディレクトリを追跡する方法は2つあります。

  • プロセスごとに、そのプロセスを表すカーネル空間データ構造に、カーネルはそのプロセスの作業ディレクトリとルートディレクトリのvnodeへの2つのvnode参照を格納します。前者の参照はchdir()fchdir()システムコールによって設定され、後者はによって設定されchroot()ます。/procLinuxオペレーティングシステム上で間接的に、またはfstatFreeBSDなどのコマンドを介して、それらを見ることができます。

    %fstat -p $$ | head -n 5
    ユーザーCMD 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ルート/ 4 drwxr-xr-x 35 r
    

    パス名の解決が機能する場合、パスが相対パスであるか絶対パスであるかに応じて、参照されるvnodeのいずれかで開始されます。(…at()3番目のオプションとして、オープン(ディレクトリ)ファイル記述子によって参照されるvnodeでパス名の解決を開始できるシステムコールのファミリがあります。)

    マイクロカーネルユニックスでは、データ構造はアプリケーションスペースにありますが、これらのディレクトリへのオープンリファレンスを保持する原理は同じままです。

  • 内部的には、Z、Korn、Bourne Again、C、およびAlmquistシェルなどのシェル内で、シェルは内部文字列変数の文字列操作を使用して作業ディレクトリをさらに追跡します。呼び出す原因があるときはいつでもこれを行いますchdir()

    相対パス名に変更すると、文字列を操作してその名前を追加します。絶対パス名に変更すると、文字列が新しい名前に置き換えられます。どちらの場合も、削除する文字列...コンポーネントを調整し、シンボリックリンクを追跡してそれらをリンク先の名前に置き換えます。(たとえば、そのためのZシェルのコードは次のとおりです。)

    内部文字列変数の名前は、(またはCシェルの)という名前のシェル変数によって追跡されます。これは従来、シェルによって生成されたプログラムに環境変数(という名前)としてエクスポートされます。PWDcwdPWD

物事を追跡するこれらの2つの方法は、シェル組み込みコマンドの-Pand -Lオプション、cdおよびpwdシェルの組み込みpwdコマンドと(特に)などの/bin/pwdコマンドと組み込みコマンドの両方の違いによって明らかになります。pwdVIMおよび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

ご覧のとおり、「論理」作業ディレクトリの取得は、PWDシェル変数(またはシェルプログラムでない場合は環境変数)を調べるだけです。一方、「物理」作業ディレクトリを取得するには、getcwd()ライブラリ関数を呼び出すだけです。

オプションが使用される/bin/pwdときのプログラムの動作-Lはやや微妙です。継承した環境変数の値を信頼することはできませんPWD。結局のところ、シェルによって呼び出される必要はなく、介在するプログラムは、PWD環境変数が常に作業ディレクトリの名前を追跡するようにするシェルのメカニズムを実装していない可能性があります。または、誰かが私がちょうどそこでしたことをするかもしれません。

そのため、システムコールトレースで見られるように、(POSIX規格が言うように)で指定さPWDれた名前がnameと同じものを生成することを確認し.ます:

%ln -sac 
%(cd b;トラス/ 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プログラム)が複数の作業ディレクトリ(ドライブごとに1つ)の名前を追跡し、ディレクトリを変更するたびに追加または上書きするために使用する環境変数のセットがあります。

従来、UnixおよびLinuxオペレーティングシステムの場合とは異なり、Win32プログラムはこれらの環境変数をユーザーに表示しません。ただし、コマンドインタープリターのSETコマンドを特定の方法で使用するだけでなく、Windows NTで実行されているUnixライクなサブシステムでそれらを見ることができます。

参考文献


1
これは私が予想していたよりもはるかに多いです。ありがとう、さらに読んでくれてありがとう!
ReactingToAngularVues

doc.cat-v.org/plan_9/4th_edition/papers/lexnames..、Plan9のコンテキストでの問題のいくつかについて語っています
icarus

@JdeBP:たぶん何かが足りない。「内部では、…、bash、…、…内で、シェルはさらに、内部文字列変数の文字列操作を使用して作業ディレクトリを追跡します。…、文字列を調整して...コンポーネントを削除し、シンボリックリンクを追跡して、それらをリンク先の名前に置き換えます。…内部文字列変数の名前は、PWD…”(強調が追加された)という名前のシェル変数によって追跡されます。…(続き)
G-Manは「Reinstate Monica」と言う

(続き)…しかし、あなたの例は、コマンドへのシンボリックリンクであるにもかかわらずPWD= …/bを示しています。そのため、シェルはリンクを「追いかけません」。あなたは虚偽を述べましたか、それとも私は誤読しましたか?cd bbaa -> b
G-Manが「Reinstate Monica」と言う

私は単純にサイドポイントを詳しく説明し、詳細についてはコードを示しました。シンボリックリンクを追跡するかどうかをいつどのように決定するかについては、さまざまなシェルのマニュアルを参照してください。Zシェルは、決定式の一部であるシェルオプションを簡単に呼び出しますCHASE_LINKS
-JdeBP

1

カーネルはディレクトリ名またはファイル名を追跡しません。ファイルまたはディレクトリは、カーネルではiノードとデバイスのペアで表されます。、などのシステムコールはchdir()open()パラメータとしてパスを取ります。これは、絶対パス(例/etc/passwd)、または現在のディレクトリからの相対パス(例:Documents..)です。プロセスが実行さchdir("Documents")れるDocumentsと、現在の作業ディレクトリで検索が行われ、プロセスの作業ディレクトリがこのディレクトリを参照するように更新されます。カーネルの観点からは、「..」という名前には特別なものはありません。これ..は、親ディレクトリを参照するファイルシステムの規則にすぎません。

このgetcwd()関数はシステムコールではなく、ルートディレクトリまで処理する必要があるライブラリ関数であり、途中のパスコンポーネントの名前を記録します。


0

おもしろいことに、伝統的にcd ..は、よりはるかにシンプルですpwd。名前のディレクトリは、..ファイルシステムに明示的に配置されています。システムは現在のディレクトリのデバイス/ inodeを追跡するためcd ..、より正確には、システムコールchdir("..")は現在のディレクトリのinodeに属するファイル内の名前「..」を検索し、現在のディレクトリのdevice / inodeを値が見つかりました。

pwd(より正確には/bin/pwd..リンクを連続してたどり、それぞれのディレクトリを読み取り、それがどこから来たのかを見つけ、ルートディレクトリ(特に..エントリを含まない)に到達するまでそれらの名前のリストを逆に組み立てます。

これが元の低レベルの基本動作です。実際のシェルコマンドは、pwd代わりに現在のパス名をキャッシュするさまざまな手法に依存しています。しかし、コアでは、実際に知られているのはそのiノードだけです。これは、ディレクトリの移動にシンボリックリンクが使用されると、現在のシェルとシステムの現在の作業ディレクトリ名の概念/bin/pwdが異なる可能性があることを意味します。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.