This shows you the differences between two versions of the page.
sde:laboratoare:06_python [2020/03/25 13:35] alexandru.radovici |
— (current) | ||
---|---|---|---|
Line 1: | Line 1: | ||
- | ====== TP 6 - IPC - Communication inter-processus ====== | ||
- | ===== Matériaux auxiliaires ===== | ||
- | |||
- | * [[http://elf.cs.pub.ro/so/res/laboratoare/lab04-refcard.pdf| lab05-refcard.pdf]] | ||
- | |||
- | ===== Liens vers des sections utiles ===== | ||
- | * [[#Pipe anonyme sous Linux | Pipe anonyme sous Linux]] | ||
- | * [[#Pipes nommés sous Linux | Pipes nommés sous Linux]] | ||
- | * [[#Génération de signaux | Génération de signaux]] | ||
- | * [[#Emission et réception de signaux | Emission et réception de signaux]] | ||
- | * [[#Types de signaux standard | Types de signaux standard]] | ||
- | * [[#Messages pour décrire les signaux | Messages pour décrire les signaux]] | ||
- | * [[#Masques de signaux. Blocage du signal | Masques de signaux. Blocage du signal]] | ||
- | * [[#Traitement du signal | Traitement du signal]] | ||
- | * [[#Processus de reporting | Processus de reporting]] | ||
- | * [[#En attente d'un signal | En attente d'un signal]] | ||
- | * [[#Minuteurs sous Linux | Minuteurs sous Linux]] | ||
- | |||
- | ===== Pipes sous Linux ===== | ||
- | |||
- | ==== Pipe anonyme sous Linux ==== | ||
- | |||
- | 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 [[http://linux.die.net/man/2/pipe|pipe]]. En python, nous pouvons utiliser deux fonctions pour créer un canal: [[https://docs.python.org/3/library/os.html#os.pipe|os.pipe()]] et [[https://docs.python.org/3/library/os.html#os.pipe2|os.pipe2(flags)]] | ||
- | |||
- | {{ so:laboratoare:pipe.png?370| Exemple d'utilisation - le processus parent transmet des données au processus enfant via des canaux }} | ||
- | <code python> | ||
- | import os | ||
- | (r, w) = os.pipe() | ||
- | </code> | ||
- | |||
- | |||
- | 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 | ||
- | <code python> | ||
- | import os | ||
- | p = os.pipe() | ||
- | # use p[0] and p[1] | ||
- | </code> | ||
- | |||
- | 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**: | ||
- | * la lecture / écriture depuis / vers les canaux est atomique si vous ne lisez / écrivez pas plus de '' PIPE_BUF '' ((limite globale fixée par défaut sur Linux à 4096 octets)) octets. | ||
- | * la lecture / écriture depuis / vers les pipes se fait à l'aide des fonctions '' 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: | ||
- | * une lecture à partir d'un canal pour lequel le ** descripteur d'écriture ** a été fermé, après que toutes les données ont été lues, renverra '' 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. | ||
- | * une écriture dans un tube pour lequel le descripteur ** read ** a été fermé provoque la génération du signal '' 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 ''. | ||
- | <note important> L'erreur ** la plus courante **, lorsque vous travaillez avec des tuyaux, vient de la négligence du fait que ''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).</note> | ||
- | |||
- | Autres fonctions utiles: [[https://docs.python.org/3/library/os.html#os.popen|os.popen]], [[https://docs.python.org/3/library/os.html#os.pclose|os.pclose]]. | ||
- | |||
- | ==== Pipes nommés sous Linux ==== | ||
- | |||
- | 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 [[https://docs.python.org/3/library/os.html#os.mkfifo|os.mkfifo]]: | ||
- | |||
- | <code python> | ||
- | import os | ||
- | os.mkfifo(path, mode); | ||
- | </code> | ||
- | |||
- | 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 '': | ||
- | <spoiler Détails sur le drapeau O_NONBLOCK> | ||
- | * si '' O_NONBLOCK '' n'est pas spécifié (cas normal), alors une lecture ouverte sera bloquée jusqu'à ce qu'un autre processus ouvre le même FIFO pour l'écriture. De même, si l'ouverture est destinée à l'écriture, un blocage peut se produire jusqu'à ce qu'un autre processus s'ouvre pour la lecture. | ||
- | * si '' O_NONBLOCK '' est spécifié, alors l'ouverture de lecture revient immédiatement, mais une ouverture d'écriture peut retourner une erreur avec '' errno '' ayant la valeur '' ENXIO '' s'il n'y a aucun autre processus ouvrant le même FIFO pour lecture. | ||
- | </spoiler> | ||
- | |||
- | |||
- | 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. | ||
- | |||
- | ===== Signaux sous Linux ===== | ||
- | 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. | ||
- | <note> Un signal est une interruption logicielle dans le flux d'exécution de processus normal.</note> | ||
- | Les signaux sont un concept spécifique aux systèmes d'exploitation UNIX. Le système d'exploitation les utilise pour signaler au processus l'émergence de situations exceptionnelles donnant au processus la possibilité de réagir. Chaque signal est associé à une classe d'événements qui peuvent se produire et répondre à certains critères. | ||
- | Les processus peuvent traiter, bloquer, ignorer ou permettre au système d'exploitation d'effectuer l'action par défaut lors de la réception d'un signal: | ||
- | * Habituellement, l'action par défaut consiste à ''terminer'' le processus. | ||
- | |||
- | * Si un processus veut ''ignorer'' un signal, le système d'exploitation n'enverra plus ce signal au processus. | ||
- | * Si un processus spécifie qu'il veut ''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 événement est synchrone avec le flux d'exécution du processus s'il se produit à chaque exécution du programme, au même point du flux d'exécution. Des exemples à cet égard tentent d'accéder à un emplacement de mémoire non valide ou non valide, à la remise à zéro, etc. | ||
- | * Un événement qui n'est pas synchrone est appelé asynchrone. Exemples d'événements asynchrones: un signal envoyé par un autre processus (le signal pour terminer un processus enfant), ou une demande de terminaison externe (l'utilisateur souhaite réinitialiser l'ordinateur). | ||
- | Un signal reçu par un processus peut être généré: | ||
- | * soit directement à partir du ''système d'exploitation'' - s'il signale des erreurs différentes; | ||
- | * soit par un ''processus'' - qui peut envoyer ses propres signaux (le signal passera également par le système d'exploitation). | ||
- | <note important> Si deux signaux sont trop proches dans le temps, ils peuvent être confondus avec un. Ainsi, normalement, il n'y a pas de mécanisme pour garantir à celui qui envoie le signal qu'il a atteint sa destination.</note> | ||
- | |||
- | 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. | ||
- | ===== Génération de signaux ===== | ||
- | En général, les événements qui génèrent des signaux se répartissent en trois grandes catégories: | ||
- | * Une ''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. | ||
- | * Un ''é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. | ||
- | * Une ''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: | ||
- | * Un signal ''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. | ||
- | * Les signaux asynchrones sont générés par des événements incontrôlables par le processus de réception. Ces signaux atteignent des moments imprévisibles. Les événements externes génèrent des signaux de manière asynchrone, tout comme les demandes explicites envoyées par d'autres processus. | ||
- | 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. | ||
- | ===== Emission et réception de signaux ===== | ||
- | 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: | ||
- | * **ignore** le signal | ||
- | * spécifier une fonction de type **handler** | ||
- | * accepte ** l'action par défaut ** pour ce type de signal. | ||
- | Le programme précise son choix à l'aide de la fonction [[https://docs.python.org/3/library/signal.html#signal.signal|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). | ||
- | <spoiler Exemple d'utilisation du ''signal''> | ||
- | Dans le code ci-dessous, nous visons à capturer les signaux '' SIGINT '' et '' SIGUSR1 '' et à prendre une action si nous les recevons. '' SIGINT '' est reçu à la fois en utilisant la commande '' kill -SIGINT <program> '' et en envoyant la combinaison de touches '' CTRL + c '' au programme. | ||
- | <code python> | ||
- | import os | ||
- | import signal | ||
- | |||
- | |||
- | pid_t child1, child2; | ||
- | int child1_pid; | ||
- | |||
- | |||
- | def signal_handler(signum, frame): | ||
- | if signum == signal.SIGINT: | ||
- | print(''CTRL+C received in {} Exiting'').format(os.getpid()) | ||
- | exit(0); | ||
- | elif signum == signal.SIGUSR1: | ||
- | print(''SIGUSR1 received. Continuing execution'') | ||
- | |||
- | |||
- | print(''Process {} started'').format(os.getpid()) | ||
- | |||
- | /* Les signaux tels que SIGKILL ou SIGSTOP ne peuvent pas être capturés */ | ||
- | try: | ||
- | signal.signal(SIGKILL, signal_handler) == SIG_ERR: | ||
- | printf(''\nYou shall not catch SIGKILL\n''); | ||
- | |||
- | if(signal(SIGINT, signal_handler) == SIG_ERR) { | ||
- | printf(''Unable to catch SIGINT''); | ||
- | exit(EXIT_FAILURE); | ||
- | } | ||
- | |||
- | if(signal(SIGUSR1, signal_handler) == SIG_ERR) { | ||
- | printf(''Unable to catch SIGUSR1''); | ||
- | exit(EXIT_FAILURE); | ||
- | } | ||
- | |||
- | |||
- | printf(''Press CTRL+C to stop us\n''); | ||
- | |||
- | while(1) { | ||
- | sleep(1); | ||
- | } | ||
- | |||
- | return 0; | ||
- | } | ||
- | </code> | ||
- | <note tip> Notez que le signal '' SIGKILL '' ne peut pas être traité (''kill -9 <program>'' ou ''kill -SIGKILL <program>''). | ||
- | </note> | ||
- | </spoiler> | ||
- | 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. | ||
- | <note important> Si un signal est envoyé au processus, alors qu'il exécute un appel système ** bloqueur **, le processus suspendra l'appel, exécutera le gestionnaire de traitement du signal défini à l'aide de '' 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 ''.</note> | ||
- | ===== Types de signaux standard ===== | ||
- | Cette section présente les noms des différents types de signaux standard et décrit le type d'événements qu'ils indiquent. | ||
- | <note> Chaque nom de signal est une ** définition de macro ** qui représente, en fait, un entier positif (le numéro de ce type de signal).</note> | ||
- | Un programme ne doit jamais faire d'hypothèses sur le code numérique d'un type particulier de signal, mais plutôt s'y référer toujours par son nom. En effet, un nombre pour un type de signal peut ** varier ** d'un système à l'autre, mais leurs noms sont standard. Pour la liste complète des signaux pris en charge par un système, vous pouvez exécuter sur la ligne de commande: | ||
- | <code bash> | ||
- | $ 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 | ||
- | </code> | ||
- | 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. | ||
- | ===== Messages pour décrire les signaux ===== | ||
- | Cel mai bun mod de a afișa un mesaj de descriere a unui semnal este utilizarea funcțiilor [[https://docs.python.org/3/library/signal.html#signal.strsignal|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: | ||
- | <code python msg_signal.py> | ||
- | import os | ||
- | import signal | ||
- | |||
- | sig = signal.strsignal(signal.SIGKILL) | ||
- | print (''signal {} is {}'').format(signal.SIGKILL, sig) | ||
- | </code> | ||
- | Pour la compilation et l'exécution, la séquence est la suivante: | ||
- | <code bash> | ||
- | so@spook$ python3 msg_signal.py | ||
- | signal 9 is Killed | ||
- | |||
- | </code> | ||
- | <note> Vous avez besoin de Python 3.8 ou supérieur pour utiliser cette fonctionnalité </note> | ||
- | ===== Masques de signaux. Blocage du signal ===== | ||
- | 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. | ||
- | <note> Un ** masque de signal ** a chaque bit associé à un type de signal.</note> | ||
- | Le masque de bits est utilisé par plusieurs fonctions, dont la fonction [[https://docs.python.org/3/library/signal.html#signal.pthread_sigmask|signal.pthread_sigmask]], utilisé pour changer le masque de signal du processus en cours. | ||
- | <code python> | ||
- | signal.pthread_sigmask(how, maks) | ||
- | </code> | ||
- | 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, [])''. | ||
- | |||
- | ===== Processus de reporting ===== | ||
- | La fonction peut être utilisée pour transmettre un signal [[https://docs.python.org/3/library/os.html#os.kill|os.kill]]: | ||
- | <code python> | ||
- | import os | ||
- | os.kill(pid, signo) | ||
- | </code> | ||
- | 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). | ||
- | ===== En attente d'un signal ===== | ||
- | <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: | ||
- | <code python>while (!signal_has_arrived) | ||
- | </code> | ||
- | 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 [[https://docs.python.org/3/library/time.html#time.sleep|time.sleep]]: | ||
- | <code python> | ||
- | while (!signal_has_arrived): | ||
- | time.sleep(1) | ||
- | </code> | ||
- | |||
- | ===== Exercices ===== | ||
- | |||
- | Pour résoudre le laboratoire, veuillez cloner [[https://www.github.com/upb-fils/sde|repository-ul]]. Si vous l'avez déjà, veuillez lancer git pull. | ||
- | ==== Exercice 1 - pipe (2p) ==== | ||
- | |||
- | Entrez dans le répertoire '' 1-pipe '', vous avez deux programmes: '' pipe.py '' et '' reader.py ''. | ||
- | === 1a - Pipe et fork (1p) === | ||
- | 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. | ||
- | === 1b - Pipe et exec === | ||
- | 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. | ||
- | ==== Exercice 2 - hitme (2p) ==== | ||
- | |||
- | 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: <code bash> | ||
- | |||
- | 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 | ||
- | </code> | ||
- | |||
- | |||
- | ==== Exercice 3 - Normal signals vs Real-Time signals (1p) ==== | ||
- | |||
- | 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 '':<code bash>python3 signals.py</code> | ||
- | |||
- | Dans le cas de signaux '' normaux '', dans une autre console, exécutez le script '' send_normal.sh '':<code bash>./send_normal.sh</code> | ||
- | Pour les signaux en temps réel, exécutez un autre script dans send_rt.sh dans une autre console:<code bash>./send_rt.sh</code> | ||
- | 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 | Types de signaux standard]]. | ||
- | <note tip> La différence entre le nombre de signaux reçus est due au fait que les signaux avec des indices entre '' SIGRTMIN '' et '' SIGRTMAX '' sont des signaux en temps réel, il est donc garanti qu'ils atteindront leur destination. Vue [[http://www.linuxprogrammingblog.com/all-about-linux-signals?page=9 | link]]. </note> | ||
- | |||
- | ==== Exercice 4 - askexit (2p) ==== | ||
- | |||
- | 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. | ||
- | ==== Exercice 5 - noint (1p) ==== | ||
- | |||
- | 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. | ||
- | * Veuillez consulter la section [[# Traitement du signal]]. | ||
- | Pour tester, exécutez <code bash>python3 noint.py sleep 120 &</code> | ||
- | * Voir [[# Traitement du signal]] et les sections [[sde:laboratoare:04_ro#Înlocuirea imaginii unui proces in linux]] et [[sde:laboratoare:03_ro#Redirectări]] des laboratoires précédents. | ||
- | ==== Exercice 6 - nohup (1p) ==== | ||
- | |||
- | Entrez dans le répertoire '' 6-nohup '' et créez un programme, appelé '' nohup.py '', qui simule la commande [[http://linux.die.net/man/1/nohup|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. | ||
- | * Consultez la section [[# Traitement du signal]]. | ||
- | Si le fichier de sortie standard était lié à un terminal, il doit être redirigé vers un fichier '' nohup.out ''. | ||
- | * Utilisez l'appel [[http://www.kernel.org/doc/man-pages/online/pages/man3/isatty.3.html|isatty]]. | ||
- | |||
- | Pour tester, exécutez <code bash>python3 nohup.py sleep 120 &</code> | ||
- | 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 <code bash>ps -ef | grep sleep </code> | ||
- | Qui est le nouveau parent du procès? | ||
- | * Voir la section [[#Traitement du signal]] et sections [[sde:laboratoare:04_ro#Înlocuirea imaginii unui proces in linux]] et [[sde:laboratoare:03_ro#Redirectări]] des laboratoires précédents. | ||
- | 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. | ||
- | ==== Exercice 7 - zombie (1p) ==== | ||
- | 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:<code bash> | ||
- | ps -eF | grep python3 | ||
- | </code> | ||
- | |||
- | 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 [[https://docs.python.org/3/library/signal.html#signal.signal|signal()]] et [[https://docs.python.org/3/library/os.html#os.waitpid|waitpid()]]).Voir aussi [[# Traitement du signal]] et [[sde:laboratoare:04_ro#Crearea unui proces in linux]]. | ||
- | <note tip> | ||
- | Si le parent ignore explicitement le signal SIGCHLD en définissant le gestionnaire sur SIG_IGN (au lieu de contourner le signal par défaut), les informations d'état de sortie enfant seront ignorées et les enfants ne deviendront pas des processus zombies.</note> | ||
- | |||
- | <hidden> | ||
- | ===== Solutii ===== | ||
- | [[https://github.com/UPB-FILS/sde/tree/master/tp05|Solutii]] | ||
- | </hidden> | ||