next up previous
Previous: No Title

Sous-sections

Systèmes à mémoire distribuée




Objectifs :Présentation du modèle à mémoire distribuée de la programmation parallèle : communications, interneteries, modèle client/serveur, bibliothèque Unix en O'Caml

Systèmes à mémoire distribuée ou systèmes répartis

Dans ce modèle chaque processus séquentiel Pi possède sa propre mémoire privée Mi . Il est le seul à y avoir accès. Les processus doivent alors communiquer pour transférer de l'information. On suppose alors qu'il y a un medium assurant ces transferts. La difficulté de ce modèle provient de l'implantation du medium. Les programmes s'en chargeant s'appellent des protocoles.

Ceux-ci sont organisés en couche. Les protocoles de haut niveau, implantant des services élaborés, utilisent les couches de plus bas niveaux (voir les 7 couches du modèle ISO).

Ce modèle est valable dans le cas de parallélisme physique (réseau d'ordinateurs) ou logique (processus Unix communiquant par "pipes" ou threads O'Caml communiquant par canaux). Il n'y a pas de valeurs globales connues par tous les processus (comme un temps global). La seule contrainte sur le temps est l'impossibilité de recevoir un message avant son émission.

Dans ce modèle la communication est explicite alors que la synchronisation est implicite (elle est en fait produite par la communication). Ce modèle est le dual du précédent.

modèles de communications

types de communications

Interneteries

