Examen de Systèmes (Licence - Juin 1999)

La correction de chaque exercice/problème est disponible en naviguant à travers les liens.

Exercice

Soit le fichier de nom memoire.c dont le contenu est le suivant :

pid_t *une_zone(const char *commande,int *descripteur) {
  pid_t *pointeur;

  *descripteur = shmget(IPC_PRIVATE,sizeof(pid_t),IPC_CREAT|S_IRUSR|S_IWUSR);
  if (*descripteur==-1) {
    exit(EXIT_FAILURE);
  }
  pointeur = shmat(*descripteur,(void *)NULL,S_IRUSR|S_IWUSR);
  if (pointeur==(pid_t)-1) {
    (void)shmctl(*descripteur,IPC_RMID,NULL);
    exit(EXIT_FAILURE);
  }
  return pointeur;
}

ainsi que le fichier de nom memoire.h suivant :

extern pid_t *une_zone(const char *,int *);
  1. pourriez-vous indiquer clairement ce qu'effectue la fonction une_zone() ?
  2. en supposant que vous désiriez l'utiliser dans un de vos programmes comment vous y prendriez-vous (déclaration, compilation, édition de liens, ...) ?

Exercice

Soit le fichier suivant :

void affiche(char *s,pid_t num,pid_t *premier,pid_t *deuxieme) {
  printf("%s: proc_num=%4d premier=%4d deuxieme=%4d\n",
  s,num,*premier,*deuxieme);
}

int main(int argc,char *argv[]) {
  pid_t proc_num, *deuxieme, un_entier, *premier;
  int descripteur;

  premier = une_zone(argv[0],&descripteur);
  *premier = 0;
  deuxieme = &un_entier;
  un_entier = proc_num = getpid();

  affiche("pere",proc_num,premier,deuxieme);

  switch (proc_num=fork()) {
  case -1:
    fprintf(stderr,"%s: fork impossible\n",argv[0]);
    (void)shmctl(descripteur,IPC_RMID,NULL);
    exit(EXIT_FAILURE);
  case 0:
    affiche("fils",proc_num,premier,deuxieme);
    *premier  = -12;
    *deuxieme = -15;
    affiche("fils",proc_num,premier,deuxieme);
    exit(EXIT_SUCCESS);
  default:
    wait(NULL);
    affiche("pere",proc_num,premier,deuxieme);
    (void)shmctl(descripteur,IPC_RMID,NULL);
    exit(EXIT_SUCCESS);
  }
}
  1. pouvez-vous indiquer quelles sorties apparaîtront à l'écran si l'on exécute le programme ? Indiquez clairement pourquoi ?
  2. que se passe-t'il si l'on supprime l'appel à wait() ?

Exercice

Soit le programme suivant (fork2.c) :

int main(int argc,char *argv[]) {
  pid_t *pid;
  int descripteur;

  pid = une_zone(argv[0],&descripteur);

  *pid = fork();
  une_fonction_qui_calcule_quelque_chose();
  switch (*pid) {
  case -1:
    fprintf(stderr,"%s: fork impossible\n",argv[0]);
    (void)shmctl(descripteur,IPC_RMID,NULL);
    exit(EXIT_FAILURE);
  case 0:
    printf("Fils\n");
    exit(EXIT_SUCCESS);
  default:
    printf("Pere\n");
    wait(NULL);
    (void)shmctl(descripteur,IPC_RMID,NULL);
    exit(EXIT_SUCCESS);
  }
}
  1. qu'est ce que ce programme produit à l'écran en résultat ?
  2. qu'elle est l'influence de la fonction une_fonction_qui_calcule_quelque_chose() ?
  3. pouvez-vous décrire la terminaison de cette application (ordre de fin des processus, synchronisation, etc.) ?

Exercice

#define N 100

static int compteur, pere;

void affiche(int sig) {
  printf("%d a recu %d signaux\n",getpid(),compteur);
  if (pere) wait(NULL);
  exit(EXIT_SUCCESS);
}

void piege(int sig) {
  int i;
  compteur++;
  if (!pere)  for (i=0; i<10000; i++);
}

int main(int argc,char *argv[]) {
  pid_t pid, moi;
  int i;
  struct sigaction sigact;
  sigset_t set;

  sigemptyset(&set);
  sigact.sa_handler = piege;
  sigact.sa_mask = set;
  sigact.sa_flags = 0;
  sigaction(SIGUSR1,&sigact,NULL);

  sigact.sa_handler = affiche;
  sigaction(SIGUSR2,&sigact,NULL);

  pere = 1;

  switch (pid=fork()) {
  case -1:
    fprintf(stderr,"%s: fork impossible\n",argv[0]);
    exit(EXIT_FAILURE);
  case 0:
    pere = 0;
    while (1) pause();
    exit(EXIT_SUCCESS);
  default:
    moi = getpid();
    for (i=0; i<N; i++) {
      kill(pid,SIGUSR1);
      kill(moi,SIGUSR1);
    }
    kill(pid,SIGUSR2);
    printf("%d a envoye %d signaux\n",getpid(),N);
    kill(moi,SIGUSR2);
  }
}

