In che modo Unix tiene traccia della directory di lavoro di un utente durante la navigazione nel file system?


29

Supponiamo che acceda a una shell su un sistema unix e inizi a toccare i comandi. Inizialmente inizio nella home directory del mio utente ~. Potrei da lì in cdgiù nella directory Documents.

Il comando per modificare la directory di lavoro qui è intuitivamente molto semplice da capire: il nodo padre ha un elenco di nodi figlio a cui può accedere e presumibilmente utilizza una variante (ottimizzata) di una ricerca per individuare l'esistenza di un nodo figlio con il nominare l'utente inserito e la directory di lavoro viene quindi "modificata" per corrispondere a questo - correggimi se sbaglio lì. Potrebbe anche essere più semplice che la shell tenti semplicemente "ingenuamente" di tentare di accedere alla directory esattamente secondo i desideri dell'utente e quando il file system restituisce un tipo di errore, la shell visualizza una risposta di conseguenza.

Ciò che mi interessa, tuttavia, è come funziona lo stesso processo quando navigo in una directory, cioè verso un genitore o un genitore di un genitore.

Data la mia posizione sconosciuta, presumibilmente "cieca" di Documents, forse una delle molte directory nell'intero albero del file system con quel nome, come fa Unix a determinare dove dovrei essere posizionato? Fa riferimento pwde lo esamina? Se sì, come tiene pwdtraccia dello stato di navigazione corrente?


Risposte:


76

Le altre risposte sono semplificazioni eccessive, ognuna delle quali presenta solo parti della storia e sono sbagliate su un paio di punti.

Esistono due modi in cui viene tracciata la directory di lavoro:

  • Per ogni processo, nella struttura dei dati dello spazio kernel che rappresenta quel processo, il kernel memorizza due riferimenti vnode ai vnodi della directory di lavoro e alla directory principale per quel processo. Il primo riferimento è impostato dalle chiamate di sistema chdir()e fchdir(), il secondo da chroot(). Si possono vedere indirettamente nei /procsistemi operativi Linux o tramite il fstatcomando su FreeBSD e simili:

    % fstat -p $$ | head -n 5
    UTENTE CMD PID FD MOUNT MODO INUM SZ | DV R / W
    JdeBP zsh 92648 testo / 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
    % 

    Quando la risoluzione del percorso è attiva, inizia dall'uno o dall'altro di quei vnodi di riferimento, a seconda che il percorso sia relativo o assoluto. (Esiste una famiglia di …at()chiamate di sistema che consente di iniziare la risoluzione del nome percorso nel vnode a cui fa riferimento un descrittore di file (directory) aperto come terza opzione.)

    Nei microkernel Unices la struttura dei dati si trova nello spazio dell'applicazione, ma il principio di mantenere riferimenti aperti a queste directory rimane lo stesso.

  • Internamente, all'interno di shell come la shell Z, Korn, Bourne Again, C e Almquist, la shell tiene inoltre traccia della directory di lavoro usando la manipolazione di stringa di una variabile di stringa interna. Lo fa ogni volta che ha motivo di chiamare chdir().

    Se si cambia in un percorso relativo, manipola la stringa per aggiungere quel nome. Se si cambia in un percorso assoluto, sostituisce la stringa con il nuovo nome. In entrambi i casi, regola la stringa da rimuovere .e i ..componenti e insegue i collegamenti simbolici sostituendoli con i loro nomi collegati. ( Ecco il codice della shell Z per questo , per esempio.)

    Il nome nella variabile di stringa interna è seguito da una variabile di shell denominata PWD(o cwdnelle shell C). Questo viene convenzionalmente esportato come variabile di ambiente (denominata PWD) nei programmi generati dalla shell.

Questi due metodi di monitoraggio cose sono rivelate dalle -Pe -Lopzioni per l' cde pwdshell built-in comandi, e le differenze tra i gusci built-in pwdcomandi e sia il /bin/pwdcomando e il built-in pwdcomandi di cose come (tra gli altri) VIM e 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
% 

