Estoy tratando de entender el concepto de archivos especiales en Linux. Sin embargo, tener un archivo especial en /dev
parece simplemente tonto cuando su función podría implementarse mediante un puñado de líneas en C, que yo sepa.
Además, podría usarlo de la misma manera, es decir, conectarlo en null
lugar de redirigirlo /dev/null
. ¿Hay alguna razón específica para tenerlo como archivo? ¿Convertirlo en un archivo no causa muchos otros problemas, como demasiados programas que acceden al mismo archivo?
files
devices
executable
null
Ankur S
fuente
fuente
cat foo | bar
es mucho peor (a escala) quebar <foo
.cat
es un programa trivial, pero incluso un programa trivial crea costos (algunos de ellos específicos de la semántica FIFO) porque los programas no puedenseek()
dentro de FIFO, por ejemplo, un programa que podría implementarse eficientemente con la búsqueda puede terminar haciendo operaciones mucho más costosas cuando se le da una canalización; con un dispositivo de caracteres como/dev/null
este puede falsificar esas operaciones, o con un archivo real puede implementarlas, pero un FIFO no permite ningún tipo de manejo contextualizado).grep blablubb file.txt 2>/dev/null && dosomething
no podría funcionar con null siendo un programa o una función.read
función para/dev/null
consiste en un "retorno 0" (lo que significa que no hace nada y, supongo, da como resultado un EOF): (Desde static github.com/torvalds/linux/blob/master/ drivers / char / mem.c )ssize_t read_null(struct file *file, char __user *buf, size_t count, loff_t *ppos) { return 0; }
(Oh, acabo de ver que @JdeBP ya hizo ese punto. De todos modos, aquí está la ilustración :-).Respuestas:
Además de los beneficios de rendimiento del uso de un dispositivo con caracteres especiales, el beneficio principal es la modularidad . / dev / null puede usarse en casi cualquier contexto donde se espera un archivo, no solo en las tuberías de shell. Considere los programas que aceptan archivos como parámetros de línea de comandos.
Todos estos son casos en los que usar un programa como fuente o sumidero sería extremadamente engorroso. Incluso en el caso de la canalización de shell, stdout y stderr pueden redirigirse a archivos de forma independiente, algo que es difícil de hacer con ejecutables como sumideros:
fuente
/dev/null
solo en los comandos del shell. Puede usarlo en otros parámetros suministrados a un software, por ejemplo, en archivos de configuración. --- Para el software esto es muy conveniente. No necesita hacer una diferencia entre/dev/null
y un archivo normal.pipe
,fork
yexecve
como cualquier otra tubería de proceso, solo con cambios en lasdup2
llamadas que configuran las conexiones, ¿verdad? Es cierto que la mayoría de los shells no ofrecen las formas más bonitas de hacer eso, pero presumiblemente si no tuviéramos tanto patrón de dispositivo como archivo y la mayoría de las cosas/dev
y/proc
fueran tratados como ejecutables, los shells se habrían diseñado con formas para hacerlo tan fácilmente como redirigimos ahora.wait4
! Tiene razón, es ciertamente posible canalizar stdout y stderr a diferentes programas usando apis POSIX, y puede ser posible inventar una sintaxis de shell inteligente para redirigir stdout y stderr a diferentes comandos. Sin embargo, no estoy al tanto de ningún shell de este tipo en este momento, y el punto más importante es que / dev / null encaja perfectamente en las herramientas existentes (que en gran medida funcionan con archivos), y / bin / null no lo haría. También podríamos imaginar alguna API de E / S que facilite que gcc envíe (de forma segura) a un programa como a un archivo, pero esa no es la situación en la que estamos.grep localhost /dev/ /etc/hosts 2> >(sed 's/^/STDERR:/' > errfile ) > >(sed 's/^/STDOUT:/' > outfile)
, el resultado se procesa por separadoerrfile
youtfile
Para ser justos, no es un archivo regular per se; Es un dispositivo especial de caracteres :
Funciona como un dispositivo en lugar de como un archivo o programa, lo que significa que es una operación más simple para redirigir la entrada o la salida, ya que se puede adjuntar a cualquier descriptor de archivo, incluida la entrada / salida / error estándar.
fuente
cat file | null
tendría mucha sobrecarga, primero en configurar una tubería, engendrar un proceso, ejecutar "nulo" en el nuevo proceso, etc. Además,null
usaría bastante CPU en un bucle leyendo bytes en un búfer que luego simplemente descartado ... La implementación de/dev/null
en el núcleo es más eficiente de esa manera. Además, ¿qué pasa si desea pasar/dev/null
como argumento, en lugar de una redirección? (¡Podrías usar<(...)
en bash, pero eso es mucho más pesado!)null
En lugar de usar la redirección a/dev/null
, ¿habría una manera simple y clara de decirle al shell que ejecute un programa mientras envía solo su stderr a nulo?/dev/zero
lugar.dd of=-
escribe en un archivo llamado-
, simplemente omita elof=
para escribir en stdout, ya que es dondedd
escribe por defecto. La tuberíafalse
no funcionaría, yafalse
que no lee su stdin, pordd
lo que sería asesinado con un SIGPIPE. Para un comando que descarta su entrada se puede utilizar ...cat > /dev/null
. También la comparación que probablemente sea irrelevante como el cuello de la botella probablemente sería la generación de números aleatorios aquí.dd
etc. ni siquiera se molestan en realizar una llamada al sistema de escritura cuando detectan que el destino es / dev / null.Sospecho que el por qué tiene mucho que ver con la visión / diseño que dio forma a Unix (y, en consecuencia, Linux), y las ventajas derivadas de ello.
Sin duda, hay un beneficio de rendimiento no despreciable al no acelerar un proceso adicional, pero creo que hay más: unix temprano tenía una metáfora de "todo es un archivo", que tiene una ventaja no obvia pero elegante si se mira desde una perspectiva del sistema, en lugar de una perspectiva de script de shell.
Digamos que tiene su
null
programa de línea de comandos y/dev/null
el nodo del dispositivo. Desde una perspectiva de script de shell, elfoo | null
programa es realmente útil y conveniente , yfoo >/dev/null
toma un poco más de tiempo para escribir y puede parecer extraño.Pero aquí hay dos ejercicios:
Vamos a implementar el programa
null
usando las herramientas Unix existentes y/dev/null
- fácil:cat >/dev/null
. Hecho.¿Se puede implementar
/dev/null
en términos denull
?Tiene toda la razón en que el código C para descartar la entrada es trivial, por lo que puede que aún no sea obvio por qué es útil tener un archivo virtual disponible para la tarea.
Considere: casi todos los lenguajes de programación ya necesitan trabajar con archivos, descriptores de archivos y rutas de archivos, porque formaron parte del paradigma "todo es un archivo" de Unix desde el principio.
Si todo lo que tiene son programas que escriben en stdout, bueno, al programa no le importa si los redirige a un archivo virtual que traga todas las escrituras, o una tubería a un programa que traga todas las escrituras.
Ahora, si tiene programas que toman rutas de archivos para leer o escribir datos (lo que hace la mayoría de los programas), y desea agregar la funcionalidad de "entrada en blanco" o "descartar esta salida" a esos programas, bueno,
/dev/null
eso es gratis.Tenga en cuenta que la elegancia de esto es que reduce la complejidad del código de todos los programas involucrados: para cada caso de uso común pero especial que su sistema puede proporcionar como un "archivo" con un "nombre de archivo" real, su código puede evitar agregar un comando personalizado Opciones de línea y rutas de código personalizadas para manejar.
La buena ingeniería de software a menudo depende de encontrar metáforas buenas o "naturales" para abstraer algún elemento de un problema de una manera que sea más fácil de pensar pero que sea flexible , para que pueda resolver básicamente el mismo rango de problemas de nivel superior sin tener que dedicar el tiempo y la energía mental a reimplementar soluciones a los mismos problemas de nivel inferior constantemente.
"Todo es un archivo" parece ser una de esas metáforas para acceder a los recursos: se llama
open
a una ruta determinada en un espacio de nombres jerárquico, se obtiene una referencia (descriptor de archivo) al objeto, y se puederead
ywrite
, etc., en los descriptores de archivo. Sus stdin / stdout / stderr también son descriptores de archivos que se abrieron previamente para usted. Sus tuberías son solo archivos y descriptores de archivos, y la redirección de archivos le permite pegar todas estas piezas juntas.Unix tuvo tanto éxito como en parte debido a lo bien que funcionaron estas abstracciones juntas, y
/dev/null
se entiende mejor como parte de ese todo.PD Vale la pena mirar la versión Unix de "todo es un archivo" y cosas
/dev/null
como los primeros pasos hacia una generalización más flexible y poderosa de la metáfora que se ha implementado en muchos sistemas que siguieron.Por ejemplo, en Unix, los objetos especiales como archivos
/dev/null
tuvieron que implementarse en el núcleo en sí, pero resulta que es lo suficientemente útil como para exponer la funcionalidad en forma de archivo / carpeta que desde entonces se han creado múltiples sistemas que proporcionan una forma para los programas Para hacer eso.Uno de los primeros fue el sistema operativo Plan 9, creado por algunas de las mismas personas que crearon Unix. Más tarde, GNU Hurd hizo algo similar con sus "traductores". Mientras tanto, Linux terminó recibiendo FUSE (que ahora también se ha extendido a los otros sistemas convencionales).
fuente
NUL
aparezca en cada directorio, es decir, todo lo que tiene que escribir es> NUL
.Creo que
/dev/null
es un dispositivo de caracteres (que se comporta como un archivo ordinario) en lugar de un programa por razones de rendimiento .Si fuera un programa, sería necesario cargar, iniciar, programar, ejecutar y luego detener y descargar el programa. El programa simple de C que está describiendo, por supuesto, no consumiría muchos recursos, pero creo que hace una diferencia significativa al considerar un gran número (digamos millones) de acciones de redireccionamiento / canalización, ya que las operaciones de gestión de procesos son costosas a gran escala. implican cambios de contexto.
Otra suposición: la conexión a un programa requiere que la memoria sea asignada por el programa receptor (incluso si se descarta directamente después). Entonces, si conecta la herramienta, tiene el doble de consumo de memoria, una vez en el programa de envío y otra vez en el programa de recepción.
fuente
read
los datos). ¡Esto no es insignificante en un PDP-11 de un solo núcleo donde se diseñó Unix! El ancho de banda de la memoria / copia es mucho más barato hoy de lo que era entonces. Unawrite
llamada del sistema a un FD abierto/dev/null
puede regresar de inmediato sin siquiera leer ningún dato del búfer.null
programa apestaría, pero la mayoría de las CPU multi-core no se ejecutan con todos los núcleos ocupados todo el tiempo, por lo que El tiempo de CPU para ejecutar elnull
programa es principalmente gratuito. En un sistema con todos los núcleos vinculados, la tubería inútil es un problema mayor. Pero no, un búfer "activo" se puede reescribir muchas veces sin descargarlo en la RAM, por lo que en su mayoría solo estamos compitiendo por el ancho de banda L3, no por el caché. No es genial, especialmente en un sistema SMT (hyperthreading) donde compiten otros núcleos lógicos en el mismo físico ...vpaddd zmm
: 16 elementos de 32 bits por instrucción.Además de "todo es un archivo" y, por lo tanto, la facilidad de uso en todos los lugares en los que se basan la mayoría de las otras respuestas, también hay un problema de rendimiento como menciona @ user5626466.
Para mostrar en la práctica, crearemos un programa simple llamado
nullread.c
:y compilarlo con
gcc -O2 -Wall -W nullread.c -o nullread
(Nota: no podemos usar lseek (2) en las tuberías, por lo que la única forma de drenar la tubería es leerla hasta que esté vacía).
mientras que con la
/dev/null
redirección de archivos estándar obtenemos velocidades mucho mejores (debido a los hechos mencionados: menos cambio de contexto, el núcleo simplemente ignora los datos en lugar de copiarlos, etc.):(Esto debería ser un comentario allí, pero es demasiado grande para eso y sería completamente ilegible)
fuente
dd
buffer + buffer de recepción no cabe; probablemente esté tratando con el ancho de banda de memoria en lugar del ancho de banda de caché de último nivel. Puede obtener un mejor ancho de banda con 512k buffers o incluso 64k buffers. (De acuerdo constrace
mi escritorio,write
yread
estoy devolviendo 1048576, por lo que creo que eso significa que solo estamos pagando al usuario <-> costo del núcleo de invalidación TLB + descarga de predicción de sucursal una vez por MiB, no por 64k, @sourcejedi. Es Specter mitigación que tiene el mayor costo, creo)syscall
devolución inmediataENOSYS
es de ~ 1800 ciclos en Skylake con la mitigación de Spectre habilitada, siendo la mayor parte lawrmsr
que invalida la BPU, según las pruebas de @ BeeOnRope . Con la mitigación desactivada, el tiempo de ida y vuelta usuario-> kernel-> usuario es ~ 160 ciclos. Pero si está tocando mucha memoria, la mitigación de Meltdown también es significativa. Hugepages debería ayudar (se deben recargar menos entradas TLB).Su pregunta se plantea como si algo se ganara quizás con simplicidad utilizando un programa nulo en lugar de un archivo. Quizás podamos deshacernos de la noción de "archivos mágicos" y, en su lugar, tener simplemente "tuberías normales".
Pero considere, una tubería también es un archivo . Normalmente no tienen nombre, por lo que solo pueden manipularse a través de sus descriptores de archivo.
Considere este ejemplo un tanto artificial:
Usando la sustitución del proceso de Bash podemos lograr lo mismo a través de una forma más indirecta:
Reemplace el
grep
paraecho
y podemos ver debajo de las cubiertas:los
<(...)
construcción simplemente se reemplaza con un nombre de archivo, y grep cree que está abriendo cualquier archivo antiguo, simplemente se llama así/dev/fd/63
. Aquí,/dev/fd
hay un directorio mágico que crea canalizaciones con nombre para cada descriptor de archivo que posee el archivo que accede a él.Podríamos hacer que sea menos mágico
mkfifo
hacer una tubería con nombre que aparezca enls
todo, como un archivo ordinario:En otra parte:
y he aquí, saldrá grep
foo
.Creo que una vez que te das cuenta de que las tuberías y los archivos normales y los archivos especiales como / dev / null son solo archivos, es evidente que implementar un programa nulo es más complejo. El núcleo tiene que manejar las escrituras en un archivo de cualquier manera, pero en el caso de / dev / null, simplemente puede dejar las escrituras en el piso, mientras que con una tubería tiene que transferir los bytes a otro programa, que luego tiene que en realidad leerlos.
fuente
/dev/fd/63
?Yo diría que hay un problema de seguridad más allá de los paradigmas históricos y el rendimiento. Limitar la cantidad de programas con credenciales de ejecución privilegiadas, por simple que sea, es un principio fundamental de la seguridad del sistema.
/dev/null
Ciertamente, se requeriría un reemplazo para tener tales privilegios debido al uso por parte de los servicios del sistema. Los marcos de seguridad modernos hacen un excelente trabajo al prevenir exploits, pero no son infalibles. Un dispositivo controlado por el núcleo al que se accede como un archivo es mucho más difícil de explotar.fuente
/dev/null
, o un programa de entrada-descartando propuesto, el vector de ataque sería el mismo: conseguir un script o programa que se ejecuta como root para hacer algo raro (como tratar delseek
en/dev/null
o abiertos varias veces desde el mismo proceso, o IDK, o invocar/bin/null
con un entorno extraño, o lo que sea).Como otros ya señalaron,
/dev/null
es un programa hecho de un puñado de líneas de código. Es solo que estas líneas de código son parte del núcleo.Para aclararlo, aquí está la implementación de Linux: un dispositivo de caracteres llama a funciones cuando se lee o se escribe en ellas. Escribir en
/dev/null
llamadas write_null , mientras lee llamadas read_null , registradas aquí .Literalmente un puñado de líneas de código: estas funciones no hacen nada. Necesitaría más líneas de código que dedos en sus manos solo si cuenta funciones distintas a leer y escribir.
fuente
Espero que también conozcas el / dev / chargen / dev / zero y otros como ellos, incluido / dev / null.
LINUX / UNIX tiene algunos de estos, disponibles para que las personas puedan hacer un buen uso de los MARCOS DE CÓDIGO BIEN ESCRITO.
Chargen está diseñado para generar un patrón de caracteres específico y repetitivo: es bastante rápido y superaría los límites de los dispositivos en serie y ayudaría a depurar los protocolos en serie que se escribieron y fallaron alguna prueba u otra.
Zero está diseñado para llenar un archivo existente o generar una gran cantidad de ceros
/ dev / null es solo otra herramienta con la misma idea en mente.
Todas estas herramientas en su kit de herramientas significan que tiene media oportunidad de hacer que un programa existente haga algo único sin tener en cuenta su (su necesidad específica) como dispositivos o reemplazos de archivos
Establezcamos un concurso para ver quién puede producir el resultado más emocionante dados solo los pocos dispositivos de personajes en su versión de LINUX.
fuente