Warum ist / dev / null eine Datei? Warum ist seine Funktion nicht als einfaches Programm implementiert?

114

Ich versuche das Konzept spezieller Dateien unter Linux zu verstehen. Ein spezielles File in zu haben, /deverscheint mir jedoch albern, wenn die Funktion meines Wissens durch ein paar Zeilen in C implementiert werden könnte.

Darüber hinaus können Sie es auf die gleiche Art und Weise verwenden, z. B. nullumleiten, anstatt umzuleiten /dev/null. Gibt es einen bestimmten Grund dafür, es als Datei zu haben? Verursacht das Erstellen einer Datei nicht viele andere Probleme, z. B. zu viele Programme, die auf dieselbe Datei zugreifen?

Ankur S
quelle
20
Übrigens ist ein Großteil dieses Overheads auch, warum cat foo | barviel schlimmer (im Maßstab) als bar <foo. catist ein triviales Programm, aber selbst ein triviales Programm verursacht Kosten (von denen einige spezifisch für die FIFO-Semantik sind), da Programme nicht seek()in FIFOs implementiert werden können Wenn eine Pipeline angegeben wird, kann sie mit einem Zeichengerät wie /dev/nulldiesem diese Operationen vortäuschen oder mit einer echten Datei implementieren, aber ein FIFO lässt keine kontextbezogene Behandlung zu.
Charles Duffy
13
grep blablubb file.txt 2>/dev/null && dosomethingkonnte nicht funktionieren, wenn null ein Programm oder eine Funktion war.
Rexkogitans
16
Möglicherweise ist es aufschlussreich (oder zumindest aufschlussreich), Informationen zum Betriebssystem Plan 9 zu lesen, um festzustellen, wo die Vision "Alles ist eine Datei" ablief - es wird ein wenig einfacher, die Möglichkeiten zu erkennen, Ressourcen als Datei verfügbar zu haben Pfade, wenn Sie sehen, dass ein System das Konzept vollständig unterstützt (anstatt meistens / teilweise, wie es modernes Linux / Unix tut).
mtraceur
25
Neben niemand unter Hinweis darauf , dass ein Gerätetreiber im Kernel - Raum ausgeführt ist ein Programm mit „einer Handvoll Zeilen C“ , keiner der bisher Antworten angesprochen hat tatsächlich die Vermutung von „zu vielen Programmen die gleiche Datei zugreifen“ in der Frage.
JdeBP,
12
Zu "seine Funktion könnte durch eine Handvoll Zeilen in C implementiert werden": Sie würden es nicht glauben, aber es wird durch eine Handvoll Zeilen in C implementiert! Der Hauptteil der readFunktion für /dev/nullbesteht zum Beispiel aus einer "return 0" (was bedeutet, dass sie nichts tut und vermutlich zu einem EOF führt): (From 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, ich sehe nur, dass @JdeBP diesen Punkt bereits gemacht hat. Wie auch immer, hier ist die Abbildung :-).
Peter A. Schneider

Antworten:

153

Neben den Leistungsvorteilen der Verwendung eines charakterspezifischen Geräts liegt der Hauptvorteil in der Modularität . / dev / null kann in fast jedem Kontext verwendet werden, in dem eine Datei erwartet wird, nicht nur in Shell-Pipelines. Betrachten Sie Programme, die Dateien als Befehlszeilenparameter akzeptieren.

# 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

Dies sind alle Fälle, in denen die Verwendung eines Programms als Quelle oder Senke äußerst umständlich wäre. Sogar im Fall einer Shell-Pipeline können stdout und stderr unabhängig voneinander in Dateien umgeleitet werden, was mit ausführbaren Dateien als Senken schwierig zu tun ist:

