termA friert nicht ein. Es zeigt nur den Rückruf im Eingabefeld an. Einfach Enter
drücken Taste um mit der Eingabe fortzufahren.
Bevor Sie Ihr Problem erläutern, ein wenig Kontext dazu, wie read
Befehl funktioniert. Es liest Eingabedaten von stdin
ein bis EOF
angetroffen wird. Es ist sicher, den Anruf zu read
zu sagen Der Befehl blockiert nicht, wenn es um das Lesen von Dateien auf der Festplatte geht. Aber wenn stdin
mit dem Terminal verbunden ist, blockiert der Befehl, bis der Benutzer etwas eingibt.
Wie funktionieren Signalhandler?
Eine einfache Erklärung, wie die Signalverarbeitung funktioniert. Siehe das folgende Snippet in C
die nur auf SIGINT
wirkt (auch bekannt als CTRL+C
)
#include <stdio.h>
#include <signal.h>
/* signal handler definition */
void signal_handler(int signum){
printf("Hello World!\n");
}
int main(){
//Handle SIGINT with a signal handler
signal(SIGINT, signal_handler);
//loop forever!
while(1);
}
Es registriert den Signalhandler und tritt dann in die Endlosschleife ein. Wenn wir Ctrl-C
treffen , wir sind uns alle einig, dass der Signalhandler signal_handler()
ausführen soll und "Hello World!"
druckt auf dem Bildschirm, aber das Programm befand sich in einer Endlosschleife. Um "Hello World!"
zu drucken es muss so gewesen sein, dass es die Schleife unterbrochen hat, um den Signal-Handler auszuführen, richtig? Es sollte also sowohl die Schleife als auch das Programm verlassen. Mal sehen:
gcc -Wall -o sighdl.o signal.c
./sighdl.o
^CHello World!
^CHello World!
^CHello World!
^CHello World!
^CHello World!
^CHello World!
^CHello World!
^CHello World!
^CHello World!
Wie die Ausgabe zeigt, haben wir jedes Mal Ctrl-C
ausgegeben , "Hello World!"
wird gedruckt, aber das Programm kehrt zur Endlosschleife zurück. Es ist nur nach Ausgabe eines SIGQUIT
Signal mit Ctrl-\
wurde das Programm tatsächlich beendet. Abhängig von Ihrem ulimit
Einstellungen, würde es einen Kern ausgeben oder die empfangene Signalnummer ausdrucken.
Während die Interpretation, dass die Schleife verlassen würde, vernünftig ist, berücksichtigt sie nicht den Hauptgrund für die Signalbehandlung, dh die asynchrone Ereignisbehandlung. Das heißt, der Signalhandler agiert aus dem Standardablauf der Programmsteuerung heraus; Tatsächlich wird das gesamte Programm in einem Kontext gespeichert, und es wird ein neuer Kontext erstellt, in dem der Signal-Handler nur ausgeführt werden kann. Sobald der Signal-Handler seine Aktionen abgeschlossen hat, wird der Kontext zurückgeschaltet und der normale Ausführungsablauf beginnt (d. h. der while(1)
).
Um Ihre Fragen zu beantworten,
Die Schlussfolgerung bestätigte Folgendes:Wenn bash einen externen Befehl im Vordergrund ausführt, verarbeitet es keine empfangenen Signale, bis der Vordergrundprozess beendet wird
Das Wichtigste, was hier zu beachten ist, ist das externe Befehlsteil. Im ersten Fall, wobei sleep
ist ein externer Prozess, aber im zweiten Fall read
ist ein eingebauter aus der Shell selbst. Die Ausbreitung des Signals zu diesen beiden unterscheidet sich also in beiden Fällen
type read
read is a shell builtin
type sleep
sleep is /usr/bin/sleep
1.Öffnen Sie ein Terminal namens termA und führen Sie die erstellte Datei callback.sh zum ersten Mal mit /bin/bash callback.sh aus, die Informationen werden sofort angezeigt.
Ja, dieses Verhalten wird erwartet. Denn zu diesem Zeitpunkt ist nur die Funktion definiert und der Trap-Handler an der Funktion myCallback
registriert und das Signal wird noch nicht im Skript empfangen. Wie die Ausführungssequenz verläuft, wird die Nachricht von read
wird zum ersten Mal ausgegeben.
2.Öffnen Sie ein neues Terminal namens termB und führen Sie pkill -USR1 -f callback.sh
aus Beim ersten Mal erscheint die Info sofort in termA
Ja, während die read
Befehl wartet auf String gefolgt von Enter Tastendruck, der den EOF
signalisiert erhält er ein Signal SIGUSR1
vom anderen Terminal wird der aktuelle Ausführungskontext gespeichert und die Steuerung auf den Signalhandler umgeschaltet, der den String mit dem aktuellen Datum ausgibt.
Sobald der Handler die Ausführung beendet hat, wird der Kontext mit while
fortgesetzt Schleife, in der der read
Der Befehl wartet immer noch auf eine Eingabezeichenfolge. Bis zum read
Befehl erfolgreich ist, würden alle nachfolgenden Signal-Traps nur die Zeichenfolge innerhalb des Signal-Handlers ausgeben.
Fahren Sie mit termB fort und führen Sie pkill -USR1 -f callback.sh
aus das zweite Mal.
Wie zuvor erklärt, der read
Befehl in Ihrer While-Schleife einmal nicht vollständig ist, nur wenn er erfolgreich einen String liest, würde die nächste Iteration der Schleife beginnen und eine neue Eingabeaufforderung ausgegeben werden.
Bildquelle:The Linux Programming Interface von Michael KerrisK
Die Schlussfolgerung bestätigte Folgendes:Wenn bash einen externen Befehl im Vordergrund ausführt, verarbeitet es keine empfangenen Signale, bis der Vordergrundprozess beendet wird.
bash
macht behandeln die Signale bei C
/ Implementierungsebene, führt aber nicht die mit trap
festgelegten Handler aus bis der Vordergrundprozess beendet ist. Das wird vom Standard verlangt:
When a signal for which a trap has been set is received while the shell is waiting for the completion of a utility executing a foreground command, the trap associated with that signal shall not be executed until after the foreground command has completed.
Beachten Sie, dass der Standard keinen Unterschied zwischen eingebauten und externen Befehlen macht; im Fall eines "Dienstprogramms" wie read
, der nicht in einem separaten Prozess ausgeführt wird, ist nicht ersichtlich, ob ein Signal in seinem "Kontext" oder in dem der Haupt-Shell auftritt und ob ein Handler mit trap
gesetzt ist sollte vor oder nach dem read
ausgeführt werden zurück.
Problem 1:callback.sh enthält eine unendliche While-Schleife, wie erklärt wird, dass sie keine empfangenen Signale verarbeitet, bis der Vordergrundprozess beendet wird. In diesem Fall wird der Vordergrundprozess nie beendet.
Das ist falsch. bash
führt den while
nicht aus Schleife in einem separaten Prozess. Da es keinen Vordergrundprozess bash
gibt wartet, kann es jeden Handler-Satz mit trap
ausführen sofort. Wenn read
ein externer Befehl wäre, das und nicht while
wäre der Vordergrundprozess.
Ausgabe 2:Kein please input something for foo: shown
in termA;
gehe zu termB, führe pkill -USR1 -f callback.sh
aus das dritte Mal.
Die folgende Info wird erneut in termA angezeigt:callback function called at Mon Nov 19 09:07:24 HKT 2018
Immer noch kein please input something for foo:
in termA gezeigt. Warum die Info please input something for foo:
Einfrieren?
Es friert nicht ein. Drücken Sie einfach Enter und es wird wieder angezeigt.
Es ist einfach so
a) bash
wird read
neu starten eingebaut an Ort und Stelle wenn es durch ein Signal unterbrochen wird - es kehrt nicht zurück und geht erneut durch while
Schleife
b) Es wird die mit -p
eingestellte Eingabeaufforderung nicht erneut anzeigen in diesem Fall.
Beachten Sie, dass bash
zeigt die Eingabeaufforderung nicht einmal erneut an, falls ein SIGINT
von der Tastatur behandelt wurde, auch wenn es die bisher vom Benutzer gelesene Zeichenfolge verwirft:
$ cat goo
trap 'echo INT' INT
echo $$
read -p 'enter something: ' var
echo "you entered '$var'"
$ bash goo
24035
enter something: foo<Ctrl-C>^CINT
<Ctrl-C>^CINT
<Ctrl-C>^CINT
bar<Enter>
you entered 'bar'
Das gibt you entered 'foobar'
aus wenn das Signal von einem anderen Fenster mit kill -INT <pid>
gesendet wurde statt mit Strg-C vom Endgerät.
Das ganze Zeug in diesem letzten Teil (wie read
unterbrochen wird usw.) ist sehr bash
Spezifisch. In anderen Shells wie ksh
oder dash
, und sogar in bash
bei Ausführung im POSIX-Modus (bash --posix
), wird jedes behandelte Signal tatsächlich den read
unterbrechen eingebaut. Im obigen Beispiel gibt die Shell you entered ''
aus und beenden nach dem ersten ^C, und wenn der read
aus einer Schleife aufgerufen wird, wird die Schleife neu gestartet.