¿Cómo realiza Unix un seguimiento del directorio de trabajo de un usuario cuando navega por el sistema de archivos?


29

Digamos que inicio sesión en un shell en un sistema Unix y empiezo a tocar comandos. Inicialmente comienzo en el directorio de inicio de mi usuario ~. Podría desde allí cdhasta el directorio Documents.

El comando para cambiar el directorio de trabajo aquí es muy simple de entender intuitivamente: el nodo primario tiene una lista de nodos secundarios a los que puede acceder, y presumiblemente usa una variante (optimizada) de una búsqueda para localizar la existencia de un nodo secundario con el nombre del usuario ingresado, y el directorio de trabajo se "modifica" para que coincida con esto - corríjame si me equivoco allí. Incluso puede ser más simple que el shell simplemente intente "ingenuamente" intentar acceder al directorio exactamente según los deseos del usuario y cuando el sistema de archivos devuelve algún tipo de error, el shell muestra una respuesta en consecuencia.

Sin embargo, lo que me interesa es cómo funciona el mismo proceso cuando navego por un directorio, es decir, a un padre o al padre de un padre.

Dada mi ubicación desconocida, presumiblemente "ciega" Documents, de uno de los muchos directorios posibles en todo el árbol del sistema de archivos con ese nombre, ¿cómo determina Unix dónde debería ubicarme a continuación? ¿Hace referencia pwdy examina eso? En caso afirmativo, ¿cómo pwdrastrea el estado de navegación actual?


Respuestas:


76

Las otras respuestas son simplificaciones excesivas, cada una presenta solo partes de la historia, y están equivocadas en un par de puntos.

Hay dos formas en que se rastrea el directorio de trabajo:

  • Para cada proceso, en la estructura de datos del espacio del kernel que representa ese proceso, el kernel almacena dos referencias de vnode a los vnodes del directorio de trabajo y el directorio raíz para ese proceso. La referencia anterior se establece mediante las llamadas al sistema chdir()y fchdir()la segunda por chroot(). Uno puede verlos indirectamente en /proclos sistemas operativos Linux o mediante el fstatcomando en FreeBSD y similares:

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

    Cuando funciona la resolución de nombre de ruta, comienza en uno u otro de los vnodos referenciados, según si la ruta es relativa o absoluta. (Hay una familia de …at()llamadas al sistema que permiten que la resolución de la ruta comience en el vnodo al que hace referencia un descriptor de archivo abierto (directorio) como tercera opción).

    En microkernel Unices, la estructura de datos está en el espacio de aplicación, pero el principio de mantener referencias abiertas a estos directorios sigue siendo el mismo.

  • Internamente, dentro de shells como el shell Z, Korn, Bourne Again, C y Almquist, el shell además realiza un seguimiento del directorio de trabajo mediante la manipulación de cadenas de una variable de cadena interna. Hace esto cada vez que tiene motivos para llamar chdir().

    Si uno cambia a un nombre de ruta relativo, manipula la cadena para agregar ese nombre. Si uno cambia a un nombre de ruta absoluto, reemplaza la cadena con el nuevo nombre. En ambos casos, ajusta la cadena para eliminar .y ..componentes y para perseguir enlaces simbólicos reemplazándolos con sus nombres vinculados. ( Aquí está el código del shell Z para eso , por ejemplo).

    El nombre en la variable de cadena interna es rastreado por una variable de shell llamada PWD(o cwden los shells C). Esto se exporta convencionalmente como una variable de entorno (denominada PWD) a los programas generados por el shell.

Estos dos métodos de cosas de seguimiento son reveladas por los -Py las -Lopciones al cde pwdincorporada en el shell comandos, y por las diferencias entre las conchas incorporados pwdcomandos y tanto el /bin/pwdcomando y el incorporado en pwdlos comandos de cosas como (entre otros) VIM y 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
% 

Como puede ver: obtener el directorio de trabajo "lógico" es una cuestión de mirar la PWDvariable de shell (o la variable de entorno si no se trata del programa de shell); mientras que obtener el directorio de trabajo "físico" es cuestión de llamar a la getcwd()función de biblioteca.

La operación del /bin/pwdprograma cuando -Lse usa la opción es algo sutil. No puede confiar en el valor de la PWDvariable de entorno que ha heredado. Después de todo, no es necesario que haya sido invocado por un shell y los programas que intervienen pueden no haber implementado el mecanismo del shell para hacer que la PWDvariable de entorno siempre rastree el nombre del directorio de trabajo. O alguien puede hacer lo que hice allí.

Entonces, lo que hace es (como dice el estándar POSIX) verificar que el nombre dado PWDarroje lo mismo que el nombre ., como se puede ver con un seguimiento de llamada del 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 'No 
existe tal archivo o directorio' __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
%

Como puede ver: solo llama getcwd()si detecta una falta de coincidencia; y se puede engañar estableciendo PWDuna cadena que de hecho nombre el mismo directorio, pero por una ruta diferente.