# Suppress errors, but print output.
$ grep foo * 2>/dev/null
ioctl
quelle
14
Auch verwenden Sie nicht /dev/nullnur in der Shell Befehle. Sie können es in anderen Parametern verwenden, die für eine Software bereitgestellt werden, z. B. in Konfigurationsdateien. --- Für die Software ist dies sehr praktisch. Es muss keinen Unterschied zwischen /dev/nulleiner normalen Datei und einer normalen Datei machen.
Pabouk
Ich bin nicht sicher, ob ich den Teil über separate Weiterleitungen zu ausführbaren Dateien als schwierig verstehe. In C führen Sie einfach ein pipe, forkund execvewie bei jedem anderen Prozess-Piping, nur mit Änderungen an den dup2Aufrufen aus, die die Verbindungen herstellen, richtig? Es ist wahr, dass die meisten Shells nicht die hübschesten Möglichkeiten bieten, dies zu tun, aber wahrscheinlich wären Shells mit Möglichkeiten entworfen worden , wenn wir nicht so viele Device-as-File-Muster und die meisten Dinge darin gehabt hätten /devund /procals ausführbare Dateien behandelt worden wären Um es so einfach zu machen, wie wir es jetzt umleiten.
Aschepler
6
@taschepler Es ist schwierig, nicht auf ausführbare Dateien umzuleiten. Es ist so, dass das Schreiben von Anwendungen, die in beide Dateien und in die Null-Senke schreiben / aus diesen lesen können, komplizierter wäre, wenn die Null-Senke keine Datei wäre. Wenn Sie nicht von einer Welt sprechen, in der alles keine Datei ist, sondern eine ausführbare Datei? Das wäre ein ganz anderes Modell als das, was Sie in * nix OS haben.
Cubic
1
@taschepler Du hast es vergessen wait4! Sie haben Recht, es ist sicherlich möglich, stdout und stderr mit POSIX apis an verschiedene Programme weiterzuleiten, und es ist möglicherweise möglich, eine clevere Shell-Syntax für die Umleitung von stdout und stderr an verschiedene Befehle zu erfinden. Allerdings ist mir derzeit keine solche Shell bekannt, und der größere Punkt ist, dass / dev / null gut in vorhandene Tools (die größtenteils mit Dateien funktionieren) passt und / bin / null nicht. Wir könnten uns auch eine IO-API vorstellen, die es für gcc so einfach macht, (sicher!) In ein Programm wie in eine Datei auszugeben, aber das ist nicht die Situation, in der wir uns befinden.
ioctl
2
@ioctl in Bezug auf Muscheln; Sowohl zsh als auch bash ermöglichen es Ihnen zumindest, Dinge wie zu tun grep localhost /dev/ /etc/hosts 2> >(sed 's/^/STDERR:/' > errfile ) > >(sed 's/^/STDOUT:/' > outfile), was separat verarbeitet wird errfileundoutfile
Matija Nalis
62

Fairerweise handelt es sich nicht um eine reguläre Datei an sich. Es ist ein spezielles Zeichengerät :

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

Da es als Gerät und nicht als Datei oder Programm fungiert, ist es einfacher, Eingaben in das Gerät oder Ausgaben daraus umzuleiten, da es an jeden Dateideskriptor angehängt werden kann, einschließlich Standardeingabe / -ausgabe / -fehler.

DopeGhoti
quelle
24
cat file | nullwürde viel Overhead haben, zuerst beim Einrichten einer Pipe, beim Erzeugen eines Prozesses, beim Ausführen von "null" im neuen Prozess usw. Außerdem nullwürde selbst eine ganze Menge CPU in einer Schleife zum Lesen von Bytes in einen späteren Puffer verwendet nur verworfen ... Die Implementierung /dev/nullim Kernel ist auf diese Weise nur effizienter. Was ist auch, wenn Sie /dev/nullanstelle einer Umleitung als Argument übergeben möchten ? (Sie könnten <(...)in Bash verwenden, aber das ist noch viel schwerer!)
Filbranden
4
Wenn Sie eine Pipe zu einem Programm namens " nullAnstatt die Umleitung zu" verwenden /dev/nullmüssten, gibt es eine einfache, eindeutige Möglichkeit, der Shell mitzuteilen, dass sie ein Programm ausführen soll, während nur das stderr an null gesendet wird?
Mark Plotnick
5
Dies ist ein sehr teures Setup für eine Overhead-Demonstration. Ich würde vorschlagen, /dev/zerostattdessen zu verwenden.
chrylis -on strike-
20
Diese Beispiele sind falsch. dd of=-Schreibvorgänge in eine Datei mit dem Namen -lassen Sie das Feld of=zum Schreiben in stdout einfach weg, da dort ddstandardmäßig geschrieben wird. Piping to falsewürde nicht funktionieren, da falsees nicht den ddStandardwert liest, und würde daher mit einem SIGPIPE getötet. Für einen Befehl, der seine Eingabe verwirft, können Sie ... verwenden cat > /dev/null. Auch der Vergleich, der wahrscheinlich als Flaschenhals irrelevant ist, wäre hier wahrscheinlich die Zufallszahlengenerierung.
Stéphane Chazelas
8
Die AST-Versionen von ddetc. machen sich nicht einmal die Mühe, einen Schreib-Syscall durchzuführen, wenn sie feststellen, dass das Ziel / dev / null ist.
Mark Plotnick
54

