Le canal est un mécanisme de communication à sens unique entre deux processus. Dans la plupart des implémentations UNIX, un canal apparaît comme une zone mémoire d'une certaine taille dans l'espace du noyau. Les processus qui communiquent via un canal anonyme doivent avoir un certain degré de parenté; généralement, un processus qui crée un tuyau sera alors appelé une fourchette, et le tuyau sera utilisé pour la communication parent-enfant. Dans tous les cas, les processus qui communiquent via des canaux anonymes ne peuvent pas être créés par différents utilisateurs du système.
L'appel système à la création est pipe. En python, nous pouvons utiliser deux fonctions pour créer un canal: os.pipe() et os.pipe2(flags)
import os (r, w) = os.pipe()
Le tuple (r, w)
contient après l'exécution de la fonction 2 descripteurs de fichiers:
r
, ouvert à la lecture; w
, ouvert pour l'écriture;
La sortie de w
est considérée comme une entrée pour r
..
Une autre option pour écrire est
import os p = os.pipe() # use p[0] and p[1]
La fonction os.pipe2 (flags)
reçoit en paramètre les options spécifiques au canal.
Mnémotechnique : STDIN_FILENO
est 0 (lecture),STDOUT_FILENO
est 1 (écrit).
Remarques:
PIPE_BUF
1) octets. os.read ()
/ os.write ()
. La plupart des applications qui utilisent des tuyaux ferment dans chacun des processus l'extrémité du tuyau inutilisé en communication unidirectionnelle. Si l'un des descripteurs est fermé, les règles s'appliquent:
0
, indiquant la fin du fichier. Le descripteur d'écriture peut être dupliqué afin que plusieurs processus puissent écrire dans le canal. Habituellement, dans le cas des canaux anonymes, il n'y a que deux processus, l'un qui écrit et l'autre qui lit, tandis que dans le cas des fichiers de canal nommé (FIFO), il peut y avoir plusieurs processus qui écrivent des données. SIGPIPE
. Si le signal est capturé et revient de la routine de traitement, la fonction système écriture
renvoie une erreur et la variable errno
a la valeur EPIPE
.
EOF
n'est pas envoyé à travers les tuyaux (la lecture des tuyaux ne se termine pas) à moins que toutes les extrémités ne soient fermées. TOUS les processus qui ont ouvert le descripteur de tuyau (dans le cas d'une fourche, assurez-vous de fermer les extrémités du tuyau dans le processus parent).
Il élimine le besoin de relier des processus connexes. Ainsi, chaque processus peut s'ouvrir pour lire ou écrire le fichier de canal nommé (FIFO), un type de fichier spécial, qui conserve les caractéristiques d'un tube. La communication se fait dans un sens ou dans les deux. Les fichiers FIFO peuvent être identifiés par la lettre p
dans le premier champ des droits d'accès ( ls -l
).
L'appel de bibliothèque pour la création de canaux FIFO est os.mkfifo:
import os os.mkfifo(path, mode);
Une fois le tube FIFO créé, il peut être appliqué à toutes les fonctions pour les opérations courantes de travail avec les fichiers: ouvrir
, fermer
, lire
, écrire
.
Le comportement d'un tube FIFO après ouverture est affecté par l'indicateur O_NONBLOCK
:
Lorsque vous fermez le dernier descripteur de fichier de la fin d'écriture pour une FIFO, une fin de fichier
- EOF
- est générée pour le processus de lecture FIFO.
Dans le monde réel, un processus peut connaître une multitude de situations imprévues, qui affectent son taux d'exécution normal. Si le processus ne peut pas les gérer, ils sont transmis au système d'exploitation. Comme le système d'exploitation ne peut pas savoir si le processus peut continuer à fonctionner normalement sans effets secondaires indésirables, il est obligé de mettre fin au processus de manière forcée. Une solution à ce problème est les signaux.
terminer
le processus.ignorer
un signal, le système d'exploitation n'enverra plus ce signal au processus.bloquer
un signal, le système d'exploitation n'enverra plus de signaux de ce type au processus en question, mais n'enregistrera que le premier signal de ce type, le reste étant perdu. Lorsque le procès décide qu'il veut à nouveau recevoir des signaux de ce type, s'il y a un signal en attente, il sera envoyé.
L'ensemble des types de signaux est fini; le système d'exploitation conserve, pour chaque processus, un tableau d'actions
choisi par lui, pour chaque type de signal. À tout moment, ces actions sont bien déterminées. Au début du processus, la table d'actions est initialisée avec les valeurs par défaut. Le mode de traitement du signal n'est pas décidé lors de la réception du signal par le processus, mais il est automatiquement choisi dans le tableau.
Les signaux sont synchrones / asynchrones avec le flux d'exécution de processus qui reçoit le signal si l'événement qui provoque l'envoi du signal est synchrone / asynchrone avec le flux d'exécution de processus.
Un signal reçu par un processus peut être généré:
système d'exploitation
- s'il signale des erreurs différentes;processus
- qui peut envoyer ses propres signaux (le signal passera également par le système d'exploitation).
Dans certains cas, il est nécessaire de savoir avec certitude qu'un signal envoyé a atteint sa destination et, implicitement, que le processus y répondra (en effectuant l'une des actions possibles). Le système d'exploitation offre une autre façon d'envoyer un signal, qui garantit
si le signal a atteint sa destination ou si cette action a échoué. Ceci est réalisé en créant une pile de signaux, d'une certaine capacité (elle doit être finie, afin de ne pas produire de situations de débordement). Lors de l'envoi d'un signal, le système d'exploitation vérifie si la pile est pleine. Dans ce cas, la demande échoue, sinon le signal est placé dans la pile et l'opération se termine avec succès. La manière classique d'envoyer des signaux lui est analogue (la pile a la dimension 1), sauf qu'aucune information n'est fournie sur la manière d'atteindre une destination.
La notion de signal est utilisée pour indiquer alternativement soit un type particulier de signal, soit des objets de ce type.
En général, les événements qui génèrent des signaux se répartissent en trois grandes catégories:
erreur
indique qu'un programme a effectué une opération non autorisée et ne peut pas continuer son exécution. Cependant, tous les types d'erreurs ne génèrent pas de signaux (en fait, la plupart n'en produisent pas). Par exemple, l'ouverture d'un fichier inexistant est une erreur, mais elle ne génère pas de signal; à la place, l'appel système ouvert renvoie -1, indiquant que l'appel s'est terminé par erreur. En général, les erreurs associées à certaines bibliothèques sont signalées en renvoyant une valeur spéciale. Les erreurs qui génèrent des signaux sont celles qui peuvent apparaître n'importe où dans le programme, pas seulement dans les appels de bibliothèque. Ils incluent la division zéro et l'accès à la mémoire invalide.événement externe
est généralement lié aux E / S et à d'autres processus. Exemples: l'apparition de nouvelles données d'entrée, l'expiration d'un temporisateur, l'achèvement de l'exécution d'un processus enfant.demande explicite
indique l'utilisation d'un appel système, tel que kill, pour générer un signal.Les signaux peuvent être générés de manière synchrone ou asynchrone:
synchrone
fait référence à une action spécifique du programme et est envoyé (s'il n'est pas bloqué) pendant cette action. La plupart des erreurs génèrent des signaux de manière synchrone. De plus, les signaux peuvent être générés de manière synchrone et par certaines requêtes explicites envoyées par un processus à lui-même. Sur certaines machines, certains types d'erreurs matérielles (généralement des exceptions à virgule flottante) ne sont pas signalés de manière complètement synchrone et certaines instructions peuvent arriver plus tard.Un type de signal donné est synchrone ou asynchrone. Par exemple, les signaux d'erreur sont généralement synchrones car les erreurs génèrent des signaux de manière synchrone. Cependant, tout type de signal peut être généré de manière synchrone ou asynchrone avec une demande explicite.
Lorsqu'un signal est généré, il passe dans un état en attente. Normalement, il reste dans cet état pendant très peu de temps et est ensuite envoyé au processus de destination. Cependant, si ce type de signal est actuellement verrouillé, il pourrait rester indéfiniment en état de veille jusqu'à ce que les signaux de ce type soient déverrouillés. Une fois ce type de signal déverrouillé, il sera envoyé immédiatement.
Lorsque le signal a été reçu, immédiatement ou tard, l'action spécifiée pour ce signal est exécutée. Pour certains signaux, tels que SIGKILL
et SIGSTOP
, l'action est fixe (le processus est terminé), mais, pour la plupart des signaux, le programme peut choisir de:
Le programme précise son choix à l'aide de la fonction signal.signal().Pendant que le gestionnaire est en cours d'exécution, ce type de signal est normalement verrouillé (le déverrouillage sera effectué par une demande explicite dans le gestionnaire qui gère le signal).
Si l'action spécifiée pour un type de signal consiste à l'ignorer, alors tout signal de ce type, qui est généré pour le processus en question, est ignoré. La même chose se produit si le signal est bloqué à ce moment-là. Un signal négligé dans ce mode ne sera jamais reçu, sauf si le programme spécifie par la suite une action différente pour ce type de signal, puis le déverrouille. Si un signal est reçu pour lequel aucun type d'action n'a été spécifié, l'action par défaut est exécutée. Chaque type de signal a sa propre action par défaut. Pour la plupart des signaux, l'action par défaut est achèvement du processus. Pour certains types de signaux, qui représentent des événements sans conséquences majeures, l'action implicite est de ne rien faire.
Lorsqu'un signal force l'achèvement d'un processus, le parent du processus peut déterminer la cause de la terminaison en examinant le code de terminaison signalé par les fonctions d'attente et d'attente. Les informations que vous pouvez obtenir incluent le fait que la fin du processus a été provoquée par un signal, ainsi que le type de signal. Si un programme que vous exécutez à partir de la ligne de commande se termine par un signal, le shell affiche généralement des messages d'erreur. Les signaux qui représentent normalement des erreurs de programme ont une propriété spéciale: lorsqu'un de ces signaux termine le processus, il écrit également un fichier core dump qui enregistre l'état du processus au moment de l'achèvement. Vous pouvez examiner le fichier avec un débogueur pour découvrir la cause de l'erreur. Si vous générez un signal, qui représente une erreur de programme, par une demande explicite, et qu'il termine le processus, le fichier est généré comme si le signal était généré par une erreur.
signal
, puis soit l'opération il échouera (avec errno
réglé sur EINTR
), ou l'opération redémarrera. Les systèmes System V se comportent comme dans le premier cas, les BSD comme dans le second. À partir de la glibc v2, le comportement est le même que sur BSD, tout dépend de la définition de la macro _BSD_SOURCE. Le comportement peut être contrôlé par le programmeur en utilisant sigaction
avec le drapeau SA_RESTART
.
Cette section présente les noms des différents types de signaux standard et décrit le type d'événements qu'ils indiquent.
$ kill -l 1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL 5) SIGTRAP 6) SIGABRT 7) SIGBUS 8) SIGFPE 9) SIGKILL 10) SIGUSR1 11) SIGSEGV 12) SIGUSR2 13) SIGPIPE 14) SIGALRM 15) SIGTERM 17) SIGCHLD 18) SIGCONT 19) SIGSTOP 20) SIGTSTP 21) SIGTTIN 22) SIGTTOU 23) SIGURG 24) SIGXCPU 25) SIGXFSZ 26) SIGVTALRM 27) SIGPROF 28) SIGWINCH 29) SIGIO 30) SIGPWR 31) SIGSYS 33) SIGRTMIN 34) SIGRTMIN+1 35) SIGRTMIN+2 36) SIGRTMIN+3 37) SIGRTMIN+4 38) SIGRTMIN+5 39) SIGRTMIN+6 40) SIGRTMIN+7 41) SIGRTMIN+8 42) SIGRTMIN+9 43) SIGRTMIN+10 44) SIGRTMIN+11 45) SIGRTMIN+12 46) SIGRTMIN+13 47) SIGRTMIN+14 48) SIGRTMIN+15 49) SIGRTMAX-15 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-12 53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9 56) SIGRTMAX-8 57) SIGRTMAX-7 58) SIGRTMAX-6 59) SIGRTMAX-5 60) SIGRTMAX-4 61) SIGRTMAX-3 62) SIGRTMAX-2 63) SIGRTMAX-1 64) SIGRTMAX
Les noms de signaux sont définis dans l'en-tête signal.h
sous Unix. En général, les signaux ont des rôles prédéfinis, mais ils peuvent être remplacés par le programmeur.
Les plus connus sont les signaux suivants:
SIGINT
- transmis lorsque la combinaison est enfoncée CTRL+C
;SIGQUIT
- transmis lorsque la combinaison est enfoncée CTRL+\
;SIGSEGV
- transmis lors de l'accès à un emplacement de mémoire invalide, etc.SIGKILL
- ne peut pas être ignoré ou remplacé. La transmission de ce signal a pour effet de mettre fin au processus, quel que soit le contexte.Cel mai bun mod de a afișa un mesaj de descriere a unui semnal este utilizarea funcțiilor strsignal. Aceasta funcție foloseste un număr de semnal pentru a specifica tipul de semnal care trebuie descris. Mai jos este prezentat un exemplu de folosire a ei:
import os import signal sig = signal.strsignal(signal.SIGKILL) print (''signal {} is {}'').format(signal.SIGKILL, sig)
Pour la compilation et l'exécution, la séquence est la suivante:
so@spook$ python3 msg_signal.py signal 9 is Killed
Afin d'effectuer des opérations de blocage / déverrouillage de signal, nous devons connaître, à chaque étape du flux d'exécution, l'état de chaque signal. Le système d'exploitation a également besoin de la même chose pour pouvoir prendre une décision sur un signal à envoyer à un processus (il a besoin de ce type d'informations pour chaque processus séparément). À cet effet, un masque de signal est utilisé pour chaque processus.
signal.pthread_sigmask(how, maks)
Le masque de signal est un ensemble de numéros de signal. Pour afficher le masque actuel, nous pouvons utiliser la fonction comme suit:signal.pthread_sigmask(signal.SIG_BLOCK, [])
.
La fonction peut être utilisée pour transmettre un signal os.kill:
import os os.kill(pid, signo)
La fonction envoie le signal signo
au processus avec l'identifiant pid
.
Les conditions requises pour qu'un processus soit autorisé à envoyer un signal à un autre processus sont les mêmes que dans le cas de kill
. Si le signal spécifié est bloqué à ce moment, la fonction se fermera immédiatement et si l'indicateur SA_SIGINFO
est défini et les ressources sont disponibles, le signal sera mis en file d'attente dans l'état en attente (un processus peut avoir une file d'attente maximale SIGQUEUE_MAX
des signaux).
<spoiler Approches pour attendre un signal > Si les signaux sont utilisés pour la communication et / ou la synchronisation, il est souvent nécessaire d'attendre qu'un certain type de signal arrive au processus en question. Un moyen facile de le faire est une boucle, dont la condition de sortie serait le réglage correct d'une variable. Par exemple:
while (!signal_has_arrived)
Le principal inconvénient de l'approche ci-dessus (type occupé-en attente
) est le temps par processeur que le processus considéré perd inutilement. Une alternative serait d'utiliser la fonction time.sleep:
while (!signal_has_arrived): time.sleep(1)
Pour résoudre le laboratoire, veuillez cloner repository. Si vous l'avez déjà, veuillez lancer git pull.
Entrez dans le répertoire 1-pipe
, vous avez deux programmes: pipe.py
et reader.py
.
Dans le fichier pipe.py
, vous créez un tuyau puis créez une fourche. Dans le processus parent, fermez l'extrémité de lecture du tuyau
et écrivez les données du buffer dans le tube.
Dans le processus enfant, fermez l'extrémité d'écriture du canal, lisez les données reçues du canal dans le tampon et affichez-les à l'écran.
Suivez les lignes TODO 1.
Dans le fichier reader.py
, lisez au clavier un texte stocké dans la variable buffer et affichez la variable.
Dans le fichier pipe.py
, modifiez le processus enfant afin qu'après la fin de l'écriture dans le tube,
rediriger stdin vers la fin de lecture du tube et exécuter (en utilisant l'une des fonctions exec )
le programme reader.py
.
Suivez le TODO 2 mois.
Entrez dans le répertoire 2-hitme /
et analysez le contenu du fichier hitme.py
. Exécutez le programme.
Utilisez la commande kill -l
pour répertorier tous les signaux disponibles. Quelle est la valeur du signal SIGKILL
?
Dans une autre console, envoyez au programme des signaux hitme
avec des valeurs entre 20 et 25 comme suit:
Le programme affichera son PID, ce sera $ PID. kill -20 $PID kill -21 $PID kill -22 $PID kill -23 $PID kill -24 $PID kill -25 $PID
Entrez dans le répertoire 3-signaux
et parcourez le contenu du fichier signaux.py
. Le programme compte combien de fois le gestionnaire de signaux est appelé en cas d'envoi des signaux SIGINT
et SIGRTMIN
(34)
Démarrez le programme signaux.py
:
python3 signals.py
Dans le cas de signaux normaux
, dans une autre console, exécutez le script send_normal.sh
:
./send_normal.sh
Pour les signaux en temps réel, exécutez un autre script dans send_rt.sh dans une autre console:
./send_rt.sh
Pour fermer l'exécutable signaux
est envoyé le signal SIGQUIT
. D'où vient la différence?
Lisez la page de manuel signal man 7
Signaux en temps réel
et passez en revue la section Types de signaux standard.
SIGRTMIN
et SIGRTMAX
sont des signaux en temps réel, il est donc garanti qu'ils atteindront leur destination. Vue link.
Entrez dans le répertoire 4-askexit
et suivez le code source. Le programme attend occupé (pendant), affichant des numéros consécutifs sur la console.
Vous devez terminer le programme pour intercepter les signaux générés par CTRL + \
, CTRL + C
et SIGUSR1
(utilisez la commande kill). Le gestionnaire associé à chacun des signaux sera le ask_handler. Pour chaque signal reçu, il sera demandé à l'utilisateur s'il souhaite terminer l'exécution ou non.
Testez la fonctionnalité du programme.
Entrez dans le répertoire 5-noint
et créez un programme, appelé noint.py
. Le programme reçoit, comme premier paramètre, le nom d'une commande à exécuter. Les autres paramètres représentent les arguments avec lesquels la commande respective doit être invoquée; la liste des arguments peut être nulle.
Le programme exécuté par noint.py
ne doit pas être averti de la réception du signal SIGINT (CTRL + C). Vous devrez ignorer le signal SIGINT
fourni par le shell de processus.
Pour tester, exécutez
python3 noint.py sleep 120 &
Entrez dans le répertoire 6-nohup
et créez un programme, appelé nohup.py
, qui simule la commande nohup. Le programme reçoit, comme premier paramètre, le nom d'une commande à exécuter. Les autres paramètres représentent les arguments avec lesquels la commande respective doit être invoquée; la liste des arguments peut être nulle.
Le programme exécuté par nohup.py
ne doit pas être averti de la fermeture du terminal auquel il était connecté. Vous devrez ignorer le signal SIGHUP
, délivré par le shell de processus, à la fin de la session en cours.
Si le fichier de sortie standard était lié à un terminal, il doit être redirigé vers un fichier nohup.out
.
Pour tester, exécutez
python3 nohup.py sleep 120 &
Après l'exécution, fermez la session shell en cours: soit en envoyant un signal SIGHUP, soit en utilisant l'icône X
sur le côté droit de la fenêtre.
Depuis une autre console, exécutez respectivement
ps -ef | grep sleep
Qui est le nouveau parent du procès?
L'utilisation de la commande exit
ou la combinaison de touches Ctrl-d
n'enverra pas de signal SIGHUP au processus de sommeil; vous pouvez tester en utilisant
sleep 120 &
, fermer le shell actuel en utilisant l'une des 2 méthodes, puis vérifier que le processus est toujours en cours d'exécution.
Entrez dans le répertoire '7-zombie' et parcourez le contenu des fichiers zombie.py
et nozombie.py
. Chaque programme créera un nouveau processus enfant, qui sera uniquement appelé exit
.
Implémentez zombie.py
sans attendre la fin de l'enfant créé. Le processus parent attend pendant TIMEOUT secondes et se termine (suivez TODO ).
À partir d'une autre exécution de la console:
ps -eF | grep python3
Notez que le processus enfant, bien qu'il ne soit plus en cours d'exécution, apparaît dans la liste des processus sous la forme <defunct>
et possède un pid (unique dans le système à ce moment-là). Notez également qu'après la mort du processus parent, le processus zombie disparaît également.
Implémentez nozombie
sans utiliser les fonctions d'attente du type wait
afin que le processus enfant ne passe pas à l'état zombie. nozombie
attendra TIMEOUT
secondes et quittera. Utilisez le signal SIGCHLD
(informations trouvées dans signal() et waitpid()).Voir aussi Traitement du signal et Crearea unui proces in linux.