Come puoi vedere: ottenere la directory di lavoro "logica" è una questione di esaminare la PWDvariabile shell (o variabile d'ambiente se non si è il programma shell); mentre ottenere la directory di lavoro "fisica" significa chiamare la getcwd()funzione di libreria.

Il funzionamento del /bin/pwdprogramma quando -Lviene utilizzata l' opzione è piuttosto sottile. Non può fidarsi del valore della PWDvariabile d'ambiente che ha ereditato. Dopotutto, non è necessario che sia stato invocato da una shell e i programmi intermedi potrebbero non aver implementato il meccanismo della shell per rendere la PWDvariabile d'ambiente tenere sempre traccia del nome della directory di lavoro. O qualcuno potrebbe fare quello che ho fatto lì.

Quindi ciò che fa è (come dice lo standard POSIX) verificare che il nome indicato in PWDdia lo stesso risultato del nome ., come si può vedere con una traccia di chiamata di sistema:

% 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 'Nessun file o directory' 
__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
%

Come puoi vedere: chiama solo getcwd()se rileva una mancata corrispondenza; e può essere ingannato impostando PWDuna stringa che in effetti denomina la stessa directory, ma con un percorso diverso.

La getcwd()funzione di libreria è un argomento a sé stante. Ma per decorare:

  • Inizialmente era puramente una funzione di libreria, che creava un percorso dalla directory di lavoro alla radice provando ripetutamente a cercare la directory di lavoro nella ..directory. Si è arrestato quando ha raggiunto un ciclo in cui ..era uguale alla sua directory di lavoro o quando si è verificato un errore nel tentativo di aprire il successivo ... Questo sarebbe un sacco di chiamate di sistema sotto le coperte.
  • Oggi la situazione è leggermente più complessa. Su FreeBSD, ad esempio (questo vale anche per altri sistemi operativi), è una vera chiamata di sistema, come puoi vedere nella traccia delle chiamate di sistema fornita in precedenza. Tutto l'attraversamento dalla directory di lavoro vnode fino alla radice viene eseguito in una singola chiamata di sistema, che sfrutta cose come l'accesso diretto del codice in modalità kernel alla cache di immissione della directory per effettuare ricerche dei componenti del percorso in modo molto più efficiente.

    Comunque, nota che anche su FreeBSD e su quegli altri sistemi operativi il kernel non tiene traccia della directory di lavoro con una stringa.

Navigare verso ..è di nuovo un argomento a sé stante. Un altro vantaggio: sebbene le directory convenzionalmente (anche se, come già accennato, non sono necessarie) contengono un effettivo ..nella struttura dei dati della directory su disco, il kernel tiene traccia della directory padre di ogni vnode di directory stesso e può quindi navigare nel ..vnode di qualsiasi directory di lavoro. Ciò è in qualche modo complicato dal mountpoint e dai meccanismi di root modificati, che vanno oltre lo scopo di questa risposta.

A parte

Windows NT infatti fa una cosa simile. Esiste una singola directory di lavoro per processo, impostata dalla SetCurrentDirectory()chiamata API e tracciata per processo dal kernel tramite un handle di file (interno) aperto in quella directory; e c'è una serie di variabili d'ambiente che i programmi Win32 (non solo gli interpreti dei comandi, ma tutti i programmi Win32) usano per tenere traccia dei nomi di più directory di lavoro (una per unità), accodandole o sovrascrivendole ogni volta che cambiano directory.

Convenzionalmente, diversamente dal caso dei sistemi operativi Unix e Linux, i programmi Win32 non mostrano queste variabili d'ambiente agli utenti. Tuttavia, a volte è possibile vederli in sottosistemi simili a Unix in esecuzione su Windows NT, nonché utilizzando i comandi degli interpreti di SETcomando in un modo particolare.

Ulteriori letture


1
Questo è molto più di quanto mi aspettassi. Grazie e grazie in più per l'ulteriore lettura!
ReactingToAngularVues

doc.cat-v.org/plan_9/4th_edition/papers/lexnames parla di alcuni dei problemi ..nel contesto di Plan9,
icarus

@JdeBP: forse mi manca qualcosa. Dici: “Internamente, all'interno di ..., bash, ... e ..., la shell tiene inoltre traccia della directory di lavoro usando la manipolazione di stringa di una variabile di stringa interna. ..., regola la stringa da rimuovere .e i ..componenti e insegue i collegamenti simbolici sostituendoli con i loro nomi collegati. ... Il nome nella variabile stringa interna è seguito da una variabile shell denominata PWD... "(enfasi aggiunta). ... (proseguendo)
G-Man dice "Ripristina Monica" l'

(Proseguendo) ... Ma il tuo esempio mostra PWD= …/bdopo un cd bcomando, anche se bè un collegamento simbolico a a- quindi la shell non "insegue" il a -> bcollegamento. Hai frainteso o ho letto male?
G-Man dice "Ripristina Monica" l'

Ho semplicemente lucidato su un punto laterale e ti ho indicato il codice per i dettagli. Vedi i vari manuali delle shell per quando e come decidono di inseguire legami simbolici o meno. Il guscio Z chiama comodamente l'opzione guscio che è una parte della formula decisione CHASE_LINKS.
JdeBP

1

Il kernel non tiene traccia dei nomi di directory o file; un file o una directory è rappresentato nel kernel da una coppia inode / dispositivo. Chiamate di sistema come chdir(), open()ecc prendere un percorso come parametro, che può essere assoluto (ad esempio /etc/passwd), o relativi alla directory corrente (esempi: Documents, ..). Quando viene eseguito un processo chdir("Documents"), viene eseguita una ricerca Documentsnella directory di lavoro corrente e la directory di lavoro del processo viene aggiornata per fare riferimento a questa directory. Dal punto di vista del kernel, non c'è nulla di speciale nel nome "..", è solo una convenzione nel file system che ..fa riferimento alla directory principale.

La getcwd()funzione non è una chiamata di sistema, ma una funzione di libreria che deve arrivare fino alla directory principale, registrando i nomi dei componenti del percorso sulla strada.


0

È interessante notare che tradizionalmente cd ..è molto più semplice di pwd. Le directory nominate ..vengono inserite esplicitamente nel file system. Il sistema tiene traccia del dispositivo / inode della directory corrente, quindi cd ..o più precisamente la chiamata di sistema chdir("..")comporta solo la ricerca del nome ".." nel file appartenente all'inode della directory corrente e la modifica del dispositivo / inode della directory corrente in valore trovato lì.

pwd(più precisamente /bin/pwd) segue i ..collegamenti in successione e legge le rispettive directory fino a trovare l'inode da cui proviene, assemblando l'elenco di quei nomi al contrario fino a raggiungere la directory radice (in particolare non contenente una ..voce).

Questo è l'originale comportamento di base di basso livello. I comandi effettivi della shell si pwdbasano invece su una varietà di tecniche che memorizzano nella cache il nome del percorso corrente. Ma al centro, è solo il suo inode che è effettivamente noto. Ciò implica che, una volta utilizzati i collegamenti simbolici per la navigazione delle directory, le nozioni attuali del nome della directory di lavoro della shell corrente e del sistema /bin/pwdpotrebbero divergere.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.