sigsuspend vs zusätzliche Signale während der Ausführung des Handlers geliefert

8

sigsuspend ändert die Signalmaske und unterbricht die Ausführung des aufrufenden Threads, bis er empfangen wird ein "Signal, dessen Wirkung entweder darin besteht, eine Signalfangfunktion auszuführen oder den Prozess zu beenden" und dann (falls der Prozess nicht beendet wird und der Signalhandler zurückkehrt) die Signalmaske in ihren ursprünglichen Zustand zurückversetzt.

Die verlinkte Seite von POSIX.1-2008 sagt nicht, ob es möglich ist, multiple -Signale innerhalb eines einzigen Aufrufs an sigsuspend zu liefern, noch sagt es etwas über die Atomizität von die Signalmaske ändert sich; d. h. es scheint mir, dass dies eine konforme Implementierung von sigsuspend ist, obwohl der ganze Punkt von sigsuspend ist, dass es nicht die Race Condition hat, die dieser Code hat:

%Vor%

Das Szenario, um das ich mich eigentlich Sorgen mache, ist ein Programm, das SIGUSR1 verwendet, um mit sich selbst zu kommunizieren (es ist eine lange Geschichte) und ich brauche einen Weg, um sicher zu sein, dass der Signalhandler nur einmal ausführt > pro internem Aufruf an sigsuspend , auch wenn andere Prozesse auf demselben System Signale senden.

Meine Fragen sind also:

  1. Gibt es eine Anforderung (in POSIX oder einem anderen relevanten Standard), höchstens ein Signal (von beliebiger Art) zu liefern nett) per Anruf nach sigsuspend?
  2. Gibt es eine Anforderung (dito) für sigsuspend, die Signalmaske zu ändern, die Ausführung zu unterbrechen und die Signalmaske atomar wiederherzustellen? Das heißt, ohne das Risiko, dass ein Signal zwischen den drei Systemaufrufen in der oben beschriebenen hypothetischen Benutzerraum-Implementierung geliefert wird?

Da dies ziemlich abstrakt ist, unter einem Testprogramm, das ich gern immer 1 drucken und erfolgreich beenden würde, aber besorgt bin, dass es unter Umständen 2 oder 0 ausgibt, hänge bis zum Alarm geht ab oder stürzt ab. (C11 Atomics aus einer Überfülle von Vorsicht verwendet; technisch ist es nicht erlaubt, lesen a volatile sig_atomic_t von einem Signal-Handler, nur schreiben zu einem.) Es verwendet SIGUSR1 standardmäßig und SIGRTMIN, wenn Sie -r in der Befehlszeile übergeben.

%Vor%     
zwol 14.11.2016, 15:17
quelle

3 Antworten

4

1) Besteht die Anforderung, höchstens ein Signal zu verarbeiten?

Nein. Tatsächlich ist das Gegenteil empfohlen . POSIX 2008 Band 4: Begründung, §B.2.4.1 Signalgenerierung und -bereitstellung besagt:

  

Wenn mehrere anstehende Signale vorhanden sind, die nicht blockiert sind, sollten die Implementierungen möglichst alle Signale gleichzeitig liefern. Einige Implementierungen stapeln Aufrufe an alle anstehenden Signalfangroutinen, wodurch der Eindruck entsteht, dass jeder Signalfänger vom nächsten Signal unterbrochen wurde. In diesem Fall sollte die Implementierung sicherstellen, dass diese Stapelung von Signalen nicht die Semantik der durch sigaction() festgelegten Signalmasken verletzt. Andere Implementierungen verarbeiten höchstens ein Signal, wenn das Betriebssystem betreten wird, wobei verbleibende Signale für eine spätere Lieferung gespeichert werden. Obwohl diese Praxis weit verbreitet ist, ist dieses Verhalten weder standardisiert noch befürwortet. In jedem Fall sollten Implementierungen versuchen, Signale, die mit dem aktuellen Status des Prozesses verknüpft sind (z. B. SIGFPE ), vor anderen Signalen, wenn möglich, zu liefern.

Darüber hinaus ist möglich, dass mehrere Signale mit derselben Signalnummer gleichzeitig in die Warteschlange eingereiht werden. POSIX 2008 Band 2: Systemschnittstellen, §2.4.1 Signalerzeugung und -zustellung states

  

Wenn ein nachfolgendes Auftreten eines anstehenden Signals erzeugt wird, ist es implementationsdefiniert, ob das Signal mehr als einmal in anderen Umständen als denen, in denen eine Warteschlangenbildung erforderlich ist, geliefert oder akzeptiert wird. Die Reihenfolge, in der mehrere gleichzeitig anstehende Signale außerhalb des Bereichs SIGRTMIN an SIGRTMAX an einen Prozess gesendet oder von diesem akzeptiert werden, ist nicht spezifiziert.