Internet est un réseau de réseau (interconnexion de réseaux). Certaines machines ont néanmoins un rôle particulier : les passerelles (pour le passage d'un réseau à un autre) et les routeurs (indiquant la route à suivre). Conceptuellement l'interconnexion se fait au niveau des réseaux et non pas des machines. Deux machines quelconques connectées sur Internet peuvent communiquer. Le réseau devient une seule entité. Différents types de machines, systèmes cohabitent sur Internet. Elles parlent toutes les protocoles IP (et UDP/TCP).

protocoles Internet

L'organisation du réseau Internet est la suivante :

----------------
| APPLICATIONS |
----------------
   ^    |
   |    |
   |    v
----------------
|   UDP / TCP  |
----------------
   ^    |
   |    |
   |    v
----------------
| IP/Interfaces|
----------------
   ^    |
   |    |
   |    v
----------------
|    MEDIAS    |
----------------

Le protocole IP est le protocole de bas niveau. L'unité de transfert est le datagramme IP. C'est un protocole non fiable : il n'assure ni le bon ordre, ni le bon port, ni la non duplication des datagrammes transmis. Il traite juste le routage d'un datagramme et la gestion des erreurs quand un datagramme n'a pas être transmis. Un datagramme contient un entête et des données. Dans l'entête apparaît les adresses du destinataire et de l'expéditeur du datagramme.Chaque machine sur Internet possède une adresse unique. Ces adresses sont codées sur 32 bits (IPv4). Par exemple l'adresse 132.227.60.30 contient 4 champs comportant des valeurs de 0 à 255.

Au dessus d'IP, deux protocoles permettent des transmissions de plus haut niveau : UDP (User Datagram Protocol) et TCP (Transfert Control Protocol). UDP est un protocole sans connexion et non fiable (il sert à multiplexer les transmissions). TCP est un protocole orienté connexion et fiable. Pour cela il doit gérer les acquittements de paquets et optimiser la transmission (technique de fenêtrage).

Les services standards (applications) d'Internet utilisent le plus souvent le modèle client/serveur. Le serveur est un programme offrant un service spécifique. Il gère les requêtes des clients en établissant une connexion ou non selon le protocole. Il y a une asymétrie entre le client et le serveur. Ces services implantent des protocoles de plus haut niveau. Parmi les services standards, on peut nommer :

D'autres services utilisent ce modèle client/serveur :

La communication entre applications s'effectuent via des prises (sockets) de communication. Elles permettent la communication entre des processus ne résidant pas forcément sur une même machine. Différents processus peuvent lire et écrire dans cette voie de communication.

Communications en O'Caml

Il existe trois possibilités d'utiliser la communication entre processus en O'Caml. La première utilise les communications entre threads. La deuxième utilise les primitives fork et pipe d'Unix. Ces deux premières méthodes utilisent un modèle logique de concurrence. Il n'y aura pas d'améliorations de performances, tous les processus tournent sur le même processeur. La troisième possibilité utilise les prises de communication (sockets) d'Unix. La communication peut alors s'effectuer entre différentes machines physiques.

canaux des threads

Le module Event de la bibliothèque des Threads d'O'Caml autorise la communication entre threads à travers des canaux de communication. Deux types sont définis :

type 'a channel
type 'a event
correspondant au type des canaux et des valeurs transmises.

Les communications s'effectuent principalement par les fonctions suivantes :

new_channel : unit -> 'a channel
send : 'a channel -> 'a -> unit evant
receive : 'a channel -> 'a event
sync : 'a event -> 'a
choose : 'a event list -> 'a event
select : 'a event list -> a

bibliothèque Unix : fork et pipe

La bibliothèque Unix d'O'Caml implante les principaux appels système de la bibliothèque Unix. Une des facilités de cette bibliothèque est d'utiliser la boucle de toplevel pour implanter ses fonctions système. Le typage facilite aussi la programmation système. Il n'est plus aussi nécessaire de pratique la lecture du man, le type de la fonction est bien souvent suffisamment parlant.

Parmi les nombreuses fonctions de cette bibliothèques, on retrouve les appels système fork pour la duplication d'un processus, les tuyaux (pipe) de communications et le duplicateur (dup2) de description de fichiers.

match fork () with
  0 -> (* code du fils *)
| _ -> (* code du pere *)

La communication entre processus

pipe : unit -> Unix.file_descr * Unix.file_descr
dup2 : Unix.file_descr -> Unix.file_descr -> unit

bibliothèque Unix : sockets

Les principaux types sont les suivants :

type socket_domain = PF_UNIX | PF_INET;;

type socket_type = SOCK_STREAM | SOCK_DGRAM |
                   SOCK_SEQPACKET | SOCK_RAW;;

type sockaddr =
  ADDR_UNIX of string | ADDR_INET of inet_addr * int

Les principales fonctions d'établissement d'une prise et de communications sont les suivantes :

socket : Unix.socket_domain -> Unix.socket_type -> int -> Unix.file_descr
connect : Unix.file_descr -> Unix.sockaddr -> unit
close : Unix.file_descr -> unit
bind : Unix.file_descr -> Unix.sockaddr -> unit
listen : Unix.file_descr -> int -> unit
accept : Unix.file_descr -> Unix.file_descr * Unix.sockaddr
read : Unix.file_descr -> string -> int -> int -> int
write : Unix.file_descr -> string -> int -> int -> int

socket crée un file_descr Unix. Pour créer une prise on utilisera l'appel suivant :

let prise = socket PF_INET SOCK_STREAM 0;;
PF_INET correspond au domain Internet, SOCK_STREAM pour un flot d'octets fiable et l'entier 0 au protocole IP.

La connexion à un serveur s'effectue par la fonction connect. L'établissement d'un service passe par les étapes suivantes :

Dès qu'une connexion est établie, les fonctions d'E/S (read et write) peuvent être utilisées sur la prise.

Les fonctions suivantes

gethostbyname : string -> Unix.host_entry
getservbyname : string -> string -> Unix.service_entry
permettent à partir d'un nom de machine d'obtenir d'une part la description complète d'une machine (incluant son adresse) etr d'autre part la description d'un service (incluant son numéro de port).

Exemples de client/serveur

serveur universel

Le schéma classique d'un serveur est le suivant :

creation de ls socket (socket)

attachement de la socket (bind)

ouverture du service (listen)

attente de connexion (accept)

  creation d'un processus (fork)


fils : boucle sur l'attente de la connexion
pere : traite la demande

code du serveur

let main () =
  let port =  int_of_string argv.(1)
  and args = Array.sub argv (Array.length argv - 2) in
  let sock =
    socket PF_INET SOCK_STREAM 0 in
  let mon_adresse =
   (gethostbyname(gethostname())).h_addr_list.(0) in
  bind sock (ADDR_INET(mon_adresse, port));
  listen sock 3;
  while true do
    let (s,app_adr) = accept sock in
    begin
      match app_adr with
        ADDR_INET(host,_) ->
          print_string ("Connexion depuis "^ string_of_inet_addr host);
          print_newline()
      | ADDR_UNIX _ -> ()
    end;
    match fork() with
     0 -> if fork() <> 0 then exit 0;
          List.iter (dup2 s) [stdin;stdout;stderr];
          close s;
          () (* travail \`a effectuer *)
    | _ -> wait ();
           close s
  done
;;

handle_unix_error main ();;

La technique du double "fork" permet d'éviter de laisser des processus "zombies" (en attente de transmission de leur code retour à leur père). Le fils termine par exit juste après le deuxième fork. Le petit fils devient donc orphelin, et est adopté par le processus init qui possède une boucle infinie de wait, et fera disparaître le processus petit fils dès qu'il termine.

code du client

let main () =
  let serveur = argv.(1)
  and port = int_of_string (argv.(2)) in
  let args = Array.sub argv (Array.length argv - 3) in 
  let serveur_adr =
    try
      inet_addr_of_string serveur
    with Failure _ ->
      try
        (gethostbyname serveur).h_addr_list.(0)
      with Not_found ->
        prerr_endline (serveur ^ " : serveur inconnu");
        exit 2
   in
   let sock =
     socket PF_INET SOCK_STREAM 0
   in
     connect sock (ADDR_INET(serveur_adr,port));
     match fork() with
       0 -> (* travail du fils a effectuer *)
            shutdown sock SHUTDOWN_SEND; (* fermeture en ecriture *)
            exit 0
     | _ -> (* travail du pere *)
            close sock;
            wait()
;;

Ce schéma père/fils permet de répartir les rôles en envoi/réception sur la prise. Ici le fils écrit sur la prise la requête et le père reçoit la réponse. Cette architecture prend du sens si le fils doit envoyer plusieurs requêtes, le père recevra les réponses des premières requêtes au fur et à mesure de leur traitement.

Threads et bibliothèque Unix

L'utilisation conjointe de la bibliothèque de processus légers et de la bibliothèque Unix provoque le blocage de toutes les "threads" actives si un appel système ne répond pas immédiatement, en particulier les lectures sur un descripteur de fichiers, incluant donc ceux créés par socket.

Pour éviter ce désagrément, le module ThreadUnix réimplante la plupart des fonctions de la bibliothèque Unix. Les fonctions définies dans ce module ne bloqueront que la thread qui effectue cet appel.

Autres lectures

Ce cours s'inspire des documents suivants :

La page du système Ensemble montre d'une boite à outils de communications développée en O'Caml.


next up previous
Previous: No Title
Emmanuel CHAILLOUX
1998-11-15