Ich vermute, dass das Warum viel mit der Vision / dem Design zu tun hat, das Unix (und folglich Linux) geprägt hat, und den Vorteilen, die sich daraus ergeben.

Kein Zweifel, es ist ein nicht zu vernachlässigender Leistungsvorteil, keinen zusätzlichen Prozess zu starten, aber ich denke, es steckt noch mehr dahinter: Early Unix hatte eine Metapher "Alles ist eine Datei", die einen nicht offensichtlichen, aber eleganten Vorteil hat, wenn man sich das ansieht es aus einer Systemperspektive und nicht aus einer Shell-Skriptperspektive.

Angenommen, Sie haben Ihr nullBefehlszeilenprogramm und /dev/nullden Geräteknoten. Von einer Shell-Scripting - Perspektive, das foo | nullist Programm eigentlich wirklich nützlich und bequem , und foo >/dev/nullnimmt ein kleines bisschen länger kann zu geben und seltsam erscheinen.

Aber hier sind zwei Übungen:

  1. Lassen Sie uns die Umsetzung des Programms in nulleinem der vorhandenen Unix - Tools und /dev/null- einfach: cat >/dev/null. Getan.

  2. Können Sie /dev/nullin Bezug auf implementieren null?

Sie haben absolut Recht, dass der C-Code zum Verwerfen von Eingaben trivial ist. Daher ist es möglicherweise noch nicht klar, warum es nützlich ist, eine virtuelle Datei für die Aufgabe zur Verfügung zu haben.

Bedenken Sie: Fast jede Programmiersprache muss bereits mit Dateien, Dateideskriptoren und Dateipfaden arbeiten, da diese von Anfang an Teil des Unix-Paradigmas "Alles ist eine Datei" waren.

Wenn Sie nur Programme haben, die nach stdout schreiben, ist es dem Programm egal, ob Sie sie in eine virtuelle Datei umleiten, die alle Schreibvorgänge schluckt, oder eine Pipe in ein Programm, das alle Schreibvorgänge schluckt.

Wenn Sie nun Programme haben, die Dateipfade zum Lesen oder Schreiben von Daten verwenden (was die meisten Programme tun) - und diesen Programmen die Funktion "Leereingabe" oder "Ausgabe verwerfen" hinzufügen möchten -, ist dies /dev/nullkostenlos.

Beachten Sie, dass die Eleganz darin besteht, dass die Codekomplexität aller beteiligten Programme verringert wird. Für jeden allgemeinen, aber besonderen Verwendungszweck, den Ihr System als "Datei" mit einem tatsächlichen "Dateinamen" bereitstellen kann, kann Ihr Code das Hinzufügen eines benutzerdefinierten Befehls vermeiden -Line-Optionen und benutzerdefinierte Codepfade zu behandeln.

Gutes Software-Engineering hängt oft davon ab, gute oder "natürliche" Metaphern zu finden, um ein Element eines Problems auf eine Weise zu abstrahieren, die leichter zu überlegen ist, aber dennoch flexibel bleibt , sodass Sie im Grunde die gleichen Probleme auf höherer Ebene lösen können, ohne dass dies erforderlich ist verbringen Sie die Zeit und die geistige Energie damit, Lösungen für dieselben Probleme auf niedrigerer Ebene ständig neu zu implementieren.

