Muy a menudo los principiantes escuchan una frase "Todo es un archivo en Linux / Unix". Sin embargo, ¿cuáles son los directorios entonces? ¿Cómo son diferentes de los archivos?
Nota: originalmente esto se escribió para respaldar mi respuesta para ¿Por qué el directorio actual en el ls
comando se identifica como vinculado a sí mismo? pero sentí que este es un tema que merece ser independiente, y de ahí este Q&A .
Esencialmente, un directorio es solo un archivo especial, que contiene una lista de entradas y su ID.
Antes de comenzar la discusión, es importante hacer una distinción entre algunos términos y comprender qué representan realmente los directorios y archivos. Es posible que haya escuchado la expresión "Todo es un archivo" para Unix / Linux. Bueno, lo que los usuarios a menudo entienden como archivo es esto: /etc/passwd
- Un objeto con una ruta y un nombre. En realidad, un nombre (ya sea un directorio o archivo, o cualquier otra cosa) es solo una cadena de texto, una propiedad del objeto real. Ese objeto se llama inodo o número I, y se almacena en el disco en la tabla de inodo. Los programas abiertos también tienen tablas de inodo, pero esa no es nuestra preocupación por ahora.
La noción de Unix de un directorio es como Ken Thompson lo expresó en una entrevista de 1989 :
... Y luego algunos de esos archivos, eran directorios que solo contenían el nombre y el número I.
Se puede hacer una observación interesante de la charla de Dennis Ritchie en 1972 que
"... el directorio no es más que un archivo, pero su contenido está controlado por el sistema y los contenidos son nombres de otros archivos. (Un directorio a veces se denomina catálogo en otros sistemas)".
... pero no hay mención de inodes en ninguna parte de la charla. Sin embargo, el manual de 1971 sobre format of directories
estados:
El hecho de que un archivo sea un directorio se indica mediante un bit en la palabra de bandera de su entrada i-node.
Las entradas del directorio tienen 10 bytes de longitud. La primera palabra es el nodo i del archivo representado por la entrada, si no es cero; si es cero, la entrada está vacía.
Así que ha estado allí desde el principio.
El emparejamiento de directorios e inodos también se explica en ¿Cómo se almacenan las estructuras de directorios en el sistema de archivos UNIX? . un directorio en sí es una estructura de datos, más específicamente: una lista de objetos (archivos y números de inodo) que apuntan a listas sobre esos objetos (permisos, tipo, propietario, tamaño, etc.). Entonces cada directorio contiene su propio número de inodo, y luego los nombres de archivo y sus números de inodo. El más famoso es el inodo # 2, que es el /
directorio . (Tenga en cuenta, sin embargo, que /dev
y /run
son los sistemas de ficheros virtuales, por lo que ya que son carpetas raíz de su sistema de archivos, sino que también tienen ínodo 2; es decir, un inodo es único en su propio sistema de archivos, pero con varios sistemas de archivos conectados, tiene inodos no únicos). El diagrama tomado de la pregunta vinculada probablemente lo explica más sucintamente:
Se puede acceder a toda esa información almacenada en el inodo a través de stat()
llamadas al sistema, según Linux man 7 inode
:
Cada archivo tiene un inodo que contiene metadatos sobre el archivo. Una aplicación puede recuperar estos metadatos usando stat (2) (o llamadas relacionadas), que devuelve una estructura estadística, o statx (2), que devuelve una estructura statx.
¿Es posible acceder a un archivo solo conociendo su número de inodo ( ref1 , ref2 )? En algunas implementaciones de Unix es posible, pero pasa por alto los permisos y las verificaciones de acceso, por lo que en Linux no está implementado, y debe atravesar el árbol del sistema de archivos (a través de, find <DIR> -inum 1234
por ejemplo) para obtener un nombre de archivo y su inodo correspondiente.
En el nivel del código fuente, se define en la fuente del kernel de Linux y también es adoptado por muchos sistemas de archivos que funcionan en sistemas operativos Unix / Linux, incluidos los sistemas de archivos ext3 y ext4 (predeterminado de Ubuntu). Lo interesante: dado que los datos son solo bloques de información, Linux en realidad tiene la función inode_init_always que puede determinar si un inodo es una tubería ( inode->i_pipe
). Sí, los enchufes y tuberías son técnicamente también archivos: archivos anónimos, que pueden no tener un nombre de archivo en el disco. Los sockets FIFO y Unix-Domain tienen nombres de archivo en el sistema de archivos.
Los datos en sí pueden ser únicos, pero los números de inodo no son únicos. Si tenemos un enlace duro a foo llamado foobar, eso también apuntará al inodo 123. Este inodo en sí contiene información sobre qué bloques reales de espacio en disco están ocupados por ese inodo. Y así es técnicamente cómo puede haber .
sido vinculado al nombre de archivo del directorio. Bueno, casi: no puede crear enlaces duros a directorios en Linux a sí mismo , pero los sistemas de archivos puede permitir enlaces duros a directorios de una manera muy disciplinada, lo que hace que una restricción de tener sólo .
y ..
como enlaces duros.
Los sistemas de archivos implementan un árbol de directorios como una de las estructuras de datos del árbol. En particular,
El punto clave aquí es que los directorios en sí mismos son nodos en un árbol, y los subdirectorios son nodos hijos, y cada hijo tiene un enlace de regreso al nodo padre. Por lo tanto, para un enlace de directorio, el recuento de inodos es mínimo 2 para un directorio simple (enlace al nombre del directorio /home/example/
y enlace a uno mismo /home/example/.
), y cada subdirectorio adicional es un enlace / nodo adicional:
# new directory has link count of 2
$ stat --format=%h .
2
# Adding subdirectories increases link count
$ mkdir subdir1
$ stat --format=%h .
3
$ mkdir subdir2
$ stat --format=%h .
4
# Count of links for root
$ stat --format=%h /
25
# Count of subdirectories, minus .
$ find / -maxdepth 1 -type d | wc -l
24
El diagrama que se encuentra en la página del curso de Ian D. Allen muestra un diagrama simplificado muy claro:
WRONG - names on things RIGHT - names above things
======================= ==========================
R O O T ---> [etc,bin,home] <-- ROOT directory
/ | \ / | \
etc bin home ---> [passwd] [ls,rm] [abcd0001]
| / \ \ | / \ |
| ls rm abcd0001 ---> | <data> <data> [.bashrc]
| | | |
passwd .bashrc ---> <data> <data>
Lo único en el diagrama DERECHO que es incorrecto es que técnicamente no se considera que los archivos estén en el árbol de directorios: agregar un archivo no tiene efectos en el recuento de enlaces:
$ mkdir subdir2
$ stat --format=%h .
4
# Adding files doesn't make difference
$ cp /etc/passwd passwd.copy
$ stat --format=%h .
4
Para citar a Linus Torvalds :
El punto principal con "todo es un archivo" no es que tenga algún nombre de archivo aleatorio (de hecho, los enchufes y las tuberías muestran que "archivo" y "nombre de archivo" no tienen nada que ver entre sí), sino el hecho de que puede usar herramientas para operar en diferentes cosas.
Teniendo en cuenta que un directorio es solo un caso especial de un archivo, naturalmente tiene que haber API que nos permitan abrir / leer / escribir / cerrar de manera similar a los archivos normales.
Ahí es donde dirent.h
entra en juego la biblioteca C, que define la dirent
estructura, que puede encontrar en man 3 readdir :
struct dirent {
ino_t d_ino; /* Inode number */
off_t d_off; /* Not an offset; see below */
unsigned short d_reclen; /* Length of this record */
unsigned char d_type; /* Type of file; not supported
by all filesystem types */
char d_name[256]; /* Null-terminated filename */
};
Por lo tanto, en el código C tiene que definir struct dirent *entry_p
, y cuando abrimos un directorio con opendir()
y empezar a leer con readdir()
, vamos a almacenar cada elemento en esa entry_p
estructura. Por supuesto, cada elemento contendrá los campos definidos en la plantilla que se dirent
muestra arriba.
El ejemplo práctico de cómo funciona esto se puede encontrar en mi respuesta sobre Cómo enumerar archivos y sus números de inodo en el directorio de trabajo actual .
Tenga en cuenta que el manual POSIX en fdopen establece que "[l] a entradas de directorio para punto y punto-punto son opcionales" y que los estados del manual readdir struct dirent
solo se requieren para tener d_name
y d_ino
campos.
Nota sobre "escribir" en directorios: escribir en un directorio es modificar su "lista" de entradas. Por lo tanto, crear o eliminar un archivo está directamente asociado con los permisos de escritura del directorio , y agregar / eliminar archivos es la operación de escritura en dicho directorio.
open()
y losread()
sockets tienenconnect()
yread()
también. Lo que sería más exacto es que el "archivo" es realmente "datos" organizados almacenados en el disco o la memoria, y algunos archivos son anónimos, no tienen nombre de archivo. Por lo general, los usuarios piensan en los archivos en términos de ese icono en el escritorio, pero eso no es lo único que existe. Ver también unix.stackexchange.com/a/116616/85039