Richtige Handhabung von EINTR in Bibliotheken

10

Was ist die empfohlene Etikette EINTRin Bibliotheken?

Ich schreibe derzeit eine Funktion, die einige Dateisystemaufgaben mit der POSIX-API ausführt, aber viele der von mir verwendeten Aufrufe können möglicherweise zurückgegeben werden EINTR. Darüber hinaus kann die Funktion unter bestimmten Umständen blockieren. (Für Interessierte implementiert es einen Verriegelungsmechanismus.)

Um dies so allgemein wie möglich zu gestalten, möchte ich wissen, wie mit einem unterbrochenen Systemaufruf richtig umgegangen werden kann.

  • Aus den meisten Quellen, die ich gelesen habe, wird der Anruf normalerweise wiederholt und das Geschäft fortgesetzt. Ich bin mir jedoch nicht sicher, ob dies das Richtige ist, da es legitime Gründe geben kann, meine Funktion zu unterbrechen, da dies sehr viel Zeit in Anspruch nehmen kann. Darüber hinaus bedeutet EINTRdies, dass die Funktion einfach verschluckt wird und der Anrufer jeden Hinweis darauf verliert, dass sie aufgetreten ist.

  • Meine derzeitige Strategie besteht darin, den Vorgang sofort abzubrechen, wenn ich EINTRden Anrufer erhalte und benachrichtige. Auf diese Weise kann der Anrufer entscheiden, ob er meine Funktion wiederholen möchte, oder? (Oder vielleicht ist mein Verständnis von Signalen fehlerhaft?)

Rüschenwind
quelle
1
Was macht Ihre Bibliothek? Ich neige dazu, Ihre derzeitige Strategie zu genehmigen (den Vorgang abzubrechen EINTRund dies dem Anrufer zu melden), aber es kann davon abhängen, was Ihre Bibliothek tut ...
Basile Starynkevitch
1
In diesem Fall wird ein Sperrvorgang ausgeführt.
Rufflewind

Antworten:

2

Überlassen Sie die Entscheidung über den Umgang immer EINTRdem Benutzer und machen Sie es einfach, den Vorgang entsprechend fortzusetzen.

Normalerweise ist der beste Weg, dies zu tun, die Rückkehr von Ihrer Bibliotheksfunktion zum Aufrufer EINTR, aber in einigen Fällen ist ein Rückruf oder eine andere Implementierung möglicherweise besser - welcher Weg am besten ist, hängt von anderen Faktoren ab, aber lassen Sie den Benutzer immer wieder versuchen, und fortsetzen.

Dies bedeutet, dass Sie, wenn Ihr Bibliothekscode teilweise erfolgreich sein kann , bevor Sie einen erhalten EINTR, sorgfältig überlegen sollten, was der Benutzer möglicherweise über diesen teilweisen Erfolg wissen muss oder ob der Benutzer den Vorgang möglicherweise dort fortsetzen muss, wo er fehlgeschlagen ist. Möglicherweise müssen Sie zusätzliche Informationen zurückgeben oder eine Schnittstelle für die Wiederaufnahme von jedem Ort bereitstellen, für den dies angemessen sein könnte.

Dies ist der Grund, warum Systemaufrufe wie readund writeheutzutage teilweise Erfolg zurückgeben - weil es als Benutzer sehr frustrierend ist, gesagt zu werden:

Sie haben versucht zu schreiben "foo"und wir haben erfolgreich entweder nichts oder "f"oder geschrieben "fo", und Sie wissen nicht, welche . Habe Spaß! Ich hoffe, Ihr System kann den gesamten Schreibvorgang nach einem dieser Schritte neu starten!

Natürlich, in einigen Fällen, wir sollten Systeme Griff Situationen schreiben genau so - zum Beispiel, vielleicht nach einer partiellen Schreib Sie die Datei immer neu, oder die Netzwerkverbindung wieder öffnen, oder Sie verwenden , um einige Byte „Start über“ zu bedeuten - Es hängt also davon ab, auf welche Anwendungsfälle Ihre Bibliothek abzielt.

Wenn eine Bibliotheksfunktion mehrere Operationen ausführt und es keine Möglichkeit gibt zu wissen, bei welcher von ihnen sie fehlgeschlagen ist, und diese Operationen nicht alle sicher und effizient idempotent sind, macht dies eine Bibliothek im Grunde genommen für Code unbrauchbar, der robust sein muss.

Wenn alle Schritte in einer Bibliotheksfunktion sind sicher und effizient idempotent, oder ist das Ganze Atom - wie eine Sperre zu erwerben - dann nur den Benutzer wissen zu lassen , dass ein EINTRzufällig ist genug.