La getcwd()función de biblioteca es un tema en sí mismo. Pero para precisar:

  • Originalmente, era puramente una función de biblioteca, que construía una ruta de acceso desde el directorio de trabajo hasta la raíz al intentar buscar repetidamente el directorio de trabajo en el ..directorio. Se detuvo cuando llegó a un bucle donde ..era el mismo que su directorio de trabajo o cuando hubo un error al intentar abrir el siguiente ... Esto sería una gran cantidad de llamadas al sistema bajo las sábanas.
  • Hoy en día la situación es un poco más compleja. En FreeBSD, por ejemplo (esto también es cierto para otros sistemas operativos), es una verdadera llamada al sistema, como puede ver en el seguimiento de llamadas del sistema que se dio anteriormente. Todo el recorrido desde el directorio de trabajo vnode hasta la raíz se realiza en una sola llamada al sistema, que aprovecha cosas como el acceso directo del código del modo kernel al caché de entrada del directorio para realizar las búsquedas de componentes de nombre de ruta de manera mucho más eficiente.

    Sin embargo, tenga en cuenta que incluso en FreeBSD y esos otros sistemas operativos, el núcleo no realiza un seguimiento del directorio de trabajo con una cadena.

Navegar ..nuevamente es un tema en sí mismo. Otro précis: aunque los directorios convencionalmente (aunque, como ya se mencionó, esto no es obligatorio) contienen una ..estructura de datos de directorio real en el disco, el kernel rastrea el directorio padre de cada vnode de directorio y, por lo tanto, puede navegar al ..vnode de cualquier directorio de trabajo. Esto es algo complicado por el punto de montaje y los mecanismos raíz modificados, que están más allá del alcance de esta respuesta.

Aparte

Windows NT, de hecho, hace algo similar. Hay un único directorio de trabajo por proceso, establecido por la SetCurrentDirectory()llamada API y rastreado por proceso por el núcleo a través de un identificador de archivo abierto (interno) a ese directorio; y hay un conjunto de variables de entorno que los programas Win32 (no solo los intérpretes de comandos, sino todos los programas Win32) usan para rastrear los nombres de múltiples directorios de trabajo (uno por unidad), agregándolos o sobrescribiéndolos cada vez que cambian de directorio.

Convencionalmente, a diferencia del caso de los sistemas operativos Unix y Linux, los programas Win32 no muestran estas variables de entorno a los usuarios. Sin embargo, a veces se pueden ver en subsistemas similares a Unix que se ejecutan en Windows NT, así como al usar los comandos de los intérpretes de SETcomandos de una manera particular.

Otras lecturas


1
Esto es mucho más de lo que esperaba. ¡Gracias y gracias adicionales por la lectura adicional!
ReactingToAngularVues

doc.cat-v.org/plan_9/4th_edition/papers/lexnames habla sobre algunos de los problemas ..en el contexto del Plan9,
icarus

@JdeBP: Quizás me estoy perdiendo algo. Usted dice: "Internamente, dentro de ..., bash, ... y ..., el shell además realiza un seguimiento del directorio de trabajo mediante la manipulación de cadenas de una variable de cadena interna. …, Ajusta la cadena para eliminar .y ..componentes y para perseguir enlaces simbólicos reemplazándolos con sus nombres vinculados. ... El nombre en la variable de cadena interna es rastreado por una variable de shell llamada PWD... "(énfasis agregado). … (Continúa)
G-Man dice 'Reincorporar a Monica' el

(Cont.) ... Pero su ejemplo muestra PWD= …/bdespués de un cd bcomando, aunque bes un enlace simbólico a a- para que el shell no "persiga" el a -> benlace. ¿Has dicho mal o he leído mal?
G-Man dice 'Restablece a Monica' el

Simplemente pasé por alto un punto lateral y te señalé el código para más detalles. Consulte los manuales de varios shells para saber cuándo y cómo deciden perseguir enlaces simbólicos o no. La cáscara Z llama cómodamente su opción de cáscara que es una parte de la fórmula de decisión, CHASE_LINKS.
JdeBP

1

El núcleo no realiza un seguimiento de los nombres de directorio o archivo; un archivo o directorio está representado en el núcleo por un par inodo / dispositivo. Las llamadas al sistema como chdir(), open(), etc. toman un camino como parámetro, que puede ser absoluta (por ejemplo /etc/passwd), o en relación con el directorio actual (ejemplos: Documents, ..). Cuando se ejecuta un proceso chdir("Documents"), se realiza una búsqueda Documentsen el directorio de trabajo actual y el directorio de trabajo del proceso se actualiza para hacer referencia a este directorio. Desde la perspectiva del kernel, no hay nada especial en el nombre "..", es solo una convención en el sistema de archivos que se ..refiere al directorio padre.

La getcwd()función no es una llamada al sistema, sino una función de biblioteca que debe abrirse camino hasta el directorio raíz, registrando los nombres de los componentes de la ruta en el camino.


0

Curiosamente, tradicionalmente cd ..es mucho más simple que pwd. Los directorios nombrados ..se colocan explícitamente en el sistema de archivos. El sistema realiza un seguimiento del dispositivo / inodo del directorio actual, por lo que, cd ..o más exactamente, la llamada del sistema chdir("..")solo implica buscar el nombre ".." en el archivo que pertenece al inodo del directorio actual y cambiar el dispositivo / inodo del directorio actual al valor encontrado allí.

pwd(con mayor precisión /bin/pwd) sigue los ..enlaces sucesivamente y lee los directorios respectivos hasta encontrar el inodo de donde proviene, reuniendo la lista de esos nombres en reversa hasta que llega al directorio raíz (en particular, no contiene una ..entrada).

Ahora este es el comportamiento básico original de bajo nivel. Los comandos de shell reales se pwdbasan en una variedad de técnicas que almacenan en caché el nombre de ruta actual. Pero en el fondo, solo se conoce su inodo. Eso implica que una vez que los enlaces simbólicos se utilizan para navegar por los directorios, las nociones de nombre del directorio de trabajo actual del shell actual y del sistema /bin/pwdpueden divergir.

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.