"Alles ist eine Datei" scheint eine solche Metapher für den Zugriff auf Ressourcen zu sein: Sie rufen openeinen bestimmten Pfad in einem hierarchischen Namespace auf, erhalten einen Verweis (Dateideskriptor) auf das Objekt, und Sie können readund writeusw. auf die Dateideskriptoren. Ihre stdin / stdout / stderr sind auch Dateideskriptoren, die gerade für Sie vorab geöffnet wurden. Ihre Pipes sind nur Dateien und Dateideskriptoren, und mit der Dateiumleitung können Sie all diese Teile zusammenfügen.

Unix war ebenso erfolgreich wie Unix, da diese Abstraktionen gut zusammengearbeitet haben und /dev/nullam besten als Teil dieses Ganzen verstanden werden können.


PS: Es lohnt sich, sich die Unix-Version von "Alles ist eine Datei" anzuschauen, und zwar /dev/nullals erste Schritte zu einer flexibleren und leistungsfähigeren Verallgemeinerung der Metapher, die in vielen folgenden Systemen implementiert wurde.

Zum Beispiel mussten in Unix spezielle dateiähnliche Objekte wie /dev/nullim Kernel selbst implementiert werden, aber es hat sich herausgestellt, dass es nützlich genug ist, Funktionen in Datei- / Ordnerform bereitzustellen, die seitdem mehrere Systeme haben, die eine Möglichkeit für Programme bieten das zu tun.

Eines der ersten war das Betriebssystem Plan 9, das von einigen der gleichen Leute wie Unix entwickelt wurde. Später machte GNU Hurd mit seinen "Übersetzern" etwas Ähnliches. In der Zwischenzeit bekam Linux FUSE (das sich mittlerweile auch auf die anderen Mainstream-Systeme ausgebreitet hat).

mtraceur
quelle
8
@PeterCordes Der Punkt der Antwort beginnt an einer Position, an der das Design nicht verstanden wird. Wenn alle das Design bereits verstanden hätten, gäbe es diese Frage nicht.
OrangeDog
1
@mtraceur: Mounten einer Image-Datei ohne Root-Berechtigung? zeigt einige Beweise dafür, dass FUSE möglicherweise kein Root-Verzeichnis benötigt, aber ich bin mir nicht sicher.
Peter Cordes
1
@PeterCordes RE: "scheint seltsam": Es ist keine Entschuldigung für das Design, sondern nur eine Bestätigung dessen, wie es aussehen kann, wenn Sie nicht an die darunter liegende Systemimplementierung denken und noch keine Ahnung von dem System haben Designvorteile. Ich habe versucht, dies zu verdeutlichen, indem ich diesen Satz mit "aus der Perspektive von Shell-Skripten" eröffnet und auf den Kontrast zwischen dieser und einer Systemperspektive einige Sätze zuvor angespielt habe. Bei weiterem Nachdenken ist "kann seltsam erscheinen" besser, also werde ich es dem anpassen. Ich begrüße weitere Formulierungsvorschläge, um es klarer zu machen, ohne es zu wortreich zu machen.
mtraceur
2
Das allererste, was mir als junger Ingenieur in Bezug auf Unix gesagt wurde, war "Everything Is A File", und ich schwöre, Sie konnten die Hauptstädte hören. Wenn Sie sich frühzeitig mit dieser Idee befassen, wird Unix / Linux leichter verständlich. Linux hat den größten Teil dieser Designphilosophie geerbt. Ich bin froh, dass es jemand erwähnt hat.
StephenG
2
@PeterCordes, DOS "löste" das Tippproblem, indem der magische Dateiname NULin jedem Verzeichnis angezeigt wurde , dh alles, was Sie eingeben müssen, ist > NUL.
Cristian Ciupitu
15

Ich denke, es /dev/nullist ein Zeichengerät (das sich wie eine gewöhnliche Datei verhält) anstelle eines Programms aus Leistungsgründen .

Wenn es sich um ein Programm handeln würde, müsste das Programm geladen, gestartet, geplant, ausgeführt und anschließend gestoppt und entladen werden. Das einfache C-Programm, das Sie beschreiben, würde natürlich nicht viel Ressourcen verbrauchen, aber ich denke, es macht einen signifikanten Unterschied, wenn man eine große Anzahl (sagen wir Millionen) von Redirect / Piping-Aktionen in Betracht zieht, da Prozessmanagementvorgänge in großem Umfang kostspielig sind Sie beinhalten Kontextwechsel.

