Étant donné un processus shell (par exemple sh
) et son processus enfant (par exemple cat
), comment puis-je simuler le comportement de Ctrl+ en Cutilisant l'ID de processus du shell?
C'est ce que j'ai essayé:
Courir sh
et ensuite cat
:
[user@host ~]$ sh
sh-4.3$ cat
test
test
Envoi SIGINT
à cat
partir d'un autre terminal:
[user@host ~]$ kill -SIGINT $PID_OF_CAT
cat
reçu le signal et terminé (comme prévu).
L'envoi du signal au processus parent ne semble pas fonctionner. Pourquoi le signal n'est-il pas propagé cat
lorsqu'il est envoyé à son processus parent sh
?
Cela ne fonctionne pas:
[user@host ~]$ kill -SIGINT $PID_OF_SH
Réponses:
Comment CTRL+ Cfonctionne
La première chose à faire est de comprendre comment CTRL+ Cfonctionne.
Lorsque vous appuyez sur CTRL+ C, votre émulateur de terminal envoie un caractère ETX (fin du texte / 0x03).
Le téléscripteur est configuré de sorte que, lorsqu'il reçoit ce caractère, il envoie un signal SIGINT au groupe de processus de premier plan du terminal. Cette configuration peut être visualisée en faisant
stty
et en regardantintr = ^C;
. La spécification POSIX indique que, lorsque l’INTR est reçu, il doit envoyer un SIGINT au groupe de processus de premier plan de ce terminal.Quel est le groupe de processus de premier plan?
La question est donc de savoir comment déterminer le groupe de processus de premier plan. Le groupe de processus de premier plan est simplement le groupe de processus qui recevra les signaux générés par le clavier (SIGTSTOP, SIGINT, etc.).
Le moyen le plus simple de déterminer l'ID du groupe de processus consiste à utiliser
ps
:La deuxième colonne sera l'ID du groupe de processus.
Comment envoyer un signal au groupe de processus?
Maintenant que nous connaissons l'identifiant du groupe de processus, nous devons simuler le comportement POSIX consistant à envoyer un signal à l'ensemble du groupe.
Cela peut être fait
kill
en mettant un-
devant l'ID du groupe.Par exemple, si votre ID de groupe de processus est 1234, vous utiliseriez:
Simulez CTRL+ en Cutilisant le numéro de terminal.
Ce qui précède explique donc comment simuler CTRL+ en Ctant que processus manuel. Mais que se passe-t-il si vous connaissez le numéro de téléscripteur et souhaitez simuler CTRL+ Cpour ce terminal?
Cela devient très facile.
Supposons que
$tty
c'est le terminal que vous souhaitez cibler (vous pouvez l'obtenir en le lançanttty | sed 's#^/dev/##'
dans le terminal).Cela enverra un SIGINT à quelque groupe de processus d’avant-plan
$tty
.la source
kill
commande pourrait échouer.sends a SIGINT to the foreground process group of the terminal.
fork
. Exemple de runnableComme dit vinc17, il n'y a aucune raison pour que cela se produise. Lorsque vous tapez une séquence de touches générant un signal (par exemple, Ctrl+ C), le signal est envoyé à tous les processus liés au terminal (associés). Un tel mécanisme n'existe pas pour les signaux générés par
kill
.Cependant, une commande comme
enverra le signal à tous les processus du groupe de processus 12345; voir kill (1) et kill (2) . Les enfants d'un shell font généralement partie du groupe de processus du shell (du moins s'ils ne sont pas asynchrones). L'envoi du signal au négatif du PID du shell peut donc faire ce que vous voulez.
Oops
Comme le fait remarquer vinc17, cela ne fonctionne pas pour les shells interactifs. Voici une alternative qui pourrait fonctionner:
ps -pPID_of_shell
obtient des informations de processus sur le shell.o tpgid=
indiqueps
de ne produire que l'ID du groupe de processus terminal, sans en-tête. S'il est inférieur à 10 000,ps
l'affichera avec le ou les espaces de début; la$(echo …)
est une tour rapide à dépouiller avant (et arrière) espaces.J'ai réussi à ce que cela fonctionne lors de tests superficiels sur une machine Debian.
la source
La question contient sa propre réponse. L' envoi du
SIGINT
aucat
processus aveckill
une simulation parfaite de ce qui se passe lorsque vous appuyez sur^C
.Pour être plus précis, le caractère d'interruption (
^C
par défaut) envoieSIGINT
à chaque processus du groupe de processus de premier plan du terminal. Si au lieu decat
vous exécutez une commande plus complexe impliquant plusieurs processus, vous devez supprimer le groupe de processus pour obtenir le même effet^C
.Lorsque vous exécutez une commande externe sans
&
opérateur d'arrière - plan, le shell crée un nouveau groupe de processus pour la commande et informe le terminal que ce groupe de processus est maintenant au premier plan. Le shell est toujours dans son propre groupe de processus, qui n'est plus au premier plan. Ensuite, le shell attend que la commande se ferme.C’est là que vous semblez être devenu la victime d’une idée fausse courante: l’idée que le shell fait quelque chose pour faciliter l’interaction entre ses processus enfants et le terminal. Ce n'est tout simplement pas vrai. Une fois que le travail d’installation est terminé (création de processus, paramétrage du mode terminal, création de canaux et redirection d’autres descripteurs de fichier, et exécution du programme cible), le shell attend . Ce que vous saisissez n’entre
cat
pas dans le shell, qu’il s’agisse d’une entrée normale ou d’un caractère spécial générateur de signaux, par exemple^C
. Lecat
processus a un accès direct au terminal par le biais de ses propres descripteurs de fichier et le terminal peut envoyer des signaux directement aucat
processus car il s'agit du groupe de processus de premier plan.Une fois le
cat
processus terminé, le shell en sera informé, car il s'agit du parent ducat
processus. Ensuite, le shell devient actif et se place à nouveau au premier plan.Voici un exercice pour augmenter votre compréhension.
À l'invite du shell d'un nouveau terminal, exécutez la commande suivante:
Le
exec
mot clé provoque l'exécution du shellcat
sans créer de processus enfant. La coquille est remplacée parcat
. Le PID qui appartenait auparavant à la coquille est maintenant le PID decat
. Vérifiez cecips
dans un autre terminal. Tapez quelques lignes aléatoires et voyez-cat
les vous les répéter, ce qui prouve qu'il se comporte toujours normalement bien qu'il ne s'agisse pas d'un processus shell en tant que parent. Que se passera-t-il quand vous appuierez^C
maintenant?Répondre:
la source
exec cat
appuyé^C
ne pas atterrir^C
dans chat. Pourquoi voudrait-il terminer lecat
qui a maintenant remplacé la coquille? Depuis que le shell a été remplacé, le shell est la chose qui implémente la logique d’envoi de SIGINT à ses enfants lors de la réception^C
.Il n'y a aucune raison de propager le
SIGINT
à l'enfant. De plus, lasystem()
spécification POSIX indique: "La fonction system () doit ignorer les signaux SIGINT et SIGQUIT et doit bloquer le signal SIGCHLD en attendant la fin de la commande."Si le shell propage la réception
SIGINT
, par exemple après une vraie touche Ctrl-C, cela signifie que le processus enfant recevra leSIGINT
signal deux fois, ce qui peut avoir un comportement indésirable.la source
system()
. Mais vous avez raison, si le signal est capté (bien évidemment), il n’ya aucune raison de le propager vers le bas.setpgid
Exemple minimal de groupe de processus POSIX CIl serait peut-être plus facile de comprendre avec un exemple minimal exécutable de l’API sous-jacente.
Ceci illustre comment le signal est envoyé à l'enfant, si l'enfant n'a pas changé de groupe de processus avec
setpgid
.principal c
GitHub en amont .
Compiler avec:
Courir sans
setpgid
Sans aucun argument CLI,
setpgid
n'est pas fait:Résultat possible:
et le programme se bloque.
Comme nous pouvons le constater, le pgid des deux processus est le même, car il est hérité d'un bout à l'autre
fork
.Puis chaque fois que vous frappez:
Il affiche à nouveau:
Cela montre comment:
kill(-pgid, SIGINT)
Quittez le programme en envoyant un signal différent aux deux processus, par exemple SIGQUIT avec
Ctrl + \
.Courir avec
setpgid
Si vous utilisez un argument, par exemple:
alors l'enfant change de pgid, et maintenant, un seul sigint est imprimé à chaque fois à partir du parent uniquement:
Et maintenant, chaque fois que vous frappez:
seul le parent reçoit également le signal:
Vous pouvez toujours tuer le parent comme avant avec un SIGQUIT:
Cependant, l'enfant a maintenant un autre PGID et ne reçoit pas ce signal! Cela peut être vu de:
Vous devrez le tuer explicitement avec:
Cela explique pourquoi les groupes de signaux existent: sinon, nous aurions un tas de processus à nettoyer manuellement tout le temps.
Testé sur Ubuntu 18.04.
la source