L'exécution produit le résultat suivant :

5684 a recu 89 signaux
5683 a envoye 100 signaux
5683 a recu 100 signaux

  1. expliquer pourquoi le processus 5684 n'a visiblement reçu que 89 signaux.
  2. pouvez-vous décrire comment cette application termine ?

Exercice

Soit la commande shell suivante :

$ ( cut -f1 -d: | cat > resultat ) < /etc/passwd

Voici un extrait du manuel en ligne de la commande cut :

NAME
     cut - cut out selected fields of each line of a file 

     cut -f list [ -d delim ] [ -s ] [ file ...  ] 

DESCRIPTION 
     Use cut to cut out columns from a table or fields from  each 
     line  of  a  file;  in data base parlance, it implements the 

     -d delim  The character following -d is the field  delimiter 
               (-f option only).  Default is tab.  Space or other 
               characters with special meaning to the shell  must 
               be quoted.  delim can be a multi-byte character. 

     -f list   The list following -f is a list of fields  assumed 
               to be separated in the file by a delimiter charac- 
               ter (see -d );  for  instance,  -f1,7  copies  the 
               first and seventh field only.  Lines with no field 
               delimiters will be passed through  intact  (useful 
               for  table  subheadings),  unless -s is specified. 
               If -f is used, the input line should contain  1023 
               characters or less.

  1. indiquez ce que fait cette commande. Non seulement quels sont les résultats produits mais aussi quelles sont les redirections effectuées, sur quels processus, etc...
  2. que se passe-t'il si l'on remplace le pipe (|) par le séquencement (;) ?
  3. donnez la séquence d'appels systèmes que le shell effectue afin d'exécuter cette commande (fork(), exec(), open(), dup(), etc.). Attention ne cherchez pas à décoder la ligne, supposez simplement qu'une fonction utilitaire le fait pour vous.

Problème

On se propose d'écrire une application client/serveur dans laquelle : Ceci est illustré dans la figure 1.
  
Figure 1: Exemple simple
une figure

Première partie

Écrire une commande UNIX service <pid> <signal> <tube> <caractère> dont le rôlé est de chercher dans le fichier de nom /tmp/base_de_donnees chaque ligne commençant par le <caractère> donné en argument. Pour chaque ligne extraite, écrire celle-ci dans le tube de nom <tube> puis signaler au processus <pid> que des données sont prêtes en envoyant le signal <signal>. Lorsque le parcours du fichier est terminé la chaîne vide sera envoyée dans le tube.

Deuxième partie

Le serveur sera implanté sous forme d'une commande UNIX qui devra être lancée par la commande serveur (sans arguments). Ce serveur aura pour seule fonction de lire les requêtes sur son tube d'entrée (/tmp/tube) puis de lancer un processus de service par requète sans attendre sa terminaison.

Troisième partie

Implanter un client sous forme d'une commande UNIX client1 <caractère> permettant d'afficher à l'écran les lignes de la base de données qui commençent par le caractère donné en argument. La figure 1 illustre le cas de la commande client1 a (dans ce cas on peut constater que ce client a choisi d'être signalé par le signal de numéro 1 et que les réponses parviendront dans le tube /tmp/bidule).

Quatrième partie

Implanter un client sous forme d'une commande UNIX client2 <car1> <car2> permettant d'afficher à l'écran les lignes de la base de données qui commençent par l'un des deux caractères donnés en argument. Note : les requêtes devront être traitées en concurrence. La figure 2 schématise ce qui se passe lors de l'exécution de client2 f k. Voici une brève description de l'enchaînement des événements :
(1)
le client envoie ses deux requêtes ({189,1,/tmp/rep1,f}, {189,2,/tmp/rep2,k}) vers le serveur,
(2)
le serveur fork()/exec() une première fois afin de créer une instance de service réalisant la recherche des lignes commençant par `f'.
(3)
le serveur fork()/exec() une seconde fois afin de créer une autre instance de service réalisant la recherche des lignes commençant par `k'.
(4) & (4')
ensuite chaque processus de service se contente d'envoyer les `bonnes' lignes dans le `bon' tube en envoyant à chaque fois le `bon' signal.
Ce client doit donc consommer les réponses comme elles arrivent (c'est-à-dire lorsqu'un signal particulier les prévient que des données sont prêtes).
  
Figure 2: Exemple client/serveur
\begin{figure}
\psfig{file=figure.eps}
\end{figure}



Jean-Baptiste Yunes
1999-06-22