Eine andere Annahme: Das Weiterleiten in ein Programm erfordert die Zuweisung von Speicher durch das empfangende Programm (auch wenn es direkt danach verworfen wird). Wenn Sie also in das Tool weiterleiten, haben Sie den doppelten Speicherverbrauch, einmal im Sendeprogramm und einmal im Empfangsprogramm.

user5626466
quelle
10
Es sind nicht nur die Einrichtungskosten, sondern es ist auch so, dass jedes Schreiben in eine Pipe ein Kopieren des Speichers und einen Kontextwechsel zum Leseprogramm erfordert. (Oder zumindest ein Kontextwechsel, wenn der Pipe-Puffer voll ist. Und der Leser muss eine weitere Kopie anfertigen, wenn es sich um readdie Daten handelt.) Dies ist auf einem Single-Core-PDP-11, für den Unix entwickelt wurde, nicht zu vernachlässigen! Speicherbandbreite / Kopieren ist heute viel billiger als damals. Ein writeSystemaufruf zu einem FD, der am geöffnet ist, /dev/nullkann sofort zurückkehren, ohne dass Daten aus dem Puffer gelesen werden.
Peter Cordes
@PeterCordes, meine Notiz ist tangential, aber es ist paradoxerweise möglich, dass Speicher-Schreibvorgänge heute teurer sind als je zuvor. Eine 8-Kern-CPU führt möglicherweise 16 Ganzzahloperationen in einer Taktzeit aus, während ein Ende-zu-Ende-Speicherschreiben beispielsweise in 16 Takten (4-GHz-CPU, 250-MHz-RAM) abgeschlossen wäre. Das ist der Faktor 256. RAM für die moderne CPU ist wie ein RL02 für die PDP-11-CPU, fast wie ein Peripheriespeicher! :) Natürlich nicht so einfach, aber alles, was auf den Cache trifft, wird ausgeschrieben, und nutzlose Schreibvorgänge würden anderen Berechnungen den immer wichtigeren Cache-Speicherplatz nehmen.
km
@kkm: Ja, eine Verschwendung von etwa 2 x 128 KB L3-Cache-Speicherplatz für einen Pipe-Puffer im Kernel und einen Lesepuffer im nullProgramm wäre zum Kotzen, aber die meisten Multi-Core-CPUs werden nicht immer mit allen Kernen ausgelastet Die CPU-Zeit zum Ausführen des nullProgramms ist größtenteils frei. Bei einem System, bei dem alle Kerne angeschlossen sind, ist die Verwendung nicht verwendbarer Leitungen ein größeres Problem. Nein, ein "heißer" Puffer kann viele Male umgeschrieben werden, ohne dass er in den Arbeitsspeicher geschrieben wird. Wir konkurrieren also meist nur um die L3-Bandbreite und nicht um den Cache. Nicht großartig, besonders auf einem SMT-System (Hyperthreading), bei dem andere logische Kerne auf demselben physischen System miteinander konkurrieren ...
Peter Cordes,
.... aber Ihre Gedächtnisberechnung ist sehr fehlerhaft. Moderne CPUs haben viel Speicherparallelität, also trotz Latenz zum DRAM etwa 200-400 Kerntaktzyklen und L3> 40 beträgt , beträgt die Bandbreite ~ 8 Bytes / Takt. (Überraschenderweise ist die Single-Threaded-Bandbreite für L3 oder DRAM auf einem Xeon mit vielen Kernen und Quad-Channel-Speicher schlechter als auf einem Desktop mit vier Kernen, da sie durch die maximale Anzahl von Anforderungen begrenzt ist, die ein Kern im Flug halten kann. Bandbreite = max_concurrency / latence: Warum ist Skylake so viel besser als Broadwell-E für Single-Threaded-Speicherdurchsatz? )
Peter Cordes
... Siehe auch 7-cpu.com/cpu/Haswell.html für Haswell-Zahlen zum Vergleich von Quadcore und 18-Core. Wie auch immer, moderne CPUs können eine Menge Arbeit pro Takt erledigen, wenn sie nicht auf Speicher warten müssen. Ihre Zahlen scheinen nur 2 ALU-Operationen pro Uhr zu sein, wie etwa ein Pentium aus dem Jahr 1993 oder ein moderner Low-End-Dual-Issue-ARM. Ein Ryzen oder Haswell führt möglicherweise 4 skalare Ganzzahl-ALU-Operationen + 2 Speicheroperationen pro Kern und Takt aus, oder viel mehr mit SIMD. Beispiel: Skylake-AVX512 hat (pro Kern) einen Durchsatz von 2 pro Takt vpaddd zmm: 16 32-Bit-Elemente pro Befehl.
Peter Cordes
7