Dies gilt auch für Echtzeitsignale. POSIX 2008 Band 2: Systemschnittstellen, §2.4.2 Erzeugung und Lieferung von Echtzeitsignalen .

  

[...] Mehrfache Vorkommen von so erzeugten Signalen werden in FIFO-Reihenfolge eingereiht. [...]

     

Wenn bei der Lieferung eines anstehenden Signals zusätzliche Signale in die Signalnummer eingereiht werden, muss das Signal anstehen. Andernfalls muss die ausstehende Anzeige zurückgesetzt werden.

2) Gibt es eine Anforderung für Atomizität?

Legitially Debatable. Es war wahrscheinlich beabsichtigt, aber es ist nicht wirklich normativ geschrieben. Es gibt gute Argumente dafür und dagegen:

Der Fall für Atomarität:

POSIX 2008 deutet stark auf Atomizität in Band 2: Systemschnittstellen, §3 pause() :

hin
  

ANWENDUNGSVERWENDUNG

     

Viele häufige Verwendungen von pause() haben Zeitfenster. In diesem Szenario wird eine mit einem Signal zusammenhängende Bedingung überprüft und, falls das Signal nicht aufgetreten ist, wird pause() aufgerufen. Wenn das Signal zwischen der Überprüfung und dem Aufruf von pause() auftritt, blockiert der Prozess oft unbestimmt. Die Funktionen sigprocmask() und sigsuspend() können verwendet werden, um diese Art von Problem zu vermeiden.

Eine ähnlich starke Implikation erscheint unter der Überschrift RATIONALE für sleep() . Darüber hinaus ist das Wort kann in POSIX 2008 definiert Band 1: Basisdefinitionen, §1.5 Terminologie ' als:

  

Für die Zwecke von POSIX.1-2008 gelten die folgenden Terminologiedefinitionen:

     

kann

     

Beschreibt ein zulässiges optionales Merkmal oder Verhalten, das dem Benutzer oder der Anwendung zur Verfügung steht. Die Funktion oder das Verhalten ist für eine Implementierung erforderlich, die POSIX.1-2008 entspricht. Eine Anwendung kann sich auf das Vorhandensein des Features oder Verhaltens verlassen.

Und POSIX 2008 Band 4: Begründung, §A.1.5 Terminologie besagt:

  

darf

     

Die Verwendung von may wurde so weit wie möglich eingeschränkt, sowohl aufgrund von Verwechslungen, die sich aus ihrer gewöhnlichen englischen Bedeutung ergeben, als auch aus Einwänden hinsichtlich der Erwünschtheit, möglichst wenige Optionen zu haben und solche, die klar spezifiziert sind wie möglich.

     

Die Verwendung von can und may wurde ausgewählt, um das optionale Anwendungsverhalten (can) gegen das optionale Implementierungsverhalten (may) zu setzen.

Das heißt, die Überschrift von pause() APPLICATION USAGE besagt, dass eine Anwendung nicht verpflichtet ist, sigsuspend() aufzurufen, um Timing-Probleme zu vermeiden, aber sollte sie sich dafür entscheiden, dann POSIX.1 -2008-konforme Implementierungen von sigsuspend() werden benötigt, um diese Timing-Window-Probleme zu vermeiden (zB atomar mit Hilfe des Kernels).

Wenn sigsuspend() nicht atomar ist, dann

  • Die Aussage, dass eine Anwendung Zeitfensterprobleme vermeiden kann, indem sigsuspend() verwendet wird, ist falsch, und daher sind alle Versionen des POSIX.1-Standards seit dem ersten Jahr 1988 intern inkonsistent.
  • sigsuspend() wäre nutzlos, da es über pause() hinaus keinen Sinn hätte. Man müsste dann fragen, warum POSIX.1 enthalten beide seit der ersten Version zusammen mit einer falschen Empfehlung, sigsuspend() gegenüber pause() zu verwenden.

Der Fall gegen Atomarität:

POSIX 2008 Band 2: Systemschnittstellen, §1.2 Format der Einträge besagt:

  

[...]

     

ANWENDUNGSVERWENDUNG

     

Dieser Abschnitt ist informativ.

     

In diesem Abschnitt finden Anwendungsentwickler Warnungen und Hinweise zum Eintrag. Im Falle eines Konflikts zwischen Warnungen und Ratschlägen und einem normativen Teil dieses Bandes von POSIX.1-2008 ist das normative Material als richtig zu betrachten.

     

RATIONALE

     

Dieser Abschnitt ist informativ.

     

Dieser Abschnitt enthält historische Informationen über den Inhalt dieses POSIX.1-2008-Bandes und warum Features von den Standardentwicklern eingeschlossen oder verworfen wurden.

     

