Gilles 'Antwort erklärt den Rennzustand. Ich werde nur diesen Teil beantworten:
Gibt es eine Möglichkeit, dieses Skript zu zwingen, immer 0 Zeilen auszugeben (daher wird die E / A-Umleitung zu tmp immer zuerst vorbereitet und die Daten werden immer zerstört)? Um klar zu sein, meine ich das Ändern der Systemeinstellungen
IDK, wenn ein Tool dafür bereits vorhanden ist, aber ich habe eine Idee, wie eines implementiert werden könnte. (Beachten Sie jedoch, dass dies nicht immer 0 Zeilen sind, sondern nur ein nützlicher Tester, der einfache Rennen wie dieses leicht fängt, und einige kompliziertere Rennen. Siehe @ Gilles 'Kommentar .) Es würde nicht garantieren, dass ein Skript sicher ist , aber möglicherweise Dies ist ein nützliches Tool zum Testen, ähnlich dem Testen eines Multithread-Programms auf verschiedenen CPUs, einschließlich schwach geordneter Nicht-x86-CPUs wie ARM.
Sie würden es als laufen lassen racechecker bash foo.sh
Verwenden Sie die gleiche System-Call - Tracing / Abfangen von Einrichtungen , die strace -f
und die ltrace -f
Verwendung jedes Kind Prozess anhängen. (Unter Linux ist dies derselbe ptrace
Systemaufruf, der von GDB und anderen Debuggern verwendet wird , um Haltepunkte festzulegen, einzelne Schritte auszuführen und Speicher / Register eines anderen Prozesses zu ändern.)
Instrument der open
und openat
Systemaufrufe: wenn jeder Prozess unter diesem Tool läuft spielt einen open(2)
Systemaufruf (oder openat
) mit O_RDONLY
, schläft vielleicht 1/2 oder 1 Sekunde. Lassen Sie andere open
Systemaufrufe (insbesondere solche einschließlich O_TRUNC
) unverzüglich ausführen.
Dies sollte es dem Schreiber ermöglichen, das Rennen in nahezu jeder Rennbedingung zu gewinnen, es sei denn, die Systemlast war ebenfalls hoch oder es war eine komplizierte Rennbedingung, bei der die Kürzung erst nach einem anderen Lesevorgang erfolgte. Eine zufällige Variation, bei der open()
s (und möglicherweise read()
s oder Schreibvorgänge) verzögert sind, würde die Erkennungsleistung dieses Tools erhöhen, aber natürlich ohne unendlich lange Tests mit einem Verzögerungssimulator, der schließlich alle möglichen Situationen abdeckt, in denen Sie auftreten können In der realen Welt können Sie nicht sicher sein, dass Ihre Skripte frei von Rennen sind, wenn Sie sie nicht sorgfältig lesen und beweisen , dass dies nicht der Fall ist .
Sie würden wahrscheinlich es auf die weiße Liste (nicht Verzögerung benötigen open
für Dateien in) /usr/bin
und /usr/lib
so Prozessstart immer nicht statt. (Die dynamische Laufzeitverknüpfung muss open()
mehrere Dateien umfassen (siehe strace -eopen /bin/true
oder /bin/ls
irgendwann). Wenn die übergeordnete Shell selbst die Kürzung vornimmt, ist dies in Ordnung. Es ist jedoch gut, wenn dieses Tool Skripte nicht unangemessen langsam macht.)
Oder vielleicht eine Whitelist für jede Datei, für die der aufrufende Prozess überhaupt keine Berechtigung zum Abschneiden hat. Das heißt, der Ablaufverfolgungsprozess kann einen access(2)
Systemaufruf ausführen, bevor der Prozess, der open()
eine Datei enthalten wollte, tatsächlich angehalten wird .
racechecker
selbst müsste in C geschrieben werden, nicht in der Shell, könnte aber möglicherweise strace
den Code als Ausgangspunkt verwenden und erfordert möglicherweise nicht viel Arbeit bei der Implementierung.
Sie könnten möglicherweise die gleiche Funktionalität mit einem FUSE-Dateisystem erhalten . Es gibt wahrscheinlich ein FUSE-Beispiel für ein reines Passthrough-Dateisystem, sodass Sie der open()
Funktion Überprüfungen hinzufügen können , die sie für schreibgeschützte Öffnungen in den Ruhezustand versetzen, aber das Abschneiden sofort zulassen.
racechecker
ständig verwenden. Und wahrscheinlich möchten Sie die Ruhezeit zum Lesen öffnen, um sie für Benutzer auf sehr stark ausgelasteten Computern konfigurierbar zu machen, die sie höher einstellen möchten, z. B. 10 Sekunden. Oder setzen Sie ihn senken, wie 0,1 Sekunden für lange oder ineffiziente Skripte , die Wieder geöffneten Dateien eine Menge .Warum gibt es eine Rennbedingung
Die beiden Seiten eines Rohrs werden parallel ausgeführt, nicht nacheinander. Es gibt eine sehr einfache Möglichkeit, dies zu demonstrieren: Ausführen
Dies dauert eine Sekunde, nicht zwei.
Die Shell startet zwei untergeordnete Prozesse und wartet, bis beide abgeschlossen sind. Diese beiden Prozesse ausführen parallel: der einzige Grund , warum einer von ihnen mit der anderen synchronisieren würde, wenn es muss für den anderen warten. Der häufigste Synchronisationspunkt ist, wenn die rechte Seite blockiert und darauf wartet, dass Daten auf ihrer Standardeingabe gelesen werden, und wenn die linke Seite mehr Daten schreibt, wird die Blockierung aufgehoben. Das Umgekehrte kann auch passieren, wenn die rechte Seite Daten nur langsam liest und die linke Seite ihre Schreiboperation blockiert, bis die rechte Seite mehr Daten liest (in der Pipe selbst befindet sich ein Puffer, der von der verwaltet wird Kernel, aber es hat eine kleine maximale Größe).
Beachten Sie die folgenden Befehle, um einen Synchronisationspunkt zu beobachten (
sh -x
druckt jeden Befehl während der Ausführung aus):Spielen Sie mit Variationen, bis Sie mit dem, was Sie beobachten, vertraut sind.
Gegeben den zusammengesetzten Befehl
Der Prozess auf der linken Seite führt Folgendes aus (ich habe nur Schritte aufgelistet, die für meine Erklärung relevant sind):
cat
mit dem Argument austmp
.tmp
Zum Lesen geöffnet .Der Prozess auf der rechten Seite führt Folgendes aus:
tmp
und kürzen Sie dabei die Datei.head
mit dem Argument aus-1
.Der einzige Synchronisationspunkt ist, dass Right-3 darauf wartet, dass Left-3 eine vollständige Zeile verarbeitet hat. Es gibt keine Synchronisation zwischen left-2 und right-1, daher können sie in jeder Reihenfolge erfolgen. In welcher Reihenfolge sie auftreten, ist nicht vorhersehbar: Dies hängt von der CPU-Architektur, der Shell, dem Kernel, den Kernen ab, auf denen die Prozesse geplant sind, von den Interrupts, die die CPU zu diesem Zeitpunkt empfängt usw.
So ändern Sie das Verhalten
Sie können das Verhalten nicht ändern, indem Sie eine Systemeinstellung ändern. Der Computer macht das, was Sie ihm sagen. Sie haben ihm gesagt, er soll abschneiden
tmp
undtmp
parallel lesen , damit die beiden Dinge parallel ausgeführt werden.Ok, es gibt eine "Systemeinstellung", die Sie ändern könnten: Sie könnten
/bin/bash
durch ein anderes Programm ersetzen , das nicht bash ist. Ich hoffe, es versteht sich von selbst, dass dies keine gute Idee ist.Wenn die Kürzung vor der linken Seite der Pipe erfolgen soll, müssen Sie sie außerhalb der Pipeline platzieren, z. B.:
oder
Ich habe keine Ahnung, warum Sie das wollen. Was bringt es, aus einer Datei zu lesen, von der Sie wissen, dass sie leer ist?
Wenn Sie dagegen möchten, dass die Ausgabeumleitung (einschließlich der Kürzung) nach dem
cat
Lesen erfolgt, müssen Sie entweder die Daten im Speicher vollständig puffern, zoder schreiben Sie in eine andere Datei und verschieben Sie sie. Dies ist normalerweise die robuste Methode, um Dinge in Skripten auszuführen, und hat den Vorteil, dass die Datei vollständig geschrieben wird, bevor sie durch den ursprünglichen Namen sichtbar wird.
Die moreutils- Sammlung enthält ein Programm namens
sponge
.So erkennen Sie das Problem automatisch
Wenn Ihr Ziel darin bestand, schlecht geschriebene Skripte zu erstellen und automatisch herauszufinden, wo sie kaputt gehen, ist das Leben leider nicht so einfach. Die Laufzeitanalyse findet das Problem nicht zuverlässig, da
cat
das Lesen manchmal vor dem Abschneiden beendet ist. Statische Analyse kann es im Prinzip tun; Das vereinfachte Beispiel in Ihrer Frage wird von Shellcheck abgefangen , in einem komplexeren Skript wird jedoch möglicherweise kein ähnliches Problem festgestellt .quelle
strace
(z. B. Linuxptrace
) verwenden könnten , um alleopen
Systemlesungen zum Lesen (in allen untergeordneten Prozessen) eine halbe Sekunde lang schlafen zu lassen, also wenn Sie mit Rennen fahren Bei einer Kürzung gewinnt die Kürzung fast immer.