Cours n°7 :
Communication entre
processus

Réseau et Prog. Bas Niveau
Victor Poupet

Fork, et après ?

À l'aide de la fonction fork on peut créer de nouveaux processus


Exemples de communication :

Techniques IPC

Il existe de nombreuses méthodes de communication inter-processus (IPC en anglais) :

Signaux

#include <signal.h>

int kill(pid_t pid, int sig);
int raise(int sig);

int sigaction(int sig, const struct
sigaction *act, struct sigaction *oact);void sigint_handler(int sig) {
  write(0, "Recu SIGINT!\n", 13);
}

int main(void) {
  void sigint_handler(int sig);
  char s[200];

  struct sigaction sa;
  sa.sa_handler = sigint_handler;
  sa.sa_flags = 0;
  sigemptyset(&sa.sa_mask);
  sigaction(SIGINT, &sa, NULL);

  printf("Entrez du texte: ");
  printf("message: %s\n", s);
}void sigint_handler(int sig) {
  write(0, "Recu SIGINT!\n", 13);
}

int main(void) {
  void sigint_handler(int sig);
  char s[200];

  struct sigaction sa;
  sa.sa_handler = sigint_handler;
  sa.sa_flags = SA_RESTART;
  sigemptyset(&sa.sa_mask);
  sigaction(SIGINT, &sa, NULL);

  printf("Entrez du texte: ");
  printf("message: %s\n", s);
}

Entrez du texte: bonjour^CRecu SIGINT!
fgets: Interrupted system callEntrez du texte: bonjour^CRecu SIGINT!
coucou^CRecu SIGINT!
salut
message: salut