[...]

Dies bedeutet, dass diese Abschnitte informativ , nicht normativ sind und nur normative Abschnitte Anforderungen direkt an den Implementierer stellen können. Sie können jedoch bei der Interpretation des Standards helfen und die Aussage, dass " Die Funktionen sigprocmask() und sigsuspend() können verwendet werden, um diese Art von Problem zu vermeiden " nicht Konflikt mit irgendeinem normativen Teil des Standards.

Neutralfall

POSIX 2008 Band 4: Begründung, §A.1.5 Terminologie besagt:

  

Implementierung definiert

     

Diese Definition ist analog zu der des ISO C-Standards und bietet zusammen mit "undefined" und "unspezifiziert" eine Reihe von Freiheitsspezifikationen, die dem Schnittstellenimplementierer erlaubt sind.

     

[...]

     

nicht angegeben

     

Siehe Implementierung definiert.

     

[...]

     

POSIX.1-2008 schweigt an vielen Stellen über das Verhalten eines möglichen Konstrukts. Zum Beispiel kann eine Variable für einen spezifizierten Bereich von Werten definiert werden, und Verhaltensweisen werden für diese Werte beschrieben; Es wird nichts darüber gesagt, was passiert, wenn die Variable einen anderen Wert hat. Diese Art von Schweigen kann einen Fehler im Standard bedeuten, aber es kann auch bedeuten, dass der Standard absichtlich still war und jegliches Verhalten erlaubt ist. Es gibt eine natürliche Tendenz zu folgern, dass, wenn der Standard still ist, ein Verhalten verboten ist. Das ist nicht die Absicht. Stille soll dem Begriff "unspezifiziert" gleichkommen.

Der ISO C-Standard (derzeit C11) definiert "implementierungsdefiniertes Verhalten" als Verhalten, bei dem die Implementierung wählen kann, aber ihre Wahl dokumentieren muss.

Das Fehlen jeglicher normativer Aussage über die Atomizität von sigsuspend() ist vielleicht entweder ein Fehler oder entspricht einer expliziten Aussage, dass es umsetzungsdefiniertes Verhalten ist.

  • Die Anwendungshinweise und Begründungen in pause() und sleep() legen nahe, dass es sich um einen Fehler im Standard handelt.
  • Umgekehrt hat eine implizite normative Aussage, dass die Atomizität von sigsuspend() implementationsdefiniert ist, Vorrang vor einer informativen gegenteiligen Aussage.

Implementierungen

Können wir uns also von In-the-Wild-Implementierungen inspirieren lassen? Vielleicht. Ich kenne keine Implementierungen, die es nicht atomar implementieren:

  • Linux -Kerne implementieren sigsuspend() atomisch.
  • BSD -Kerne implementieren sigsuspend() atomisch.
Iwillnotexist Idonotexist 01.12.2016, 00:23
quelle
5

Dies ist keine Antwort, sondern ein exploratives Programm. Ich hoffe, dass ein freundlicher Benutzer von macOS und / oder * BSD es auf ihren Rechnern testet und ihre Ergebnisse meldet.

Nur aus Interesse habe ich ein grobes Programm geschrieben, um zu bewerten, wie oft mehr als ein Signal pro Anruf an sigsuspend() geliefert wird.

Die Idee im folgenden Programm ist es, den Benutzer das Signal in der Befehlszeile angeben zu lassen. Der Prozess wird einen untergeordneten Prozess abzweigen.

Der untergeordnete Prozess blockiert dieses Signal und installiert dann einen Signalhandler, der (unter Verwendung von integrierten GCC-Bausteinen) einen Zähler inkrementiert. Es tritt dann in eine Schleife ein, wo es sich selbst stoppt (via raise(SIGSTOP) ). Der Elternprozess wird dies erkennen und einen Cluster der Signale senden, dann SIGCONT . Wenn der Kindprozess aufwacht, ruft er sigsuspend() auf, bis keine weiteren Signale mehr anstehen. Für jeden sigsuspend() Aufruf wird die Anzahl der gelieferten Signale gezählt. Nachdem keine Signale mehr anstehen, stoppt das Kind sich selbst.

Das übergeordnete Element sendet die Hälfte der (Kopien des) Signals mit kill() , der Rest mit sigqueue() mit variierender Nutzlast.

Dies ist ein völliger Hack, darf dem POSIX.1-Standard überhaupt nicht folgen und enthält wahrscheinlich viele Bugs. (Wenn Sie etwas bemerken, lassen Sie es mich bitte in einem Kommentar wissen, damit ich es beheben kann.)

%Vor%

Speichern des obigen als z.B. hack.c und kompiliert und führt es mit