Wenn wir es erneut versuchen EINTR, können wir auch die Signalbehandlung unterbrechen . Auf der niedrigen Ebene können Signalhandler nur einen begrenzten Satz von Funktionen sicher verwenden. In vielen Fällen setzt ein Signalhandler lediglich einen Booleschen Wert, der angibt, dass das Signal empfangen wurde, und kehrt dann zurück, in der Erwartung, dass der Code fortgesetzt wird Verlassen Sie alles, was es tat. Wenn wir eine erhalten EINTRund dann erneut versuchen, anstatt die Kontrolle an den Benutzer zurückzugeben, verhindern wir möglicherweise, dass der Code dies tut.

Was nach einer vollständigen Programmentscheidung zu tun EINTRist - die richtige Antwort kann nicht bekannt sein, ohne zu wissen, was das Programm tut und wie das Programm auf ein Signal reagieren soll, und es hat Auswirkungen auf den Rest des Programms.

Zu wissen, wie oder ob der Benutzer möglicherweise fortfahren muss, und dem Benutzer bei Bedarf zu helfen, liegt in der Verantwortung der Bibliothek. Die richtige Antwort kann nicht bekannt sein, ohne zu wissen, was die Bibliothek tut.

mtraceur
quelle
4

Signale in Unix

Wenn ein anderer Prozess Ihr Prozesssignal sendet, stoppt Ihr Programm, was es tut ...

1) Führen Sie den von Ihnen geschriebenen Handler-Code aus. Sie haben keine Ahnung, was Ihr Programm tun könnte, wenn das Signal eintrifft. Das ist die Idee mit Signalen, sie können vollständig asynchron sein.

2) Wenn der Signalhandler fertig ist, kehrt er normalerweise nur zurück, und Ihr Programm wird dort fortgesetzt, wo es aufgehört hat, als wäre nichts passiert.

In Richard Stevens wurden einige nützliche Informationen gefunden

Ein Merkmal früherer UNIX-Systeme ist, dass der Systemaufruf unterbrochen wurde, wenn ein Prozess ein Signal abfing, während der Prozess in einem "langsamen" Systemaufruf blockiert wurde. Der Systemaufruf gab einen Fehler zurück und errno wurde auf EINTR gesetzt. Dies geschah unter der Annahme, dass, da ein Signal aufgetreten ist und der Prozess es abgefangen hat, eine gute Chance besteht, dass etwas passiert ist, das den blockierten Systemaufruf aufwecken sollte.

Die POSIX.1-Semantik für unterbrochene Lese- und Schreibvorgänge wurde mit der Version 2001 des Standards geändert. Frühere Versionen gaben Implementierungen die Wahl, wie mit Lese- und Schreibvorgängen umgegangen werden soll, bei denen teilweise Datenmengen verarbeitet wurden. Wenn das Lesen Daten empfangen und in den Puffer einer Anwendung übertragen hat, aber noch nicht alle von der Anwendung angeforderten Daten empfangen hat und dann unterbrochen wird, kann das Betriebssystem entweder den Systemaufruf mit errno auf EINTR fehlschlagen lassen oder den Systemaufruf erfolgreich ausführen und zurückkehren lassen die teilweise Menge der empfangenen Daten. Wenn der Schreibvorgang nach der Übertragung einiger Daten im Puffer einer Anwendung unterbrochen wird, kann das Betriebssystem den Systemaufruf entweder mit errno auf EINTR fehlschlagen lassen oder den Systemaufruf erfolgreich ausführen und die teilweise geschriebene Datenmenge zurückgeben. Historisch, Von System V abgeleitete Implementierungen schlagen den Systemaufruf fehl, während von BSD abgeleitete Implementierungen einen teilweisen Erfolg zurückgeben. In der Version 2001 des POSIX.1-Standards ist die Semantik im BSD-Stil erforderlich.

Das Problem bei unterbrochenen Systemaufrufen besteht darin, dass wir die Fehlerrückgabe jetzt explizit behandeln müssen. Die typische Codesequenz (unter der Annahme einer Leseoperation und unter der Annahme, dass wir den Lesevorgang neu starten möchten, auch wenn er unterbrochen ist) wäre

again:
    if ((n = read(fd, buf, BUFFSIZE)) < 0) {
        if (errno == EINTR)
            goto again;     /* just an interrupted system call */
        /* handle other errors */
    }

Um zu verhindern, dass Anwendungen unterbrochene Systemaufrufe verarbeiten müssen, führte 4.2BSD den automatischen Neustart bestimmter unterbrochener Systemaufrufe ein.

Prabhakar Gudimani
quelle
3
Dies beantwortet die Frage nicht. So sollten Bibliotheken das Problem angehen. Wenn ich weiß, dass ich EINTR in bestimmten Vorgängen ignorieren möchte, kann ich dies mit diesem Aufruf retry ( goto again) tun . Aber als Bibliotheksentwickler weiß ich nicht, ob mein Bibliotheksbenutzer dies wünscht.
Messa