Abgesehen von "Alles ist eine Datei" und damit der Benutzerfreundlichkeit, auf der die meisten anderen Antworten basieren, gibt es auch Leistungsprobleme, wie @ user5626466 erwähnt.

Um dies in der Praxis zu zeigen, erstellen wir ein einfaches Programm mit dem Namen nullread.c:

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

und kompiliere es mit gcc -O2 -Wall -W nullread.c -o nullread

(Hinweis: Wir können lseek (2) nicht für Rohre verwenden. Daher muss das Rohr nur abgelesen werden, bis es leer ist.)

% 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

wohingegen /dev/nullwir mit der Standard- Dateiumleitung viel bessere Geschwindigkeiten erzielen (aufgrund der genannten Fakten: weniger Kontextwechsel, Kernel ignoriert nur Daten, anstatt sie zu kopieren usw.):

% 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

(dies sollte dort ein Kommentar sein, ist aber zu groß dafür und wäre völlig unlesbar)

Matija Nalis
quelle
Auf welcher Hardware haben Sie getestet? 4,8 GB / s sind ziemlich niedrig im Vergleich zu den 23 GB / s, die ich auf einem Skylake i7-6700k (DDR4-2666) erhalte, aber der Puffer sollte im L3-Cache heiß bleiben. Ein guter Teil der Kosten ist, dass Systemanrufe mit Spectre teuer sind + Meltdown-Abschwächung aktiviert: Dies schadet doppelt, da die Pipe-Puffer kleiner als 1 MB sind, sodass mehr Systemaufrufe beim Schreiben / Lesen erfolgen. Auf meinem Skylake-System ist der Leistungsunterschied jedoch schlimmer als erwartet. Auf meinem Skylake-System sind es 23 GB / s vs. 3,3 GB / s unter x86-64 Linux 4.15.8-1-ARCH, das ist also ein Faktor von 6,8. Wow, Systemaufrufe sind jetzt teuer )
Peter Cordes
1
@PeterCordes 3GB / s mit 64k Pipe-Puffern schlägt 2x 103124 Systemaufrufe pro Sekunde vor ... und diese Anzahl von Kontextwechseln, heh. Auf einer Server-CPU mit 200000 Systemaufrufen pro Sekunde können Sie mit einem PTI-Overhead von ca. 8% rechnen, da nur sehr wenig Arbeit ausgeführt wird. (In dem Diagramm, auf das ich verweise, ist die PCID-Optimierung nicht enthalten, aber für kleine Arbeitsgruppen ist dies möglicherweise nicht so wichtig.) Ich bin mir also nicht sicher, ob PTI dort einen großen Einfluss hat? brendangregg.com/blog/2018-02-09/…
sourcejedi
1
Oh, interessant, es ist also ein Silvermont mit 2 MB L2-Cache , sodass Ihr ddPuffer + Empfangspuffer nicht passt. Sie haben wahrscheinlich mit Speicherbandbreite statt mit Cache-Bandbreite der letzten Ebene zu tun. Möglicherweise erhalten Sie eine bessere Bandbreite mit 512-KB-Puffern oder sogar 64-KB-Puffern. (Laut straceauf meinem Desktop, writeund readwir geben 1048576 zurück. Ich denke, das bedeutet, dass wir die Kosten für den <-> Benutzer-Kernel für die TLB-Ungültigmachung + Branch-Prediction-Flush nur einmal pro MB und nicht pro 64 KB bezahlen, @sourcejedi. Es ist Spectre Ich denke, die Minderung hat die meisten Kosten)
Peter Cordes
1
@sourcejedi: Bei aktivierter Spectre-Schadensbegrenzung betragen die Kosten für eine sofortige syscallRückgabe ENOSYSbei Skylake mit aktivierter Spectre-Schadensbegrenzung ~ 1800 Zyklen, wobei der größte Teil laut @ BeeOnRope-Testswrmsr die BPU ungültig macht . Bei deaktivierter Schadensbegrenzung beträgt die Benutzer-> Kernel-> Benutzerumlaufzeit ~ 160 Zyklen. Wenn Sie jedoch viel Gedächtnis berühren, ist die Meltdown-Reduzierung ebenfalls von Bedeutung. Riesenseiten sollten helfen (weniger TLB-Einträge müssen neu geladen werden).
Peter Cordes
1
@PeterCordes Auf einem Single-Core-Unix-System würden wir sicherlich 1 Kontextwechsel pro 64K sehen, oder was auch immer Ihr Pipe-Puffer war, und das würde wehtun ... tatsächlich sehe ich auch die [gleiche Anzahl von Kontextwechseln mit 2 CPU-Kernen] ( unix.stackexchange.com/questions/439260/… ); Es muss auch ein Schlaf / Wach-Zyklus für jeden 64k als Kontextwechsel gezählt werden (zu einem nominalen "Leerlaufprozess"). Das Aufrechterhalten der Pipeline-Prozesse auf derselben CPU funktionierte tatsächlich mehr als doppelt so schnell.
Sourcejedi
6