aus %Vor%

liefert die Ausgabe für den Fall, um den sich das OP Sorgen macht. In Linux auf x86-64 Architektur (Kernel 4.4.0, GCC 5.4.0) gibt es so etwas wie

aus %Vor%

Die obige Ausgabe zeigt, dass alle 400.000 mal sigsuspend() aufgerufen wurde, nur ein Signal wurde geliefert. (Es wurde jedoch nur eine Instanz des Signals gesendet, selbst wenn das Elternelement es acht Mal gesendet hat: vier mit unterschiedlichen sigqueue() Nutzlasten, vier mit kill () '.)

Wie ich in einem Kommentar erwähnt habe, glaube ich, dass der Linux-Kernel basierend auf den Linux-Kernel-Quellen nur ein Signal pro sigsuspend() -Aufruf liefert, wenn das Signal vor (und somit nach) dem sigsuspend() -Aufruf blockiert ist. Die obige Ausgabe unterstützt diese Annahme.

Wenn Sie dasselbe mit einem Echtzeitsignal ausführen, sagen wir ./hack SIGRTMIN+0 , gibt etwas wie

aus %Vor%

zeigt, dass bei diesem Lauf jede einzelne Instanz des Signals genau einmal pro sigsuspend() -Aufruf geliefert wurde.

Dieses Programm kann keinen Beweis dafür liefern, dass ein System so funktioniert, wie das OP hofft (oder dass Linux so funktioniert, wie ich glaube, dass es auf Basis der Kernel-Quellen funktioniert). Es kann nur verwendet werden, um es zu widerlegen (und auf Linux, meine Überzeugung). Wenn es ein System gibt, das meldet

%Vor%

mit # 2 oder größer, dann wissen wir, dass auf diesem System mehr als eine (Kopie des) Signals pro sigsuspend () -Aufruf geliefert wird.

(Der Fall, dass # 0 ist, sollte nur auftreten, wenn das ausgewählte Signal SIGINT, SIGHUP oder SIGTERM ist, diese werden auch gefangen, so dass der Benutzer das Programm stoppen kann.)

Es ist natürlich nicht garantiert, dass dieses Programm selbst dann, wenn es auf einem solchen System läuft, auf den Fall mehrerer Lieferungen stolpert.

Wenn jedoch der Kindprozess angehalten wird, mehrere (Kopien eines) Signals erhalten und dann fortgesetzt werden, wäre dies der optimale Punkt für ein System, mehr als eine (Kopie des) Signals pro sigsuspend () -Aufruf zu liefern. Wenn es das in diesem Fall nicht tut, was wäre der Fall, wo es wäre?

    
Nominal Animal 02.12.2016 05:31
quelle
3

Dies ist die Gliederung der sem_post() / sem_wait() Abhilfe, die ich in einem Kommentar zur ursprünglichen Frage erwähnt habe.

Dies setzt voraus, dass SIGUSR1 normalerweise blockiert ist; nur für die Dauer des sigsuspend() -Aufrufs nicht gesperrt.

Zunächst wird der Signal-Handler durch einen einfachen Signal-Handler ersetzt, der nur einen dedizierten Semaphor sendet:

%Vor%

Bei jedem Aufruf von sigsuspend() , das den obigen Signal-Handler freigibt, wird dieses Codebeispiel unmittelbar danach hinzugefügt:

%Vor%

Technisch kehrt sigsuspend() zurück, bevor die eigentliche Arbeit erledigt ist. Da die Arbeit jedoch im selben Thread ausgeführt wird, sollte es keinen wirklichen Unterschied geben, wenn die Arbeit vor der Anweisung ausgeführt wird, die auf sigsuspend() folgt. Schließlich wirkt sich sigsuspend() nur auf die Thread-Signalmaske aus, so dass die Reihenfolge (zwischen der Arbeit und der Rückgabe von sigsuspend() ) rein intern zum Thread ist.

(Editiert, um darauf hinzuweisen, dass OP, zwol, darauf hinweist, dass es Fälle gibt, in denen es darauf ankommt. Im Falle des OPs verwendet der Signalhandler einen alternativen Stapel, was bedeutet, dass die Arbeit nicht wirklich in den normalen Programmablauf verschoben werden kann. )

Sie könnten natürlich dasselbe mit einer atomaren Variablen pro Thread erreichen.

(Zu diesem Zeitpunkt unterstützen nicht alle C-Compiler atomare Operationen auf portable Weise, aber alle POSIXy-Systeme sollten Semaphore unterstützen. Ich würde persönlich atomare Ops verwenden, aber eingepackt in Hilfsfunktionen, um sie bei Bedarf einfach portieren zu können .)

    
Nominal Animal 02.12.2016 05:54
quelle

Tags und Links