Corrigé de l'examen de systèmes II (licence - Juin 2001)

Exercice

  1. cela permet de distinguer le créateur du processus (utilisateur dit réél) de l'utilisateur effectivement utilisé pour déterminer les droits d'accès à un objet du système. Ce mécanisme est utilisé pour permettre à tout un chacun de modifier son mot de passe (donc modifier le fichier /etc/passwd) sans avoir accès en écriture au fichier. La commande /bin/passwd utilise normalement le S_ISUID bit afin que le processus lancé par un quelconque utilisatuer possède les droits d'accès attachés au propriétaire de la commande (en l'occurence root), mais ceci de façon controlée (on peut supposer que les créateurs du système ont correctement écrit la commande de façon qu'elle ne permette pas de modifier le mot de passe de qui que ce soit d'autre que le créateur du processus).
  2. pour des raisons de sécurité. À supposer qu'un utilisateur ait accès en écriture à un fichier exécutable ne lui appartenant pas et possédant le S_ISUID bit positionné, la norme POSIX garantit ainsi que l'écriture (donc la modification de l'exécutable et par-là même de la commande) ne permettra pas d'obtenir un comportement futur non contrôlé par le propriétaire de l'exécutable.

Exercice

Voici le code de la commande :

#include <sys/times.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>

#define CLK_TCK sysconf(_SC_CLK_TCK)

int main(int argc,char *argv[]) {
  struct tms temps;
  switch(fork()) {
  case -1:
    fprintf(stderr,"%s: cannot fork\n",argv[0]);
    exit(EXIT_FAILURE);
  case 0:
    argv++;
    execvp(*argv,argv);
    exit(EXIT_FAILURE);
  default:
    wait(NULL);
    times(&temps);
    printf("Temps utilisateur: %.2f s.\n",((float)temps.tms_cutime)/CLK_TCK);
    printf("Temps système    : %.2f s.\n",((float)temps.tms_cstime)/CLK_TCK);
    exit(EXIT_SUCCESS);
  }
}

Il y manque une gestion correcte des signaux d'interruption et du code de retour de la commande, mais l'essentiel est là.

Exercice

  1. Un arbre de processus est créé, la racine étant la commande elle-même. Au premier niveau sont créés N processus chacun d'eux appellant récursivement la fonction main en ayant pris soin de décrémenter la valeur de n. Donc seront créés au second niveau N(N-1) petit-fils, puis N(N-1)(N-2) arrière petit-fils, etc. On aura donc au total : 1+sommei=0 à N n!/(n-i)!
  2. la terminaison s'effectue en partant du bas de l'arbre et en remontant, chaque processus attendant la mort de tous ses fils.
  3. dans ce cas l'exécution conduit à un engorgement sans véritable contrôle de la table des processus du système. Il partagent tous le même code donc la mémoire ne devrait pas être en cause. Par contre chaque processus tentera de créer N fils et ceci à l'infini.

Exercice

  1. ce programme semble destiné à créer dynamiquement une liste de nombre tirés au hasard. L'envoi du signal SIGUSR1 déclenche l'action ajoute() qui insèrera un nouvel élément en tête de la liste. L'envoi du signal SIGUSR2 déclenche l'action ajoute() qui insèrera un nouvel élément en fin de la liste. L'envoie du signal SIGQUIT déclenche l'action capte() qui permet d'afficher le nombre d'éléments de la liste, puis l'ensemble ordonné des éléments de la liste.
  2. dans ce programme il y a concurrence entre les fonctions capte() et ajoute() lesquelles manipulent la liste. Il est possible que certaines incohérences soient produites si un signal est délivré lorsque qu'une action pour un autre signal est en cours. On peut par exemple remarquer que le nombre de noeuds n'est mis à jour qu'après avoir inséré un nouvel élément dans la liste, donc si la fonction capte() est appelée à cet instant l'affichage pourrait indiquer un nombre de noeuds supérieur à ceux efectivement listés.
  3. pour corriger il suffit de rendre mutuellement exclusif les actions. Pour SIGUSR1 ajouter :
    sigaddset(&(action.sa_mask),SIGUSR2);
    sigaddset(&(action.sa_mask),SIGQUIT);

    Pour SIGUSR2 ajouter :

    sigaddset(&(action.sa_mask),SIGUSR1);
    sigaddset(&(action.sa_mask),SIGQUIT);

    Pour SIGQUIT ajouter :

    sigaddset(&(action.sa_mask),SIGUSR1);
    sigaddset(&(action.sa_mask),SIGUSR2);

    Rappellons qu'un signal est bloqué lors de sa délivrance.

  4. il reste des comportements que l'on ne peut espérer modifier, parce qu'ils sont liés à la sémantique POSIX des signaux. Le premier est que POSIX ne garanti en aucune façon l'ordre de délivrance des signaux il est donc possible d'envoyer SIGUSR1 suivi par SIGUSR2 alors que le système délivrera à l'application SIGUSR2 puis SIGUSR1. Le second est que si POSIX garanti qu'un signal envoyé est délivré, il n'a a pas de garantie que plusieurs exemplaires d'un même signal envoyé seront tous délivrés.

Exercice

Le programme suivant est une solution possible :

#include <sys/types.h>
#include <sys/wait.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>

int main(int argc,char *argv[]) {
  int tube[2], somme, i, j, status;
  char *mot;
  char nombre[10];

  if (argc<3) {
    fprintf(stderr,"usage: %s <mot> fichier [... fichier]\n",*argv);
    exit(EXIT_FAILURE);
  }
  mot = argv[1];
  if (pipe(tube)==-1) {
    fprintf(stderr,"%s: impossible de créer un tube\n",*argv);
    exit(EXIT_FAILURE);
  }
  for (i=2; i<argc; i++) { /* un processus par fichier */
    switch(fork()) {
    case -1: /* c'est raté on abandonne */
      fprintf(stderr,"%s: fork() impossible\n",*argv);
      close(tube[0]);
      while (wait(NULL)!=-1); /* on supprime les zombis et attends la mort des fils existant */
      exit(EXIT_FAILURE);
    case 0:
      close(tube[0]);
      close(STDOUT_FILENO);
      dup(tube[1]);
      close(tube[1]);
      execlp("grep","grep","-c",argv[1],argv[i],NULL);
      exit(3); /* grep renvoie 0: ok, 1: rien trouvé, 2: mauvais fichier */
    default:
      break;
    }
  }
  close(tube[1]);
  somme = 0;
  for (i=2; i<argc; i++) { /* on collecte les résultats */
    wait(&status);
    if (WIFEXITED(status)) {
      switch(WEXITSTATUS(status)) {
      case 0: /* grep a trouvé quelque chose, résultat dans le tube */
      case 1: /* grep n'a rien trouvé mais envoie 0 dans le tube */
        j = 0;
        do {
          if (read(tube[0],nombre+j,sizeof(char))!=sizeof(char)) {
            fprintf(stderr,"%s: fin de lecture prematurée\n");
            while (wait(NULL)!=-1); /* zombis, fils...*/M
            close(tube[0]);
            exit(EXIT_FAILURE);
          }
        } while (nombre[j++]!='\n');
        nombre[j-1] = '\0';
        somme += atoi(nombre);
        break;
      case 2: /* mauvais fichier */
      case 3: /* exec raté */
      default: /* ??? */
        break;
      }
    }
  }
  close(tube[0]);
  printf("Nombre de \"%s\" = %d\n",argv[1],somme);
  exit(EXIT_SUCCESS);
}

Une autre méthode serait de capter le signal SIGCHLD afin d'effectuer les lectures des résultats au plus tôt, alors que dans la solution présentée les lectures ne peuvent se faire qu'après la création de tous les processus fils (ce qui peut se révéler assez long. Essayez comptemot int /usr/include/*.h)...

Exercice

Voici une solution possible :

#if !defined(_XOPEN_SOURCE) && (!defined(_POSIX_C_SOURCE) || _POSIX_C_SOURCE<=2)
#error compilez en mode POSIX.4 ou XPG4
#error  XPG4: -D_XOPEN_SOURCE
#error  POSIX.4: -D_POSIX_C_SOURCE=199309L -lrt
#endif

#ifdef _XOPEN_SOURCE
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/sem.h>
#endif
#ifdef _POSIX_C_SOURCE
#include <semaphore.h>
#include <sys/mman.h>
#endif
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>

#ifdef _XOPEN_SOURCE
static int nsems, semdesc;
#endif
#ifdef _POSIX_C_SOURCE
sem_t semdesc;
#endif
volatile int *table;

int incr(int position) {
#ifdef _XOPEN_SOURCE
  struct sembuf p, v;

  p.sem_num = position%nsems;
  p.sem_op = -1;
  p.sem_flg = 0;
  v.sem_num = position%nsems;
  v.sem_op = +1;
  v.sem_flg = 0;
  semop(semdesc,&p,1);
#endif
#ifdef _POSIX_C_SOURCE
  sem_wait(&semdesc);
#endif 
  table[position]++;
#ifdef _XOPEN_SOURCE
  semop(semdesc,&v,1);
#endif
#ifdef _POSIX_C_SOURCE
  sem_post(&semdesc);
#endif
  return 0;
}

int comptelettre(const char *fichier) {
  int d;
  char c;

  d = open(fichier,O_RDONLY);
  if (d==-1) return EXIT_FAILURE;
  while (read(d,&c,sizeof(char))==sizeof(char)) {
    if (c>='a' && c<='z') {
      incr(c-'a');
      continue;
    }
    if (c>='A' && c<='Z') {
      incr(c-'A');
      continue;
    }
  }
  close(d);
  return EXIT_SUCCESS;
}

#ifndef MIN
#define MIN(a,b) ((a)<(b)?(a):(b))
#endif

int main(int argc,char *argv[]) {
  int memdesc;
  int i, status, retour;
  int valeur;

  if (argc<2) {
    fprintf(stderr,"usage: %s fichier [... fichier]\n",*argv);
    exit(EXIT_FAILURE);
  }
#ifdef _XOPEN_SOURCE
  memdesc = shmget(IPC_PRIVATE,26*sizeof(int),IPC_CREAT|0600);
  if (memdesc==-1) {
    fprintf(stderr,"%s: impossible de créer un segment de mémoire\n",*argv);
    exit(EXIT_FAILURE);
  }
  table = shmat(memdesc,NULL,0);
  if (table==(int *)-1) {
    fprintf(stderr,"%s: impossible d'attacher le segment de mémoire\n",*argv);
    shmctl(memdesc,IPC_RMID,NULL);
    exit(EXIT_FAILURE);
  }
  nsems = 13; /* XPG4 défini comme valeur recommandée 25 !!! */
  semdesc = semget(IPC_PRIVATE,nsems,IPC_CREAT|0600);
  if (semdesc==-1) {
    fprintf(stderr,"%s: impossible de créer les sémaphores\n",*argv);
    shmdt((void *)table);
    shmctl(memdesc,IPC_RMID,NULL);
    exit(EXIT_FAILURE);
  }
  valeur = 1;
  for (i=0; i<nsems; i++) {
    semctl(semdesc,i,SETVAL,&valeur);
  }
#endif
#ifdef _POSIX_C_SOURCE
  memdesc = shm_open("/shmmem",O_RDWR|O_CREAT,S_IRUSR|S_IWUSR);
  if (memdesc==-1) {
    perror("coucou");
    fprintf(stderr,"%s: impossible de créer un segment de mémoire\n",*argv);
    exit(EXIT_FAILURE);
  }
  if (ftruncate(memdesc,26*sizeof(int))==-1) {
    fprintf(stderr,"%s: impossible de créer la mémoire à la taille voulue\n",
	    *argv);
    close(memdesc);
    shm_unlink("/shmmem");
    exit(EXIT_FAILURE);
  }
  table = mmap(NULL,26*sizeof(int),PROT_READ|PROT_WRITE,
	       MAP_SHARED|MAP_NORESERVE,memdesc,0);
  if (table==MAP_FAILED) {
    fprintf(stderr,"%s: impossible d'attacher le segment de mémoire\n",*argv);
    close(memdesc);
    shm_unlink("/shmmem");
    exit(EXIT_FAILURE);
  }
  if (sem_init(&semdesc,1,1)==-1) {
    fprintf(stderr,"%s: impossible d'obtenir un sémaphore\n",*argv);
    munmap((void *)table,26*sizeof(int));
    close(memdesc);
    shm_unlink("/shmmem");
    exit(EXIT_FAILURE);
  }
#endif
  for (i=1; i<argc; i++) {
    switch(fork()) {
    case -1:
      fprintf(stderr,"%s: fork() impossible\n",*argv);
#ifdef _XOPEN_SOURCE
      shmdt((void *)table);
      shmctl(memdesc,IPC_RMID,NULL);
      semctl(semdesc,IPC_RMID,NULL);
#endif
#ifdef _POSIX_C_SOURCE
      munmap((void *)table,26*sizeof(int));
      close(memdesc);
      shm_unlink("/shmmem");
#endif
      while (wait(NULL)!=-1);
      exit(EXIT_FAILURE);
    case 0:
      retour = comptelettre(argv[i]);
#ifdef _XOPEN_SOURCE
      shmdt((void *)table);
#endif
#ifdef _POSIX_C_SOURCE
      munmap((void *)table,26*sizeof(int));
      close(memdesc);
#endif
      exit(retour);
    default:
      break;
    }
  }
  for (i=1; i<argc; i++) {
    wait(&status);
  }
  for (i=0; i<26; i++) {
    printf("Lettre '%c' en nombre %d\n",i+'a',table[i]);
  }
#ifdef _XOPEN_SOURCE
  shmdt((void *)table);
  shmctl(memdesc,IPC_RMID,NULL);
  semctl(semdesc,IPC_RMID,NULL);
#endif
#ifdef _POSIX_C_SOURCE
  munmap((void *)table,26*sizeof(int));
  close(memdesc);
  shm_unlink("/shmmem");
  sem_destroy(&semdesc);
#endif
  exit(EXIT_SUCCESS);
}
L'auteur de cette page est : Jean-Baptiste Yunès