web-dev-qa-db-fra.com

Pourquoi / dev / null est-il un fichier? Pourquoi sa fonction n'est-elle pas implémentée comme un simple programme?

J'essaie de comprendre le concept de fichiers spéciaux sous Linux. Cependant, avoir un fichier spécial dans /dev semble tout simplement idiot quand sa fonction pourrait être implémentée par une poignée de lignes en C à ma connaissance.

De plus, vous pouvez l'utiliser à peu près de la même manière, c'est-à-dire diriger vers null au lieu de rediriger vers /dev/null. Y a-t-il une raison spécifique pour l'avoir comme fichier? En faire un fichier ne cause-t-il pas beaucoup d'autres problèmes comme trop de programmes accédant au même fichier?

114
Ankur S

En plus des avantages en termes de performances de l'utilisation d'un appareil spécial pour les personnages, le principal avantage est modularité./dev/null peut être utilisé dans presque tous les contextes où un fichier est attendu, pas seulement dans les pipelines Shell. Considérez les programmes qui acceptent les fichiers comme paramètres de ligne de commande.

# We don't care about log output.
$ frobify --log-file=/dev/null

# We are not interested in the compiled binary, just seeing if there are errors.
$ gcc foo.c -o /dev/null  || echo "foo.c does not compile!".

# Easy way to force an empty list of exceptions.
$ start_firewall --exception_list=/dev/null

Ce sont tous des cas où l'utilisation d'un programme comme source ou récepteur serait extrêmement lourde. Même dans le cas du pipeline Shell, stdout et stderr peuvent être redirigés vers des fichiers indépendamment, ce qui est difficile à faire avec les exécutables comme récepteurs:

# Suppress errors, but print output.
$ grep foo * 2>/dev/null
154
ioctl

En toute honnêteté, ce n'est pas un fichier ordinaire en soi; c'est un dispositif spécial de caractère :

$ file /dev/null
/dev/null: character special (3/2)

Il fonctionne comme un périphérique plutôt que comme un fichier ou un programme, ce qui signifie qu'il est plus simple de rediriger l'entrée ou la sortie de celui-ci, car il peut être attaché à n'importe quel descripteur de fichier, y compris l'entrée/sortie/erreur standard.

62
DopeGhoti

Je soupçonne que le pourquoi a beaucoup à voir avec la vision/conception qui a façonné Unix (et par conséquent Linux), et les avantages qui en découlent.

Il ne fait aucun doute qu'il y a un avantage non négligeable en termes de performances à ne pas lancer un processus supplémentaire, mais je pense qu'il y a plus à cela: Early Unix avait une métaphore "tout est un fichier", qui a un avantage non évident mais élégant si vous regardez dans une perspective système, plutôt que dans une perspective de script Shell.

Supposons que vous ayez votre programme de ligne de commande null et /dev/null le nœud du périphérique. Du point de vue des scripts Shell, le foo | null le programme est réellement utile et pratique , et foo >/dev/null prend un tout petit peu plus de temps pour taper et peut sembler bizarre.

Mais voici deux exercices:

  1. Implémentons le programme null en utilisant les outils Unix existants et /dev/null - facile: cat >/dev/null. Terminé.

  2. Pouvez-vous implémenter /dev/null en termes de null?

Vous avez tout à fait raison de dire que le code C pour simplement supprimer l'entrée est trivial, il n'est donc pas encore évident pourquoi il est utile d'avoir un fichier virtuel disponible pour la tâche.

Considérez: presque tous les langages de programmation doivent déjà travailler avec des fichiers, des descripteurs de fichiers et des chemins de fichiers, car ils faisaient partie du "tout est un fichier" d'Unix paradigme depuis le début.

Si tout ce que vous avez sont des programmes qui écrivent sur stdout, eh bien, le programme ne se soucie pas de les rediriger vers un fichier virtuel qui avale toutes les écritures, ou un canal vers un programme qui avale toutes les écritures.

Maintenant, si vous avez des programmes qui empruntent des chemins de fichiers pour lire ou écrire des données (ce que font la plupart des programmes) - et que vous souhaitez ajouter la fonctionnalité "entrée vide" ou "supprimer cette sortie" à ces programmes - enfin, avec /dev/null qui vient gratuitement.

