Comment Unix suit-il le répertoire de travail d'un utilisateur lors de la navigation dans le système de fichiers?

29

Supposons que je me connecte à un shell sur un système Unix et que je commence à taper des commandes. Je commence d'abord dans le répertoire personnel de mon utilisateur ~. Je pourrais partir de là cdvers le répertoire Documents.

La commande pour modifier le répertoire de travail ici est très simple à comprendre intuitivement: le nœud parent a une liste de nœuds enfants auxquels il peut accéder, et il utilise probablement une variante (optimisée) d'une recherche pour localiser l'existence d'un nœud enfant avec le nommez l'utilisateur entré, et le répertoire de travail est alors "modifié" pour correspondre à cela - corrigez-moi si je me trompe. Il peut même être plus simple que le shell essaie simplement "naïvement" d'essayer d'accéder au répertoire exactement selon les souhaits de l'utilisateur et lorsque le système de fichiers renvoie un certain type d'erreur, le shell affiche une réponse en conséquence.

Ce qui m'intéresse cependant, c'est comment fonctionne le même processus lorsque je navigue dans un répertoire, c'est-à-dire vers un parent ou le parent d'un parent.

Étant donné mon emplacement inconnu, vraisemblablement «aveugle» de Documents, l'un des nombreux répertoires de l'arborescence du système de fichiers portant ce nom, comment Unix détermine-t-il où je dois être placé ensuite? Fait-il référence à cela pwdet l'examine-t-il? Si oui, comment suit pwd-il l'état de navigation actuel?

ReactingToAngularVues
la source
1
Voir aussi Récursion des liens symboliques - qu'est-ce qui la rend "réinitialisée"? où beaucoup de choses sont expliquées
Stéphane Chazelas

Réponses:

76

Les autres réponses sont des simplifications excessives, chacune ne présentant que des parties de l'histoire, et elles sont erronées sur quelques points.

Il existe deux façons de suivre le répertoire de travail:

  • Pour chaque processus, dans la structure de données de l'espace noyau qui représente ce processus, le noyau stocke deux références vnode aux vnodes du répertoire de travail et au répertoire racine de ce processus. La première référence est définie par les appels système chdir()et fchdir(), la seconde par chroot(). On peut les voir indirectement dans /procsur les systèmes d'exploitation Linux ou via la fstatcommande sur FreeBSD et similaires:

    % fstat -p $$ | head -n 5
    UTILISATEUR CMD PID FD MONTAGE MODE INUM SZ | DV R / W
    JdeBP zsh 92648 texte / 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
    % 

    Lorsque la résolution de chemin d'accès fonctionne, elle commence à l'un ou l'autre de ces vnodes référencés, selon que le chemin est relatif ou absolu. (Il existe une famille d' …at()appels système qui permettent à la résolution de chemin d'accès de commencer au niveau du vnode référencé par un descripteur de fichier ouvert (répertoire) comme troisième option.)

    Dans les micro-noyaux, la structure des données se trouve dans l'espace d'application, mais le principe de la tenue de références ouvertes à ces répertoires reste le même.

  • En interne, dans des shells tels que le shell Z, Korn, Bourne Again, C et Almquist, le shell garde en outre la trace du répertoire de travail en utilisant la manipulation de chaîne d'une variable de chaîne interne. Il le fait chaque fois qu'il a un appel à faire chdir().

    Si l'on change pour un chemin d'accès relatif, il manipule la chaîne pour ajouter ce nom. Si l'on passe à un nom de chemin absolu, il remplace la chaîne par le nouveau nom. Dans les deux cas, il ajuste la chaîne à supprimer .et les ..composants et à chasser les liens symboliques en les remplaçant par leurs noms liés. ( Voici le code du shell Z pour cela , par exemple.)

    Le nom dans la variable de chaîne interne est suivi par une variable shell nommée PWD(ou cwddans les shells C). Ceci est traditionnellement exporté en tant que variable d'environnement (nommée PWD) vers des programmes générés par le shell.