Ihre Frage wird gestellt, als ob etwas vielleicht in der Einfachheit gewonnen würde, indem ein Nullprogramm anstelle einer Datei verwendet wird. Vielleicht können wir den Begriff "magische Dateien" loswerden und stattdessen nur "gewöhnliche Pfeifen" haben.

Aber bedenken Sie, eine Pipe ist auch eine Datei . Sie werden normalerweise nicht benannt und können daher nur über ihre Dateideskriptoren bearbeitet werden.

Betrachten Sie dieses etwas konstruierte Beispiel:

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

Mit der Prozessersetzung von Bash können wir dasselbe auf viel einfachere Weise erreichen:

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

Ersetzen Sie die grepfürecho und wir können unter der Decke sehen:

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

Das <(...) Konstrukt wird einfach durch einen Dateinamen ersetzt, und grep glaubt, dass es eine alte Datei öffnet. Es wird einfach nur benannt /dev/fd/63. Hier /dev/fdist ein magisches Verzeichnis, das Named Pipes für jeden Dateideskriptor erstellt, der von der Datei, die darauf zugreift, verwendet wird.

Wir könnten es weniger magisch mkfifomachen, eine Named Pipe zu lserstellen , die in und in allem auftaucht , genau wie eine gewöhnliche Datei:

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

Anderswo:

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

und siehe da, grep wird ausgeben foo.

Ich denke , wenn Sie Rohre und normale Dateien und spezielle Dateien wie / dev / null realisieren sind alle nur Dateien, es ist offensichtlich ein Null - Programm Umsetzung ist mehr komplex. Der Kernel muss Schreibvorgänge in eine Datei so oder so behandeln, aber im Fall von / dev / null kann er die Schreibvorgänge einfach auf den Boden fallen lassen, wohingegen er mit einer Pipe die Bytes tatsächlich an ein anderes Programm übertragen muss, das dies dann tun muss Lesen Sie sie tatsächlich.

Phil Frost
quelle
@ Lyle Ja? Warum wird dann ein Echo gedruckt /dev/fd/63?
Phil Frost
Hm. Guter Punkt. Nun, dies wird von Shells implementiert. Vielleicht unterscheidet sich Ihre Shell von der alten Bourne-Shell, mit der ich aufgewachsen bin.
Lyle
Ein Unterschied ist, dass das Echo nicht von stdin liest, während grep dies tut, aber ich kann mir nicht vorstellen, wie die Shell das wissen würde, bevor es sie ausführt.
Lyle
1
Und strace macht das klarer: für mich. Sie haben es genau richtig, mit Bash. Das Konstrukt '<(...)' macht etwas ganz anderes als <Dateiname. Hm. Ich habe etwas gelernt.
Lyle
0