Un processus peut envoyer un signal à un autre

  • Il existe une liste de signaux qui peuvent être envoyés
  • Le destinataire est désigné par son PID
  • Chaque processus peut réagir aux signaux à l'aide de fonctions (handlers)
  • Un processus peut ignorer un signal
  • Il existe des handlers par défaut (ex : SIGINT provoque par défaut l'arrêt du processus)
  • Certains handlers ne peuvent pas être modifiés (ex : SIGKILL, SIGSTOP)

Exemples de signaux :

  • SIGINT (^C)
  • SIGSTOP
  • SIGCONT
  • SIGTERM (kill)
  • SIGKILL (kill -9)
  • SIGQUIT (fermeture du terminal)
  • SIGCHLD (fils terminé ou stoppé)
  • SIGUSR1
  • SIGUSR2 (pas de comportement par défaut)

struct sigaction est un type contenant plusieurs champs, parmi lesquels :

  • sa_handler : la fonction handler (ou SIG_IGN pour ignorer)
  • sa_mask : liste de signaux à ignorer pendant le traîtement
  • sa_flags : des options

Tubes (pipes)

#include <unistd.h>
int pipe(int fildes[2]);

int main(void) {
  int pfds[2];
  char buf[30];

  if (pipe(pfds) == -1) {
    perror("pipe");
    exit(1);
}

  printf("ecriture (%d)\n", pfds[1]);
  write(pfds[1], "test", 5);
  printf("lecture (%d)\n", pfds[0]);
  read(pfds[0], buf, 5);
  printf("recu: %s\n", buf);
}
int main(void) {
  int pfds[2];
  char buf[30];

  pipe(pfds);
  if (!fork()) {
    printf("(FILS) ecriture\n");
    write(pfds[1], "test", 5);
    printf("(FILS) fin\n");
    exit(0);
  } else {
    printf("(PERE) lecture\n");
    read(pfds[0], buf, 5);
    printf("(PERE) lu: %s\n", buf);
    wait(NULL);
  }
}

(PERE) lecture
(FILS) ecriture
(FILS) fin
(PERE) lu: test
$ ls | wc -l

int main(void) {
  int pfds[2];
  pipe(pfds);

  if (!fork()) {
    close(1); // fermer stdout
    dup(pfds[1]); //stdout = pfds[1]
    execlp("ls", "ls", NULL);
  } else {
    close(0); // fermer stdin
    dup(pfds[0]); // stdin = pfds[0]
    execlp("wc", "wc", "-l", NULL);
  }
}

Structure abstraite dans laquelle on peut lire et écrire des données

Tubes nommés (FIFO)

int main(void) {
  char s[300];
  int num, fd;

  mknod("my_fifo", S_IFIFO | 0666, 0);
  printf("attente...\n");
  fd = open("my_fifo", O_WRONLY);
  printf("connecté !\n");

  while (gets(s), !feof(stdin)) {
    write(fd, s, strlen(s));
  }
}int main(void) {
  char s[300];
  int num, fd;

  mknod("my_fifo", S_IFIFO | 0666, 0);

  printf("attente...\n");
  fd = open("my_fifo", O_RDONLY);
  printf("connecté !\n");

  do {
    num = read(fd, s, 300);
    s[num] = '\0';
    printf("lu: %s\n", s);
  } while (num > 0);
}

File (first in, first out)

Verrous de fichiers

struct flock fl;
int fd;

fl.l_type = F_WRLCK; // autres types: F_RDLCK, F_UNLCK
fl.l_whence = SEEK_SET; // autres: SEEK_CUR, SEEK_END
fl.l_start = 0;
fl.l_len = 0; // tout le fichier
fl.l_pid = getpid();

fd = open("filename", O_WRONLY);

fcntl(fd, F_SETLKW, &fl);

On peut verrouiller une portion de fichier


On peut obtenir la même chose avec des sémaphores

Sémaphores

#include <sys/ipc.h>
#include <sys/sem.h>

int semget(key_t key, int nsems, int semflg);
int semctl(int semid, int semnum, int cmd, ...);
int semop(int semid, struct sembuf *sops, unsigned int nsops);

Semblable aux verrous, mais plus versatiles

  • permettent de gérer des sections critiques (problèmes de concurrence)
  • peuvent être utilisés pour verrouiller des fichiers
  • sont créés à l'échelle de l'OS (donc accessibles par tous les processus)
  • souvent plus rapide que les verrous de fichiers

Attention : les sémaphores systèmes ne sont pas les mêmes que les sémaphores POSIX (vus en TD)

Un sémaphore a une valeur (entier relatif)

  • la valeur correspond à des passages autorisés
  • on peut augmenter la valeur
  • on peut demander à diminuer la valeur : on doit attendre que la valeur soit supérieure à ce qu'on veut retirer

Mémoire partagée

int shmget(key_t key, size_t size, int shmflg);
void *shmat(int shmid, void *shmaddr, int shmflg);
int shmdt(void *shmaddr);
int shmctl(int shmid, int cmd, struct shmid_ds *buf);key_t key;
int shmid;
char *data;

key = ftok("/home/toto/fichier", 'R');
shmid = shmget(key, 1024, 0644 | IPC_CREAT);
data = shmat(shmid, (void *)0, 0);
if (data == (char *)(-1)) {
  perror("shmat");
}

gets(data);
printf("partagé: %s\n", data);

shmctl(shmid, IPC_RMID, NULL);

Bloc de mémoire commun à deux processus

Fichiers sur mémoire

void *mmap(void *addr, size_t len, int prot, int flags, int fildes, off_t off);
int munmap(caddr_t addr, size_t len);

int fd, pagesize;
char *data;

fd = open("toto", O_RDONLY);
pagesize = getpagesize();
data = mmap((caddr_t)0, pagesize,
PROT_READ, MAP_SHARED, fd, pagesize);

Si on veut partager un fichier entre deux processus, il est parfois plus simple de mettre le fichier dans la mémoire et de manipuler des adresses

  • on ouvre le fichier avec open
  • on place le fichier en mémoire avec mmap

Arguments de mmap

  • addr : adresse où mettre le fichier (en général on laisse l'OS choisir)
  • len : taille du bloc à mettre en mémoire
  • prot : permissions du bloc (doit être compatible avec les paramètre de open)
  • flags : options
  • filedes : descripteur obtenu par open
  • off : position du début du bloc à mettre en mémoire
  • Les fichiers en mémoire sont beaucoup plus rapides d'accès (mémoire rapide et pas d'appel système)
  • Chaque processus doit placer le fichier en mémoire
  • L'espace mémoire est libéré automatiquement lorsque le processus termine

Sockets

#include <sys/socket.h>

int socket(int domain, int type, int protocol);
int bind(int socket, const struct sockaddr *address, socklen_t address_len);
ssize_t recv(int socket, void *buffer, size_t length, int flags);
ssize_t send(int socket, const void *buffer, size_t length, int flags);

Tubes bi-directionnels à travers un réseau