Ces deux méthodes de choses de suivi sont révélées par les -Pet -Loptions à la cdet pwdshell commandes intégrées, et par les différences entre les coquilles intégrées pwdcommandes et à la fois la /bin/pwdcommande et le haut- pwdcommandes des choses comme (entre autres) VIM et 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 = / bonjour / là / bin / pwd -L)
/ usr / home / JdeBP / a
% 

Comme vous pouvez le voir: obtenir le répertoire de travail "logique" consiste à regarder la PWDvariable shell (ou la variable d'environnement si l'on n'est pas le programme shell); tandis que l'obtention du répertoire de travail "physique" consiste à appeler la getcwd()fonction de bibliothèque.

Le fonctionnement du /bin/pwdprogramme lorsque l' -Loption est utilisée est quelque peu subtil. Il ne peut pas faire confiance à la valeur de la PWDvariable d'environnement dont il a hérité. Après tout, il n'a pas besoin d'être invoqué par un shell et les programmes intermédiaires peuvent ne pas avoir implémenté le mécanisme du shell pour que la PWDvariable d'environnement suive toujours le nom du répertoire de travail. Ou quelqu'un peut faire ce que j'ai fait juste là.

Donc, ce qu'il fait est (comme le dit la norme POSIX) de vérifier que le nom donné en PWDdonne la même chose que le nom ., comme on peut le voir avec une trace d'appel système:

% 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, taille = 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, taille = 158, blksize = 10240}) = 0 (0x0)
 stat (".", {mode = drwxr-xr-x, inode = 120932, taille = 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 'Aucun fichier ou répertoire de ce type' 
__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, taille = 2, blksize = 131072}) = 0 (0x0) 
stat (". ", {Mode = drwxr-xr-x, inode = 120932 , taille = 2, blksize = 131072}) = 0 (0x0)
/ usr / home / JdeBP / c
%

Comme vous pouvez le voir: il n'appelle que getcwd()s'il détecte un décalage; et il peut être trompé en définissant PWDune chaîne qui nomme effectivement le même répertoire, mais par une route différente.

La getcwd()fonction de bibliothèque est un sujet à part entière. Mais pour précis:

  • À l'origine, c'était purement une fonction de bibliothèque, qui a construit un chemin d'accès du répertoire de travail jusqu'à la racine en essayant à plusieurs reprises de rechercher le répertoire de travail dans le ..répertoire. Il s'est arrêté lorsqu'il a atteint une boucle où ..était le même que son répertoire de travail ou lorsqu'une erreur s'est produite lors de l'ouverture du suivant ... Ce serait beaucoup d'appels système sous les couvertures.
  • De nos jours, la situation est légèrement plus complexe. Sur FreeBSD, par exemple (ce qui est vrai pour d' autres systèmes d'exploitation, ainsi), il est un véritable appel système, comme vous pouvez le voir dans la trace d'appel système donné plus tôt. Toute la traversée du répertoire de travail vnode jusqu'à la racine se fait en un seul appel système, qui tire parti de choses comme l'accès direct du code en mode noyau au cache d'entrée de répertoire pour effectuer les recherches de composants de chemin beaucoup plus efficacement.

    Cependant, notez que même sur FreeBSD et ces autres systèmes d'exploitation, le noyau ne garde pas la trace du répertoire de travail avec une chaîne.

Naviguer vers ..est à nouveau un sujet à part entière. Un autre précis: Bien que les répertoires conventionnellement (bien que, comme cela a déjà été mentionné, cela ne soit pas requis) contiennent un réel ..dans la structure de données du répertoire sur le disque, le noyau suit le répertoire parent de chaque répertoire vnode lui-même et peut ainsi naviguer vers le ..vnode de n'importe quel directeur de travail. Ceci est quelque peu compliqué par le point de montage et les mécanismes racine modifiés, qui dépassent le cadre de cette réponse.

De côté

Windows NT fait en fait une chose similaire. Il existe un seul répertoire de travail par processus, défini par l' SetCurrentDirectory()appel d'API et suivi par processus par le noyau via un descripteur de fichier ouvert (interne) vers ce répertoire; et il existe un ensemble de variables d'environnement que les programmes Win32 (pas seulement les interpréteurs de commandes, mais tous les programmes Win32) utilisent pour suivre les noms de plusieurs répertoires de travail (un par lecteur), en les ajoutant ou en les écrasant chaque fois qu'ils changent de répertoire.