Notez que l'élégance de celui-ci est qu'il réduit la complexité du code de tous les programmes impliqués - pour chaque cas d'utilisation commun mais spécial que votre système peut fournir en tant que "fichier" avec un "nom de fichier" réel, votre code peut éviter d'ajouter une commande personnalisée options de ligne et chemins de code personnalisés à gérer.

Une bonne ingénierie logicielle dépend souvent de trouver des métaphores bonnes ou "naturelles" pour résumer certains éléments d'un problème d'une manière qui devient plus facile à penser mais reste flexible , de sorte que vous pouvez résoudre essentiellement la même gamme de problèmes de niveau supérieur sans avoir à consacrer du temps et de l'énergie mentale à réimplémenter des solutions aux mêmes niveaux inférieurs- problèmes de niveau constamment.

"Tout est un fichier" semble être une de ces métaphores pour accéder aux ressources: vous appelez open d'un chemin donné dans un espace de noms hiérarchique, obtenant une référence (descripteur de fichier) à l'objet, et vous pouvez read et write, etc. sur les descripteurs de fichiers. Vos stdin/stdout/stderr sont également des descripteurs de fichiers qui viennent juste d'être pré-ouverts pour vous. Vos tuyaux ne sont que des fichiers et des descripteurs de fichiers, et la redirection de fichiers vous permet de coller toutes ces pièces ensemble.

Unix a réussi autant qu'il l'a fait en partie grâce à la bonne collaboration de ces abstractions et /dev/null est mieux compris comme faisant partie de cet ensemble.


P.S. Cela vaut la peine de regarder la version Unix de "tout est un fichier" et des choses comme /dev/null comme premières étapes vers une généralisation plus flexible et plus puissante de la métaphore qui a été implémentée dans de nombreux systèmes qui ont suivi.

Par exemple, dans Unix, des objets spéciaux de type fichier comme /dev/null devait être implémenté dans le noyau lui-même, mais il s'avère qu'il est suffisamment utile pour exposer des fonctionnalités sous forme de fichier/dossier qui depuis lors, plusieurs systèmes ont été créés qui permettent aux programmes de le faire.

