termA ne se fige pas. Il affiche simplement le rappel dans la zone de saisie. Appuyez simplement sur le Enter
touche pour continuer la saisie.
Avant d'expliquer votre problème, un peu de contexte sur la façon dont read
la commande fonctionne. Il lit les données d'entrée de stdin
jusqu'au EOF
est rencontré. Il est prudent de dire l'appel à read
La commande n'est pas bloquante lorsqu'il s'agit de lire des fichiers sur disque. Mais quand stdin
est connecté au terminal, la commande se bloquera jusqu'à ce que l'utilisateur tape quelque chose.
Comment fonctionnent les gestionnaires de signaux ?
Une explication simple sur le fonctionnement du traitement du signal. Voir l'extrait ci-dessous dans C
qui agit juste sur SIGINT
( alias 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);
}
Il enregistrera le gestionnaire de signal puis entrera dans la boucle infinie. Lorsque nous atteignons Ctrl-C
, nous pouvons tous convenir que le gestionnaire de signal signal_handler()
doit s'exécuter et "Hello World!"
imprime à l'écran, mais le programme était dans une boucle infinie. Pour imprimer "Hello World!"
il doit avoir été le cas qu'il a rompu la boucle pour exécuter le gestionnaire de signal, n'est-ce pas ? Il devrait donc sortir de la boucle ainsi que du programme. Voyons :
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!
Comme l'indique la sortie, chaque fois que nous avons émis Ctrl-C
, "Hello World!"
s'imprime, mais le programme revient à la boucle infinie. Ce n'est qu'après avoir émis un SIGQUIT
signaler avec Ctrl-\
le programme a-t-il réellement quitté. En fonction de votre ulimit
paramètres, il viderait un noyau ou imprimerait le numéro de signal reçu.
Bien que l'interprétation selon laquelle la boucle se terminerait est raisonnable, elle ne prend pas en compte la principale raison de la gestion du signal, c'est-à-dire la gestion des événements asynchrones. Cela signifie que le gestionnaire de signal agit en dehors du flux standard du contrôle du programme ; en fait, l'ensemble du programme est enregistré dans un contexte, et un nouveau contexte est créé juste pour que le gestionnaire de signal s'exécute. Une fois que le gestionnaire de signal a terminé ses actions, le contexte est rétabli et le flux d'exécution normal commence (c'est-à-dire le while(1)
).
Pour répondre à vos questions,
La conclusion a vérifié que lorsque bash exécute une commande externe au premier plan, il ne gère aucun signal reçu jusqu'à ce que le processus de premier plan se termine
L'élément clé à noter ici est le externe partie commande. Dans le premier cas, où sleep
est un processus externe mais dans le second cas, read
est un intégré du shell lui-même. Donc la propagation du signal vers ces deux diffère dans ces deux cas
type read
read is a shell builtin
type sleep
sleep is /usr/bin/sleep
1.Ouvrez un terminal nommé termA et exécutez le fichier créé callback.sh avec /bin/bash callback.sh pour la première fois, les informations s'affichent instantanément.
Oui, ce comportement est attendu. Parce qu'à ce moment, seule la fonction est définie et le gestionnaire d'interruptions est enregistré dans la fonction myCallback
et le signal n'est pas encore reçu dans le script. Au fur et à mesure de la séquence d'exécution, le message du read
l'invite est lancée pour la première fois.
2.Ouvrez un nouveau terminal, nommé termB et exécutez
pkill -USR1 -f callback.sh
la première fois, les informations s'affichent instantanément dans termA
Oui, tandis que le read
la commande attend une chaîne suivie de Entrée appui sur la touche qui signale le EOF
, il reçoit un signal SIGUSR1
à partir de l'autre terminal, le contexte d'exécution en cours est enregistré et le contrôle est commuté sur le gestionnaire de signal qui imprime la chaîne avec la date actuelle.
Dès que le gestionnaire a fini de s'exécuter, le contexte reprend au while
boucle dans laquelle le read
La commande attend toujours une chaîne d'entrée. Jusqu'au read
commande est réussie, tous les pièges de signal suivants imprimeront simplement la chaîne à l'intérieur du gestionnaire de signal.
Continuez dans termB, exécutez
pkill -USR1 -f callback.sh
la deuxième fois.
Comme expliqué précédemment, le read
la commande n'est pas terminée pour une fois dans votre boucle while, seulement si elle réussit à lire une chaîne, la prochaine itération de la boucle commencerait et un nouveau message d'invite serait lancé.
Source de l'image :L'interface de programmation Linux par Michael KerrisK
La conclusion a vérifié que lorsque bash exécute une commande externe au premier plan, il ne gère aucun signal reçu jusqu'à ce que le processus de premier plan se termine.
bash
fait gérer les signaux à C
/ niveau d'implémentation, mais n'exécutera pas les gestionnaires définis avec trap
jusqu'à ce que le processus de premier plan soit terminé. C'est requis par la norme :
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.
Notez que le standard ne fait aucune différence entre les commandes intégrées et les commandes externes ; dans le cas d'un "utilitaire" comme read
, qui n'est pas exécuté dans un processus séparé, il n'est pas évident de savoir si un signal se produit dans son "contexte" ou dans celui du shell principal, et si un gestionnaire défini avec trap
doit être exécuté avant ou après le read
renvoie.
problème 1 :callback.sh contient une boucle while infinie, comment expliquer qu'il ne gère aucun signal reçu jusqu'à ce que le processus de premier plan se termine., dans ce cas, le processus de premier plan ne se termine jamais.
C'est faux. bash
n'exécute pas le while
boucle dans un processus séparé. Puisqu'il n'y a pas de processus de premier plan bash
est en attente, il peut exécuter n'importe quel gestionnaire défini avec trap
immédiatement. Si read
était une commande externe, ça et non while
serait le processus de premier plan.
issue2 :Non
please input something for foo: shown
dans termeA ;allez à termB, exécutez
pkill -USR1 -f callback.sh
la troisième fois.Les informations suivantes s'affichent à nouveau dans termA :
callback function called at Mon Nov 19 09:07:24 HKT 2018
Toujours pas de
please input something for foo:
affiché dans termA.Pourquoi l'informationplease input something for foo:
geler?
Il ne gèle pas. Appuyez simplement sur Entrée et il s'affichera à nouveau.
C'est simplement que
a) bash
va redémarrer le read
intégré en place lorsqu'il est interrompu par un signal - il ne reviendra pas et repassera par le while
boucle
b) il ne réaffichera pas l'invite définie avec -p
dans ce cas.
Notez que bash
n'affichera même plus l'invite dans le cas où un SIGINT
du clavier a été géré, même s'il supprime la chaîne lue jusqu'à présent par l'utilisateur :
$ 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'
Cela imprimera you entered 'foobar'
si le signal a été envoyé depuis une autre fenêtre avec kill -INT <pid>
au lieu d'utiliser Ctrl-C depuis la borne.
Tout le contenu de cette dernière partie (comment read
est interrompu, etc) est très bash
spécifique. Dans d'autres shells comme ksh
ou dash
, et même en bash
lorsqu'il est exécuté en mode POSIX (bash --posix
), tout signal traité interrompra en fait le read
intégré. Dans l'exemple ci-dessus, le shell imprimera you entered ''
et sortir après le premier ^C, et si le read
est appelée depuis une boucle, la boucle sera redémarrée.