Académique Documents
Professionnel Documents
Culture Documents
TP 1 Programmation unix
Eléments de correction
Préparation du TP
— Commandes de base du shell unix : ls, cd, pwd, mkdir, mv, rm, passwd, cp, cat, wc ;
— Emploi du manuel unix (commande man) ;
— Redirection des flux standards d’entrée et de sortie ;
— Processus, fork(), execl() ;
— Utilisation des tubes (pipe) unix ordinaires et nommés.
Utilisation du shell
Exercice 1
Connectez-vous à l’aide du login qui vous a été donné. Ouvrez une fenêtre shell et tapez la
commande login puis passwd et changez de mot de passe. Déconnectez-vous (tapez exit) et
reconnectez-vous (login).
Exercice 2
Déplacez-vous à la racine du système et regardez les fichiers qui s’y trouvent. Allez dans le
répertoire /bin. Quels types de fichiers s’y trouvent ? reconnaissez-vous des noms ? Lesquels ?
Même question pour le répertoire /dev, /lib puis /usr/lib. Pouvez-vous lire ces fichiers ?
écrire dedans ? les exécuter ?
Exercice 3
Tapez la commande man man. A quoi correspond-elle ? Utilisez la commande man avec l’ensemble
des commandes vues dans ce TP.
La commande man man appel le manuel sur la commande man qui explique donc le fonction-
nement de cette commande.
Exercice 4
Créez un fichier texte rep.txt qui contient la liste des fichiers de votre répertoire courant. Re-
copiez ce fichier dans un autre fichier rep2.txt en utilisant la commande cat et une redirection
appropriée.
ls > rep.txt
cat rep.txt > rep2.txt
Exercice 5
Utilisez le manuel pour comprendre le traitement effectué par la commande wc. Ecrivez à l’aide
d’un pipe une commande qui compter les fichiers présents dans le répertoire courant et qui
affiche le résultat sur le flux standard de sortie.
La commande wc fichier.txt compte le nombre de mots, de lignes et de caractères dans
le fichier donné en argument. L’option -w limite la commande au nombre de mots. Ainsi wc
-w fichier.txt affiche le nombre de mots présents dans le fichier. Pour compter le nombre
de fichiers présents dans un répertoire, il suffit d’utiliser les deux commandes ls et wc à
l’aide d’un pipe. Le flux standard de sortie de la première commande est redirigé vers le flux
standard d’entrée de la deuxième : ls | wc -w.
Processus
Exercice 6 (à partir du shell)
1. Ouvrez une fenêtre shell et taper les commandes ps, ps -e, ps -f puis ps -ef. Que
font ces commandes. Expliquez les résultats obtenus.
La commande ps permet de lister les processus en cours d’exécution. Sans arguments,
ne sont listés que les processus exécutés à partir du shell en cours d’execution, lui
compris. Le programme shell utilisé s’appelle bash. On doit donc noter la présence de
deux processus en cours d’exécution : l’interpréter bash et la commande ps elle même.
L’argument -e permet d’afficher tous les processus et l’argument -f ajoute des infor-
mations concernant l’environnement d’exécution des processus. Les deux arguments
peuvent être cumulés par simple concaténation (-ef)
2. Ouvrez une deuxième fenêtre shell. Tapez la commande top -d 1 et observez. Sur la
première fenêtre, tapez la commande cat. Qu’observez vous sur la deuxième ? Quittez
le programme top et tapez la commande ps. Observez le résultat obtenu en comparant
avec le résultat obtenu lorsque l’on tape la même commande dans la première fenêtre
shell.
Le programme top permet de visualiser les processus en cours avec une période de
rafraichissement réglée à 1 seconde ( paramètre -d 1). La commande cat tapée sans
arguments sur la première fenêtre est en attente d’un fichier à afficher. On peut voir ce
processus apparaı̂tre sur la deuxième fenêtre via la commande top. Après avoir quitté
le programme top, la commande ps montre que le terminal utilisé par la deuxième
fenêtre n’est pas le même que celui de la première fenêtre (/dev/pts/1 au lieu de
/dev/pts/0).
3. Dans la deuxième fenêtre, exécutez l’interpréteur (shell) bash, puis tapez à nouveau la
commande ps. Interprettez le résultat obtenu. Tapez exit puis tapez à nouveau ps.
2
Exécuter la commande bash revient à lancer un nouvel interpréteur, que l’on voit bien
apparaı̂tre avec la commande ps. Dans ce cas on n’utilise plus le premier qui reste en
attente. La commande exit permet de quitter le deuxième interpréter pour retrouver
le premier, ainsi que la commande ps nous le montre.
4. Des questions précédentes, trouvez un moyen de lancer des commandes dans le deuxième
fenêtre dont les résultats s’affichent dans la première fenêtre. Que se passe-t-il lorsque
vous tapez une commande erronée ou qui n’existe pas, par exemple coucou ?
Ici nous devons donc rediriger le flux standard de sortie de l’interpréteur vers la fenêtre
de visualisation du premier shell. On en connaı̂t le device associé qui est /dev/pts/0.
Il suffit donc de taper la commande dans la deuxième fenêtre bash > /dev/pts/0.
Une fois cette commande lancée, tous les résultats des commandes tapées sur la
deuxième fenêtre sont affichés dans la première fenêtre. Lorsque l’on tape la com-
mande coucou qui n’existe pas dans la deuxième fenêtre, le message d’erreur continue
d’apparaı̂tre dans la deuxième fenêtre car le flux d’erreur standard n’a pas été redirigé.
La commande complète permettant de rediriger l’affichage et les erreurs serait bash
> /dev/pts/0 2> /dev/pts/0.
cc -o prog prog.c
où prog.c est le nom du fichier source et prog le nom donné au programme exécutable.
Corrigé :
#include <stdio.h>
3
Corrigé :
#include <stdio.h> // fonctions E/S haut niveau
#include <stdlib.h>
#include <errno.h> // gestion des erreurs
#include <sys/types.h> // définitions de types (ex: size_t)
#include <fcntl.h> // ctrl des descripteurs de fichiers
#include <unistd.h> // fonctions E/S bas niveau
// copie:
do {
n = read( fd1, (void *) buffer, sizeof(buffer) );
if (n>0)
if ( write( fd2, (void*) buffer, n ) != n )
perror ( "write" );
}while ( n>0 );
close( fd1 );
close( fd2 );
exit(0);
}
4
Exercice 9 (fork)
Ecrire un programme qui utilise une fourche pour créer un programme fils (fonction fork(). Le
processus père affichera à l’écran le message ”je suis le père de PID : XXXX”, tandis que que
le processus fils affichera ”je suis le fils de PID : YYYY” (utiliser la fonction int getpid()).
Corrigé :
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main() {
int pid;
pid = fork();
if ( pid==-1 ) {
perror("Exercice 9.\n");
exit(1);
}
else if ( pid==0) {
// processus fils
printf("Je suis le fils de PID %d\n", getpid() );
}
else {
printf("Je suis le Pere de PID %d\n", getpid() );
}
}
Exercice 10 (execl)
La fonction execl() est un mécanisme complémentaire à la création de processus implanté via
la fonction fork() dont dispose le système Unix. Ecrire un programme appelé bonjour dont
le code affiche à l’écran la chaı̂ne de caractères suivante : "bonjour". Ecrire puis exécuter le
deuxième programme suivant :
#include <stdio.h>
#include <unistd.h>
int main() {
printf("preambule du bonjour. \n");
execl("./bonjour", (char*) 0);
printf("postambule du bonjour. \n");
}
5
Corrigé (programme bonjour.c) :
#include <stdio.h>
int main() {
printf("Bonjour\n");
}
Seuls les affichages des textes ”preambule du bonjour.” et ”Bonjour” apparaissent. Explica-
tion : la fonction execl() lance l’exécution du programme bonjour qui est chargé en mémoire
à la place du programme appelant. Ce dernier ne reprend donc pas son exécution lorsque le
programme appelé a terminé la sienne, et le dernier affichage ”postambule du bonjour” ne
peut donc avoir lieu.
Signaux
Exercice 11 (signaux)
Ecrire un programme qui affiche bonjour lorsqu’il reçoit le signal USR1, bonsoir lorsqu’il reçoit
le signal USR2 et quitte après avoir affiché fin du programme lorsque l’on tape Ctrl-C sur le
clavier. Tester le programme en envoyant les signaux à partir du shell.
Corrigé :
#include <stdio.h>
#include <signal.h>
int main() {
signal(SIGUSR1, bonjour);
signal(SIGUSR2, bonsoir);
signal(SIGQUIT, quitte);
6
for(;;);
}
Exercice 12 (synchronisation)
Ecrire un programme dans lequel un processus crée un fils et initialise un handler (afficher
BONJOUR) sur SIGUSR1. Le fils affiche des informations à l’écran puis envoi le signal SI-
GUSR1 à son père. Attention le programme fils doit se terminer avant le processus père. La
sortie (les messages sur l’écran) du programme devra ressembler à ceci :
— père : Je suis le processus 27406
— fils : Je suis le processus 27407
— père : j’attends un signal BONJOUR
— fils : fin du processus
— père : fin du processus
Correction
// premier programme
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
// prototypes:
void bonjour (int sig);
int main() {
int pid;
int status;
switch(pid=fork()){
case -1 :
perror("Exercice 12.\n");
exit(2);
case 0:
// fils
printf("Fils: je suis le processus %d\n", getpid() );
7
default :
// pere
// "armement" des signaux:
signal (SIGUSR1, bonjour);
// affichages:
printf("Pere: je suis le processus %d\n", getpid() );
// attente signal SIGUSR1:
wait(&status);
// Quitte:
printf( "Pere: Fin du processus.\n");
exit( EXIT_SUCCESS );
}
}
int main() {
int tube[2];
char buffer[32];
int pid;
int status;
if ( pipe(tube)==-1 ){
perror( "Erreur a l’ouverture du pipe.\n");
exit(1);
}
8
if ( (pid = fork()) == -1) {
perror( "Erreur fork.\n" );
exit(1);
}
else if( pid==0) {
// processus fils:
close(tube[0]); // fermeture lecture du tube
write(tube[1],"Je suis le fils!", 17);
exit(0);
}
else {
// processus pere:
close(tube[1]); // fermeture ecriture du tube
read(tube[0], buffer, 17); // lecture du tube
printf( "Pere: message <%s> recu\n", buffer);
wait(&status); // attend la terminaison du fils
}
}
Corrigé :
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
9
exit( 2 );
}
#include <stdio.h>
#include <stdlib.h>
10
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
return 0;
}
Correction (ecriture tube) :
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
// ctrl. arguments:
if ( narg != 2 ) {
printf( "usage: ecriture-tube nom_du_tube \n" );
exit( 1 );
}
11
if ( ( p=open( arg[1], O_WRONLY ) ) == -1 ) {
printf( "Echec lors de l’ouverture du tube %s \n", arg[1] );
perror( "Erreur: " );
exit( 2 );
}
// écriture bloquante:
write ( p, "ABCDEFGH", 8);
printf ("Ecriture dans le tube nommé %s réalisée", arg[1]);
return 0;
}
Correction (lecture tube) :
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
// ctrl. arguments:
if ( narg != 2 ) {
printf( "usage: lecture-tube nom_du_tube \n" );
exit( 1 );
}
12
printf ("Processus : %d \n", getpid() );
printf ("Ouverture du tube %s en lecture réussie \n", arg[1]);
printf ("Processus : %d, descripteur associé : %d \n", getpid(), p );
// lecture bloquante:
read( p, buf, 8);
buf[8] = ’\0’;
return 0;
}
Attention, l’écriture autant que la lecture sont bloquantes. Cela signifie que le programme
d’écriture n’écrit pas dans le tube tant qu’aucun programme de lecture du tube ne s’exécute.
Inversement, le programme de lecture dans le tube ne lit pas dans le tube tant qu’aucun
programme d’écriture ne s’exécute. Après le lancement du programme de création, on observe
la présence d’un nouveau fichier de type ”tube” dans le répertoire courant. Après avoir lancé
le programme d’écriture, on n’observe aucun changement car la programme est bloqué en
attente qu’un programme de lecture s’exécute. Lorsque le programme de lecture est exécuté,
on observe les affichages des deux programmes s’opérer sur les deux consoles. Le tube est
toujours présent dans le répertoire courant, mais sa taille vaut 0 car il est déjà vidé par
le programme de lecture. Après avoir recommencé l’opération d’écriture dans le tube puis
appelé le programme cat, le tube est vidé par l’appel à cat. Une exécution du programme
de lecture est bloquante car le programme attend une nouvelle écriture dans le tube.
13