¿Qué significa en Linux que todo es archivo?

En un viejo artículo, hay una presentación en la que mencioné una frase bastante habitual en el mundo Unix/Linux: «Todo es archivo». Vamos a explicar qué significa realmente esa frase.

Todo es archivo

¿Todo es Archivo?

En realidad, ese dicho no es del todo correcto. Sin embargo, proviene de lo que hoy tal vez podríamos llamar la filosofía Unix. Probablemente, nadie dijo esa frase de manera literal en los primeros años de esa familia de sistemas operativos. Aun así, ideas de ese estilo estaban en la mente desde los comienzos y en los trabajos de por ejemplo, Bill Joy (pionero de TCP/IP). Linus Torvalds, no estuvo ausente en esta discusión. Y mucho más cerca en el tiempo, Lennart Poettering, retoma esa idea una vez más para defender el trabajo realizado en systemd. Por si no se dieron cuenta dice Poettering la interfaz de cgroups están expuesta en un pseudo-sistema de archivos...

Sin embargo, es más apropiado decir que todo es un descriptor de archivo o que todo puede tener un descriptor de archivo. En Linux un descriptor de archivo es un identificador único de proceso para un archivo u otro recurso de entrada o salida. Por ejemplo, no existe necesariamente un archivo para una interfaz de red. Sin embargo, existen sockets relacionados con ellas.

En el siguiente ejemplo podemos ver los descriptores de archivos para la dirección ip 127.0.0.1 de la interfaz lo.

sudo lsof -n -i@127.0.0.1
COMMAND    PID   USER   FD   TYPE DEVICE SIZE/OFF NODE NAME
chronyd   1337 chrony    5u  IPv4   8171      0t0  UDP 127.0.0.1:323 
cupsd     1538   root    7u  IPv4  14727      0t0  TCP 127.0.0.1:ipp (LISTEN)
qemu-syst 1774   qemu   32u  IPv4  19480      0t0  TCP 127.0.0.1:rfb (LISTEN)
syncthing 2620 sergio   19u  IPv4  22825      0t0  TCP 127.0.0.1:8384 (LISTEN)

En este caso, vemos los descriptores de archivos de lectura y escritura 5u, 7u, 32u y 19u.

Para ver la relación entre descriptores y sockets podemos hacer un ping a una determinada dirección ip:

ping -c 10000 9.9.9.9 &> /dev/null

Si buscamos los descriptores de archivos con lsfd, mostrará el siguiente resultado:

lsfd -p 914323 -Q '(FD >= 0)' 
COMMAND    PID   USER ASSOC  XMODE   TYPE SOURCE MNTID    INODE NAME
ping    914323 sergio     0 rw-D--    CHR  pts:6    36        9 /dev/pts/6 
ping    914323 sergio     1 -w----    CHR  mem:3    34        4 /dev/null
ping    914323 sergio     2 -w----    CHR  mem:3    34        4 /dev/null
ping    914323 sergio     3 rw---m   PING sockfs     9 30437500 state=close id=35 laddr=0.0.0.0
ping    914323 sergio     4 rw---- PINGv6 sockfs     9 30437501 socket:[30437501]

Analicemos cada descriptor de archivo:

  • 0: Es la "standard input" que apunta a una pseudoterminal (dispositivo de caracteres), al tratarlo como un archivo tiene permisos: de lectura y escritura. Usa un sistema de archivos y como tal también maneja inodos.
  • 1: Es la "standard output" que apunta a /dev/null. Solamente tiene permisos de escritura, el sistema de archivos proviene de la memoria principal y tiene permisos solamente de escritura.
  • 2: Es el "standard error" que apunta también a /dev/null.
  • 3: Apunta a un socket de tipo IPv4, y tiene permisos de lectura y además está siendo utilizado por un evento de multiplexación.
  • 4: Finalmente tenemos el descriptor de 5 que apunta a un tipo de socket que usa IPv6.

Dentro del directorio /proc podemos ver que los descriptores 1 (salida estándar) y 2 (error estándar) se redirigen al archivo /dev/null, mientras tanto, 3 y 4 apuntan a sockets:

ls -l /proc/914323/fd
total 0
lrwx------ 1 sergio sergio 64 may 19 17:33 0 -> '/dev/pts/6 (deleted)'
l-wx------ 1 sergio sergio 64 may 19 17:33 1 -> /dev/null
l-wx------ 1 sergio sergio 64 may 19 17:33 2 -> /dev/null
lrwx------ 1 sergio sergio 64 may 19 17:33 3 -> 'socket:[30437500]'
lrwx------ 1 sergio sergio 64 may 19 17:33 4 -> 'socket:[30437501]'

Podemos hacer otra prueba, en este caso usaremos solamente ping sobre ipv6 y en lugar de redireccionar a /dev/null, lo haremos redirigiendo stdin y stderr a archivos regulares distintos:

ping6 -c 10000 ::1 > out.txt 2> errors.txt &

Aquí solamente usará un socket (ya que no usa ipv4):

lsfd -p 926928 -Q '(FD >= 0)'
COMMAND    PID   USER ASSOC  XMODE   TYPE SOURCE MNTID    INODE NAME
ping6   926928 sergio     0 rw----    CHR  pts:7    36       10 /dev/pts/7
ping6   926928 sergio     1 -w----    REG   0:34    52 38207299 /tmp/out.txt
ping6   926928 sergio     2 -w----    REG   0:34    52 38207300 /tmp/errors.txt
ping6   926928 sergio     3 rw---- PINGv6 sockfs     9 31193485 state=close id=44 laddr=::

¿Qué sucede si se borra /tmp/errors.txt?

lsfd -p 926928 -Q '(FD >= 0)'
COMMAND    PID   USER ASSOC  XMODE   TYPE SOURCE MNTID    INODE NAME
ping6   926928 sergio     0 rw----    CHR  pts:7    36       10 /dev/pts/7
ping6   926928 sergio     1 -w----    REG   0:34    52 38207299 /tmp/out.txt
ping6   926928 sergio     2 -w-D--    REG   0:34    52 38207300 /tmp/errors.txt
ping6   926928 sergio     3 rw---- PINGv6 sockfs     9 31193485 state=close id=44 laddr=::

Bueno, veremos que el archivo aparece con el flag D indicando que borró del respectivo sistema de archivos.

Por lo tanto, si bien literalmente no todo es archivo, podemos afirmar que en general podríamos mediante los descriptores de archivos establecer un puente entre un proceso y los recursos a los que está accediendo. En definitiva, un descriptor de archivos, permite al proceso comunicarse con el recurso subyacente de manera eficiente y uniforme, independientemente de si el recurso es un archivo en el sistema de archivos, un dispositivo de hardware o un socket de red.

Enlaces útiles

Comentarios

Comments powered by Disqus