<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> <html> <head> <meta name="generator" content="HTML Tidy for Linux/x86 (vers 1st February 2002), see www.w3.org"> <!-- this stylesheet will later on be added by lfparser automatically: --> <style type="text/css"> <!-- pre { font-family:monospace,Courier } pre.code { font-family:monospace,Courier;background-color:#aedbe8;border-color:#aedbe8 } p.code { width:80%; alignment:center; background-color:#aedbe8; border-style:none; border-width:medium; border-color:#aedbe8; padding:0.1cm ; text-align:left } --> </style> <title></title> </head> <body> <h1>Programmation en temps partag� - Files d'attente de messages (2)</h1> <h4>ArticleCategory:</h4> SoftwareDevelopment <h4>AuthorImage:</h4> <img src="../../common/images/LeonardoGiordani.jpg" width="85" height="109" alt="[Leonardo]"> <h4>TranslationInfo:</h4> <p>original in en: <a href="mailto:leo.giordani(at)libero.it">Leonardo Giordani</a></p> <p>en to fr: <a href="http://tontonpol.dyndns.org">Paul Delannoy</a></p> <h4>AboutTheAuthor:</h4> <P> Etudiant ing�nieur en t�l�communications � l'�cole Polytechnique de Milan, il travaille comme administrateur r�seau et s'int�resse � la programmation (surtout en langage assembleur et en C/C++). Depuis 1999 il ne travaille que sous Linux/Unix. <h4>Abstract:</h4> Cette s�rie d'articles se propose d'initier le lecteur au concept de <i>multit�che</i> et � sa mise en oeuvre dans le syst�me d'exploitation Linux. Nous partirons des concepts th�oriques de base concernant le <i>multit�che</i> pour aboutir � l'�criture compl�te d'une application illustrant la communication entre processus, avec un protocole simple mais efficace. <p> Pour comprendre l'article il faudrait avoir : <ul> <li>Une connaissance minimale du shell <li>Une connaissance standard du langage C (syntaxe, boucles, librairies) </ul> Toute r�f�rence � des pages de manuel est plac�e entre parenth�ses apr�s le nom de la commande concern�e. Toute fonction de la <i>glibc</i> est document�e par la commande "info Libc". <h4>ArticleIllustration:</h4> <img src="../../common/images/illustration272.jpg" width="300" height="180" alt="[run in paralell]"> <h4>ArticleBody:</h4> <h3>Introduction</h3> Nous avons appris dans l'article pr�c�dent � synchroniser et � faire travailler ensemble deux processus (ou plus) en utilisant des files d'attente de messages. Nous allons maintenant un peu plus loin, en commen�ant � cr�er notre propre protocole d'�change de messages. <p>Nous avons pr�c�demment d�fini un protocole comme un ensemble de r�gles qui permettent le dialogue entre deux personnes ou machines, m�me si elles sont diff�rentes. L'usage de la langue anglaise par exemple est un protocole, puisqu'il me permet de parler � mes lecteurs indiens (qui ont toujours �t� tr�s int�ress�s par ce que j'�cris). Pour parler de choses plus proches de Linux, si vous recompilez votre noyau (pas de panique, ce n'est pas si compliqu�), vous remarquerez sans doute la section Networking (R�seau), qui permet de faire comprendre � votre noyau diff�rents protocoles r�seau, comme TCP/IP.</p> <p>Afin de cr�er un protocole, nous allons commencer par d�finir le type d'application que nous envisageons. Notre exemple sera un simulateur de commutateur t�l�phonique. Un processus principal simulera le commutateur lui-m�me, et ses processus fils simuleront les actions des utilisateurs : chaque utilisateur devra pouvoir �changer des messages avec un autre au travers du commutateur.</p> <p>Le protocole doit g�rer trois situations diff�rentes : la naissance d'un utilisateur (i.e. l'utilisateur existe et se connecte), le travail courant d'un utilisateur, et la disparition d'un utilisateur (il n'est plus connect�). Abordons les trois cas :</p> <p>Lorsqu'un utilisateur se connecte au syst�me, il cr�e sa propre file d'attente de messages (n'oublions pas : c'est un processus), dont les identifiants doivent �tre envoy�s au commutateur afin que celui-ci sache comment atteindre cet utilisateur. Il a le temps d'initialiser des structures de donn�es si n�cessaire. Il re�oit du commutateur l'identifiant d'une file d'attente de messages, dans laquelle il pourra �crire les messages d�livr�s � d'autres utilisateurs par le commutateur lui-m�me.</p> <p>L'utilisateur peut donc envoyer et recevoir des messages. Lorsqu'il �met un message vers un autre utilisateur, deux cas sont possibles : le destinataire est connect� ou non. Nous d�cidons que dans les deux cas, un accus� de r�ception sera envoy� � l'exp�diteur, afin qu'il sache ce qu'il advient de son message. Ceci peut �tre accompli par le commutateur lui-m�me, sans que le destinataire n'ait � faire quoi que ce soit.</p> <p>Lorsqu'un utilisateur se d�connecte, il doit en informer le commutateur, mais aucune autre action n'est n�cessaire. Le m�tacode d�crivant cette fa�on de faire est le suivant</p> <pre class="code"> /* Naissance */ create_queue init send_alive send_queue_id get_switch_queue_id /* Travail courant */ while(!leaving){ receive_all if(<send condition>){ send_message } if(<leave condition>){ leaving = 1 } } /* Disparition */ send_dead </pre> <p>Maintenant il faut d�finir le comportement de notre commutateur t�l�phonique : lorsqu'un utilisateur se connecte il nous envoie l'identifiant de sa file d'attente de messages; nous devons conserver cet identifiant, afin de faire parvenir � cet utilisateur les messages qui lui sont destin�s, et nous devons r�pondre en lui fournissant l'identifiant d'une file d'attente dans laquelle il peut �crire les messages destin�s � d'autres utilisateurs. Nous devons ensuite analyser les messages en attente et v�rifier que les destinataires soient vivants : si le destinataire est connect� nous d�livrons le message, sinon nous l'ignorons; dans les deux cas nous informons l'exp�diteur. Lors de la disparition d'un utilisateur, nous supprimons simplement l'identifiant de sa file d'attente, il devient ainsi injoignable.</p> <p>A nouveau, le m�tacode est le suivant :</p> <pre class="code"> while(1){ /* Nouvel utilisateur */ if (<birth of a user>){ get_queue_id send switch_queue_id } /* Disparition utilisateur */ if (<death of a user>){ remove_user } /* Distribution des messages */ check_message if (<user alive>){ send_message ack_sender_ok } else{ ack_sender_error } } </pre> <h3>Gestion des erreurs</h3> G�rer les erreurs est une des choses les plus importantes et difficiles, dans la conduite d'un projet. De plus, un bon ensemble de routines de gestion d'erreurs, peut repr�senter la moiti� des lignes de code n�cessaires au projet. Nous n'allons pas expliquer ici comment d�velopper un bon syst�me d'analyse des erreurs, c'est un sujet trop cons�quent, mais � partir de maintenant je vais toujours tester les conditions d'erreur. Une bonne introduction � ces questions d'analyse d'erreurs est la lecture du manuel de la biblioth�que glibc (www.gnu.org) mais, si vous �tes int�ress�s, j'�crirai un article sur le sujet. <h3>Mise en oeuvre du protocole - Couche 1</h3> Notre petit protocole aura deux couches : la premi�re (la plus basse) est constitu�e des fonctions de gestion des files d'attente, de pr�paration et d'envoi des messages, alors que la plus haute met en oeuvre le protocole gr�ce � des fonctions ressemblant au m�tacode utilis� pour d�crire le comportement du commutateur et des utilisateurs. <p>La toute premi�re chose � faire est de d�finir la structure des messages, en utilisant le prototype msgbuf fourni par le noyau</p> <pre class="code"> typedef struct { int service; int sender; int receiver; int data; } messg_t; typedef struct { long mtype; /* Type du message */ messg_t message; } mymsgbuf_t; </pre> <p>Il y a ici quelque chose de g�n�ral que nous pourrons �tendre plus tard : les champs exp�diteur et destinataire contiennent un identifiant d'utilisateur et le champ donn�es contient des donn�es quelconques, tandis que le champ service permet d'adresser une demande particuli�re au commutateur. Par exemple il pourrait y avoir deux services pr�vus : l'un pour la distribution imm�diate d'un message, l'autre pour la distribution diff�r�e, le champ data devant alors contenir le d�lai en secondes. Ce n'est qu'un exemple, mais il permet de comprendre que le champ service offre de nombreuses possibilit�s.</p> <p>Nous pouvons maintenant d�finir quelques fonctions pour traiter nos structures de donn�es, particuli�rement pour lire ou �crire les valeurs des champs des messages. Ce sont plus ou moins toujours les m�mes, aussi je ne vous en propose que deux, vous trouverez les autres dans les fichiers *.h</p> <pre class="code"> void set_sender(mymsgbuf_t * buf, int sender) { buf->message.sender = sender; } int get_sender(mymsgbuf_t * buf) { return(buf->message.sender); } </pre> <p>Le but de telles fonctions n'est certes pas de compresser le code (elles ne contiennent qu'une seule ligne !) : elles ont pour but de conserver la d�finition du protocole proche d'un langage humain, et donc d'�tre plus simples � utiliser.</p> <p>Ecrivons maintenant les fonctions destin�es � g�n�rer des cl�s IPC, cr�er et supprimer des files d'attente de messages, envoyer et recevoir des messages : construire une cl� IPC est simplement ceci :</p> <pre class="code"> key_t build_key(char c) { key_t key; key = ftok(".", c); return(key); } </pre> <p>La fonction qui cr�e une file d'attente </p> <pre class="code"> int create_queue(key_t key) { int qid; if((qid = msgget(key, IPC_CREAT | 0660)) == -1){ perror("msgget"); exit(1); } return(qid); } </pre> <p>comme vous le voyez la gestion d'erreurs est ici extr�mement simple. La fonction suivante supprime une file d'attente</p> <pre class="code"> int remove_queue(int qid) { if(msgctl(qid, IPC_RMID, 0) == -1) { perror("msgctl"); exit(1); } return(0); } </pre> <p>Et enfin les fonctions de r�ception et d'envoi de messages : pour nous, �mettre un message c'est l'�crire dans une file d'attente sp�cifique, celle qui nous a �t� indiqu�e par le commutateur.</p> <pre class="code"> int send_message(int qid, mymsgbuf_t *qbuf) { int result, lenght; lenght = sizeof(mymsgbuf_t) - sizeof(long); if ((result = msgsnd(qid, qbuf, lenght, 0)) == -1) { perror("msgsnd"); exit(1); } return(result); } int receive_message(int qid, long type, mymsgbuf_t *qbuf) { int result, length; length = sizeof(mymsgbuf_t) - sizeof(long); if((result = msgrcv(qid, (struct msgbuf *)qbuf, length, type, IPC_NOWAIT)) == -1) { if(errno == ENOMSG){ return(0); } else { perror("msgrcv"); exit(1); } } return(result); } </pre> <p>C'est tout. Vous trouverez des instructions compl�mentaires dans le fichier <a href="../../common/src/article296/">layer1.h</a> : essayez de cr�er un programme (par ex. celui du dernier article) avec elles. Dans le prochain article nous aborderons la couche 2 du protocole et sa mise en oeuvre.</p> <h3>Lectures recommand�es</h3> <ul> <li>Silberschatz, Galvin, Gagne, <b>Operating System Concepts - Sixth Edition</b>, Wiley&Sons, 2001</li> <li>Tanenbaum, WoodHull, <b>Operating Systems: Design and Implementation - Second Edition</b>, Prentice Hall, 2000</li> <li>Stallings, <b>Operating Systems - Fourth Edition</b>, Prentice Hall, 2002</li> <li>Bovet, Cesati, <b>Understanding the Linux Kernel</b>, O'Reilly, 2000</li> <li>Le guide du programmeur Linux : <b>http://www.tldp.org/LDP/lpg/index.html</b></li> <li>Linux Kernel 2.4 Internals <b>http://www.tldp.org/LDP/lki/lki-5.html</b></li> <li>Site Web du canal IRC #kernelnewbies <b>http://www.kernelnewbies.org/</b></li> <li> La FAQ de la liste de diffusion "linux-kernel" <b>http://www.tux.org/lkml/</b> </li> </ul> <p>Comme toujours vous pouvez m'adresser commentaires, corrections, questions � mon adresse �lectronique (leo.giordani(at)libero.it) ou par la page de discussion. S.V.P �crivez en anglais, allemand ou italien.</p> </body> </html>