Examen de Systèmes (Licence - Juin 1999)
La correction de chaque exercice/problème est disponible en naviguant à travers
les liens.
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 *);
- pourriez-vous indiquer clairement ce qu'effectue la fonction
une_zone() ?
- en supposant que vous désiriez l'utiliser dans un de vos programmes
comment vous y prendriez-vous (déclaration, compilation, édition
de liens, ...) ?
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);
}
}
- pouvez-vous indiquer quelles sorties apparaîtront à l'écran si l'on
exécute le programme ? Indiquez clairement pourquoi ?
- que se passe-t'il si l'on supprime l'appel à wait() ?
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);
}
}
- qu'est ce que ce programme produit à l'écran en résultat ?
- qu'elle est l'influence de la fonction
une_fonction_qui_calcule_quelque_chose() ?
- pouvez-vous décrire la terminaison de cette application (ordre de fin
des processus, synchronisation, etc.) ?
#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
- expliquer pourquoi le processus 5684 n'a visiblement reçu que 89
signaux.
- pouvez-vous décrire comment cette application termine ?
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.
- 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...
- que se passe-t'il si l'on remplace le pipe (
|
) par le
séquencement (;
) ?
- 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.
On se propose d'écrire une application client/serveur dans laquelle :
- les clients interrogent le serveur par l'intermédiaire d'un tube nommé
de nom /tmp/tube. Dans celui-ci ils envoient une requête informant le
serveur qu'ils désirent extraire du fichier /tmp/base_de_donnees
toutes les lignes commençant par une lettre (caractère ASCII) donnée et que
les réponses doivent être envoyées sur un tube nommé particulier (choisi
par le client) et qu'ils doivent être prévenus de la disponibilité de
chaque réponse par l'intermédiaire d'un signal (choisi par le client).
- le serveur intercepte chaque requête et créé un processus (dit de
service) dont le rôle est de répondre au client.
- chaque processus de service réalise effectivement la demande du client
en renvoyant chaque ligne dont le caractère de début est celui demandé dans le
tube du client puis en envoyant pour chacune d'entre elle un signal permettant
au client de savoir que des données sont disponibles.
Ceci est illustré dans la figure 1.
Figure 1:
Exemple simple
 |
É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.
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.
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).
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
 |
Jean-Baptiste Yunes
1999-06-22