L'un des premiers était le système d'exploitation Plan 9, fabriqué par certaines des mêmes personnes qui ont créé Unix. Plus tard, GNU Hurd a fait quelque chose de similaire avec ses "traducteurs". Pendant ce temps, Linux a fini par obtenir Fuse (qui s'est également propagé aux autres systèmes traditionnels).

54
mtraceur

Je pense /dev/null est un périphérique de caractères (qui se comporte comme un fichier ordinaire) au lieu d'un programme pour raisons de performances.

S'il s'agissait d'un programme, il faudrait charger, démarrer, planifier, exécuter, puis arrêter et décharger le programme. Le simple programme C que vous décrivez ne consommerait bien sûr pas beaucoup de ressources, mais je pense qu'il fait une différence significative lorsque l'on considère un grand nombre (par exemple des millions) d'actions de redirection/canalisation comme les opérations de gestion de processus sont coûteuses à grande échelle car ils impliquent des changements de contexte.

Autre hypothèse: le piping dans un programme nécessite mémoire à allouer par le programme récepteur (même s'il est jeté directement après). Donc, si vous canalisez l'outil, vous avez la double consommation de mémoire, une fois sur le programme émetteur et à nouveau sur le programme récepteur.

14
user5626466

Mis à part "tout est un fichier" et donc la facilité d'utilisation partout sur laquelle la plupart des autres réponses sont basées, il y a aussi un problème de performance comme le mentionne @ user5626466.

Pour montrer en pratique, nous allons créer un programme simple appelé nullread.c:

#include <unistd.h>
char buf[1024*1024];
int main() {
        while (read(0, buf, sizeof(buf)) > 0);
}

et compilez-le avec gcc -O2 -Wall -W nullread.c -o nullread

(Remarque: nous ne pouvons pas utiliser lseek (2) sur les tuyaux, donc le seul moyen de vidanger le tuyau est de le lire jusqu'à ce qu'il soit vide).

% time dd if=/dev/zero bs=1M count=5000 |  ./nullread
5242880000 bytes (5,2 GB, 4,9 GiB) copied, 9,33127 s, 562 MB/s
dd if=/dev/zero bs=1M count=5000  0,06s user 5,66s system 61% cpu 9,340 total
./nullread  0,02s user 3,90s system 41% cpu 9,337 total

alors qu'avec la norme /dev/null La redirection de fichiers nous permet d'obtenir des vitesses bien meilleures (en raison des faits mentionnés: moins de changement de contexte, le noyau ignorant simplement les données au lieu de les copier, etc.):

% time dd if=/dev/zero bs=1M count=5000 > /dev/null
5242880000 bytes (5,2 GB, 4,9 GiB) copied, 1,08947 s, 4,8 GB/s
dd if=/dev/zero bs=1M count=5000 > /dev/null  0,01s user 1,08s system 99% cpu 1,094 total

(cela devrait être un commentaire là-bas, mais il est trop grand pour cela et serait complètement illisible)

7
Matija Nalis

Votre question est posée comme si quelque chose gagnerait peut-être en simplicité en utilisant un programme nul au lieu d'un fichier. Nous pouvons peut-être nous débarrasser de la notion de "fichiers magiques" et avoir à la place des "tuyaux ordinaires".

Mais considérez, n tuyau est aussi un fichier. Ils ne sont normalement pas nommés et ne peuvent donc être manipulés que via leurs descripteurs de fichiers.

Considérez cet exemple quelque peu artificiel:

$ echo -e 'foo\nbar\nbaz' | grep foo
foo

En utilisant la substitution de processus de Bash, nous pouvons accomplir la même chose de manière plus détournée:

$ grep foo <(echo -e 'foo\nbar\nbaz')
foo

Remplacez le grep par echo et nous pouvons voir sous les couvertures:

$ echo foo <(echo -e 'foo\nbar\nbaz')
foo /dev/fd/63

La construction <(...) est juste remplacée par un nom de fichier, et grep pense qu'il ouvre n'importe quel ancien fichier, il se trouve juste qu'il est nommé /dev/fd/63. Ici, /dev/fd Est un répertoire magique qui crée des canaux nommés pour chaque descripteur de fichier possédé par le fichier qui y accède.

Nous pourrions faire moins de magie avec mkfifo pour créer un tube nommé qui apparaît dans ls et tout, comme un fichier ordinaire:

$ mkfifo foofifo
$ ls -l foofifo 
prw-rw-r-- 1 Indigo indigo 0 Apr 19 22:01 foofifo
$ grep foo foofifo

Autre part:

$ echo -e 'foo\nbar\nbaz' > foofifo

et voici, grep affichera foo.

Je pense qu'une fois que vous réalisez que les tuyaux et les fichiers réguliers et les fichiers spéciaux comme/dev/null ne sont que des fichiers, il est évident que la mise en œuvre d'un programme nul est plus complexe. Le noyau doit gérer les écritures dans un fichier de toute façon, mais dans le cas de/dev/null, il peut simplement supprimer les écritures sur le sol, tandis qu'avec un tuyau, il doit réellement transférer les octets vers un autre programme, qui doit ensuite effectivement les lire.

6
Phil Frost

Je dirais qu'il existe un problème de sécurité au-delà des paradigmes historiques et des performances. Limiter le nombre de programmes avec des informations d'identification d'exécution privilégiées, aussi simple soit-elle, est un principe fondamental de la sécurité du système. Un remplaçant /dev/null aurait certainement besoin de tels privilèges en raison de son utilisation par les services système. Les cadres de sécurité modernes font un excellent travail pour prévenir les exploits, mais ils ne sont pas infaillibles. Un périphérique piloté par le noyau auquel on accède sous forme de fichier est beaucoup plus difficile à exploiter.

0
NickW

Comme d'autres l'ont déjà souligné, /dev/null est un programme composé d'une poignée de lignes de code. C'est juste que ces lignes de code font partie du noyau.

Pour le rendre plus clair, voici l'implémentation Linux: un périphérique de caractères appelle des fonctions lorsqu'il est lu ou écrit. Ecrire à /dev/null appelle write_null , lors de la lecture des appels read_null , enregistré ici .

Littéralement quelques lignes de code: ces fonctions ne font rien. Vous auriez besoin de plus de lignes de code que de doigts sur vos mains uniquement si vous comptez des fonctions autres que la lecture et l'écriture.

0
Matthieu Moy