Classiquement, contrairement au cas des systèmes d'exploitation Unix et Linux, les programmes Win32 n'affiche pas ces variables d'environnement pour les utilisateurs. On peut parfois les voir dans des sous-systèmes de type Unix fonctionnant sous Windows NT, ainsi qu'en utilisant les commandes des interprètes de SETcommande d'une manière particulière.

Lectures complémentaires

JdeBP
la source
1
C'est bien plus que ce à quoi je m'attendais. Merci et merci encore pour la lecture!
ReactingToAngularVues
doc.cat-v.org/plan_9/4th_edition/papers/lexnames parle de certains des problèmes avec ..dans le contexte de Plan9,
icare
@JdeBP: Peut-être que je manque quelque chose. Vous dites: « En interne, dans ..., bash, ... et ..., la coque en outre conserve la trace du répertoire de travail en utilisant la manipulation de chaînes d'une variable de chaîne interne. …, Il ajuste la chaîne à supprimer .et les ..composants et à chasser les liens symboliques en les remplaçant par leurs noms liés. … Le nom dans la variable de chaîne interne est suivi par une variable shell nommée PWD… ”(soulignement ajouté). … (Suite)
G-Man dit «Réintègre Monica»
(Suite)… Mais votre exemple montre PWD= …/baprès une cd bcommande, même s'il bs'agit d'un lien symbolique vers a- donc le shell ne «poursuit» pas le a -> blien. Avez-vous déformé ou ai-je mal lu?
G-Man dit `` Réintègre Monica ''
J'ai simplement glissé sur un point latéral et je vous ai indiqué le code pour plus de détails. Consultez les différents manuels des shells pour savoir quand et comment ils décident de poursuivre ou non des liens symboliques. La coquille de Z appelle commodément son option de coque qui est une partie de la formule de décision CHASE_LINKS.
JdeBP
1

Le noyau ne garde pas trace des noms de répertoires ou de fichiers; un fichier ou un répertoire est représenté dans le noyau par une paire inode / périphérique. Les appels système comme chdir(), open(), etc. prennent un chemin en tant que paramètre, qui peut être absolue (par exemple /etc/passwd), ou par rapport au répertoire courant (exemples: Documents, ..). Lorsqu'un processus s'exécute chdir("Documents"), une recherche est effectuée Documentsdans le répertoire de travail actuel et le répertoire de travail du processus est mis à jour pour faire référence à ce répertoire. Du point de vue du noyau, il n'y a rien de spécial dans le nom "..", c'est juste une convention dans le système de fichiers qui ..fait référence au répertoire parent.

La getcwd()fonction n'est pas un appel système, mais une fonction de bibliothèque qui doit remonter jusqu'au répertoire racine, en enregistrant les noms des composants du chemin sur le chemin.

Johan Myréen
la source
0

Fait intéressant, la tradition cd ..est beaucoup plus simple quepwd . Les répertoires nommés ..sont placés explicitement dans le système de fichiers. Le système garde la trace du périphérique / inode du répertoire courant, donc cd ..ou plus précisément l'appel système chdir("..")implique simplement de rechercher le nom ".." dans le fichier appartenant à l'inode du répertoire actuel et de changer le périphérique / inode du répertoire actuel en valeur trouvée là-bas.

pwd (plus précisément /bin/pwd ) suit les ..liens successivement et lit les répertoires respectifs jusqu'à trouver l'inode d'où il vient, assemblant la liste de ces noms à l'envers jusqu'à ce qu'il atteigne le répertoire racine (notamment ne contenant pas d' ..entrée).

Maintenant, c'est le comportement de base de bas niveau d'origine. Les commandes shell réelles pwdreposent plutôt sur une variété de techniques de mise en cache du nom de chemin actuel. Mais au fond, ce n'est que son inode qui est réellement connu. Cela implique qu'une fois les liens symboliques utilisés pour naviguer dans les répertoires, les notions de nom de répertoire de travail actuel du shell actuel et du système /bin/pwdpeuvent diverger.


la source