Ich würde argumentieren, dass es ein Sicherheitsproblem gibt, das über historische Paradigmen und Leistungen hinausgeht. Die Begrenzung der Anzahl von Programmen mit privilegierten Ausführungsberechtigungen, egal wie einfach, ist ein grundlegender Grundsatz der Systemsicherheit. Ein Ersatz /dev/nullwäre sicherlich erforderlich, um solche Berechtigungen aufgrund der Verwendung durch Systemdienste zu haben. Moderne Sicherheits-Frameworks leisten hervorragende Arbeit, um Exploits zu verhindern, sind jedoch nicht narrensicher. Ein kernelgetriebenes Gerät, auf das als Datei zugegriffen wird, ist viel schwieriger auszunutzen.

NickW
quelle
Das klingt nach Unsinn. Das Schreiben eines fehlerfreien Kerneltreibers ist nicht einfacher als das Schreiben eines fehlerfreien Programms, das die Standardeingabe liest und verwirft. Dabei spielt es keine setuid oder irgendetwas sein müssen, so dass für beide /dev/nulloder ein vorgeschlagenen eingabe Verwerfen Programm, das Angriffsvektor gleich sein würde: Holen Ihnen ein Skript oder Programm , das als root läuft zu tun versuchen , etwas seltsam (wie zu lseekin /dev/nulloder offen es mehrmals aus dem gleichen Prozess oder IDK was. Oder /bin/nullmit einer seltsamen Umgebung aufrufen , oder was auch immer).
Peter Cordes
0

Wie andere bereits ausgeführt haben, /dev/null handelt es sich um ein Programm, das aus einer Handvoll Codezeilen besteht. Es ist nur so, dass diese Codezeilen Teil des Kernels sind.

Um es klarer zu machen, hier ist die Linux-Implementierung: Ein Zeichengerät ruft Funktionen auf, wenn es gelesen oder geschrieben wird. Schreiben in /dev/nullAufrufe write_null , während Lesen in Aufrufe read_null , die hier registriert sind .

Buchstäblich eine Handvoll Codezeilen: Diese Funktionen tun nichts. Sie benötigen nur dann mehr Codezeilen als Finger auf Ihren Händen, wenn Sie andere Funktionen als Lesen und Schreiben zählen.

Matthieu Moy
quelle
Vielleicht hätte ich es genauer formulieren sollen. Ich meinte, warum es als char-Gerät anstelle eines Programms zu implementieren. Es wären ein paar Zeilen, aber die Programmumsetzung wäre entschieden einfacher. Wie die anderen Antworten gezeigt haben, hat dies einige Vorteile. Effizienz und Portabilität unter ihnen.
Ankur S
Sicher. Ich habe diese Antwort gerade hinzugefügt, weil es Spaß gemacht hat, die eigentliche Implementierung zu sehen (ich habe sie kürzlich selbst entdeckt), aber der wahre Grund ist, worauf andere in der Tat hingewiesen haben.
Matthieu Moy
Ich auch! Ich habe vor kurzem angefangen, Geräte unter Linux zu lernen und die Antworten waren ziemlich informativ
Ankur S
-2

Ich hoffe, dass Sie auch die / dev / chargen / dev / zero und andere wie diese kennen, einschließlich / dev / null.

LINUX / UNIX stellt einige davon zur Verfügung, damit die Benutzer gut geschriebene Codefragen nutzen können.

Chargen wurde entwickelt, um ein spezifisches und sich wiederholendes Zeichenmuster zu generieren. Es ist recht schnell und würde die Grenzen serieller Geräte überschreiten und beim Debuggen von seriellen Protokollen helfen, die geschrieben wurden und bei einem Test oder einem anderen fehlgeschlagen sind.

Mit Zero können Sie eine vorhandene Datei füllen oder eine ganze Reihe von Nullen ausgeben

/ dev / null ist nur ein weiteres Tool mit der gleichen Idee.

Alle diese Tools in Ihrem Toolkit bedeuten, dass Sie die halbe Chance haben, ein vorhandenes Programm dazu zu bringen, etwas Einzigartiges zu tun, ohne Rücksicht auf ihre (Ihre speziellen Bedürfnisse) als Geräte oder zum Ersetzen von Dateien

Lass uns einen Wettbewerb veranstalten, um zu sehen, wer mit nur wenigen Zeichengeräten in deiner LINUX-Version das aufregendste Ergebnis erzielen kann.

hilfreich
quelle