Vous êtes sur la page 1sur 13

Université Pierre et Marie Curie M1 - IRSI

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.

Exercice 7 (passage d’arguments)


Ecrire un programme qui affiche son nom et les paramètres qui lui sont donnés en ligne de
commande. Rappel : pour compiler un programme sous unix, la commande à utiliser est :

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>

int main( int argc, char *argv[] ) {

// nom du programme (1er argument):


printf( "Mon nom est: %s\n", argv[0] );
// arguments suivants:
for ( int i=1; i<argc; i++ )
printf( "Argument %d: %s\n", i, argv[i] );
}

Exercice 8 (fonctions d’E/S bas niveau sur des fichiers)


Ecrire un programme qui copie un fichier dans un autre (voir cours).

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

int main (int argc, char *argv[]) {


int fd1, fd2, buffer[1024];
size_t n;

// controle erreurs d’usage:


if ( argc != 3 ) {
printf ("usage:%s fichier_source fichier_destination\n", argv[0]);
exit(1);
}

// ouverture fichier source a copier:


fd1 = open(argv[1], O_RDONLY);
if ( fd1 == -1 ) {
perror( "open1" );
exit(1);
}

// ouverture fichier destination:


fd2 = open( argv[2], O_WRONLY | O_TRUNC | O_CREAT );
if ( fd2 == -1 ) {
perror( "open2" );
exit(1);
}

// 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");
}

Que peut-on conclure ?

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>

// handler utilisateur "bonjour" et "bonsoir"


void bonjour (int sig){
signal(SIGUSR1, bonjour);
printf("bonjour\n");
}

void bonsoir( int sig){


signal(SIGUSR2, bonsoir);
printf("bonsoir\n");
}

void quitte( int sig){


printf("fin du programme\n");
}

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() );

// emission du signal vers le pere:


printf("Fils: emission du signal SIGUSR\n" );
kill( getppid(), SIGUSR1 );
sleep(1);
printf( "Fils: Fin du processus.\n");
exit( EXIT_SUCCESS );

7
default :
// pere
// "armement" des signaux:
signal (SIGUSR1, bonjour);
// affichages:
printf("Pere: je suis le processus %d\n", getpid() );
// attente signal SIGUSR1:
wait(&status);

// attente fin du fils:


printf("Pere: signal recu, j’attend la fin du fils...\n" );
wait(&status);

// Quitte:
printf( "Pere: Fin du processus.\n");
exit( EXIT_SUCCESS );
}
}

/* handler utilisateur "bonjour" et "bonsoir" */


void bonjour (int sig) {
printf("BONJOUR\n");
}

Tubes ordinaires et nommés


Exercice 13 (tubes ordinaires)
Ecrire un programme qui crée un processus fils. Le processus fils envoi un message ”Je suis le
fils !” au processus père qui le reçoit et l’affiche à l’écran, le tout en utilisant un tube ordinaire.
Correction

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
}
}

Exercice 14 (tubes ordinaires)


Comme vu en cours, écrire un programme qui réalise la commande shell :
cat nom_du_fichier_a_lire | wc

Corrigé :

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>

int main(int narg, char*arg[])


{
int p[2], n;
char buffer[256];

if ( narg != 2 ) { // ctrl. arguments


printf( "usage: catwc nom_fichier \n" );
exit( 1 );
}

if ( pipe( p )==-1 ) { // création du tube avant la fourche


perror( "Erreur pipe" );

9
exit( 2 );
}

if ( (n=fork()) ==-1 ) { // fourche


perror( "Erreur fork" );
exit( 3 );
}
else if (n==0) { // processus fils: commande "cat"
close( 1 ); // redirection de la sortie standard
dup( p[1] ); // vers le tube en écriture
close( p[1] ); // fermeture du descripteur initial du tube
close( p[0] ); // lecture non utilisée
execlp( "cat", "cat", arg[1], 0 ); // recouvrement du processus
} // par la commande "cat nom_fichier"
else { // processus père: commande "wc"
close( 0 ); // redirection de l’entrée standard
dup( p[0] ); // vers le tube en lecture
close( p[0] ); // fermeture du descripteur initial du tube en lecture
close( p[1] ); // écriture non utilisée
execlp( "wc", "wc", 0); // recouvrement du processus
// par la commande "wc":
}
exit( 0 );
}

Exercice 15 (tubes nommés)


Ecrire trois programmes :
— creation tube nom du tube qui créer un tube dont le nom est donné en argument ;
— ecriture tube nom du tube qui écrit dans le tube un message saisi au clavier par l’uti-
lisateur ;
— lecture tube nom du tube qui lit dans le tube le message présent et l’affiche à l’écran ;
Les deux derniers programmes seront lancés à partir de deux consoles shell différentes. Après le
lancement du programme de création dans la première fenêtre, observer les fichiers présents dans
le répertoire courant. Lancer le programme d’écriture à partir de la même fenêtre et observer
à nouveau les fichiers présents et leur taille dans le répertoire courant. Même opération après
exécution du programme de lecture dans la deuxième fenêtre shell. Recommencez l’opération
en tentant une lecture du tube par la commande cat à partir du shell, après la création du
tube et l’écriture dans le tube. Tentez ensuite une execution du programme de lecture dans la
deuxième fenêtre et observer le résultat obtenu.

Correction (creation tube) :

#include <stdio.h>
#include <stdlib.h>

10
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>

// usage: creation-tube nom_du_tube

int main(int narg, char *arg[])


{
// ctrl. arguments:
if ( narg != 2 ) {
printf( "usage: creation-tube nom_du_tube \n" );
exit( 1 );
}

// création du tube avec droits (rw):


if ( mknod(arg[1], S_IFIFO|0666, 0) == -1 ) {
printf( "Echec lors de la création du tube %s \n", arg[1] );
perror( "Erreur: " );
exit( 2 );
}

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>

int main(int narg, char *arg[])


{
// descripteur tube:
int p;

// ctrl. arguments:
if ( narg != 2 ) {
printf( "usage: ecriture-tube nom_du_tube \n" );
exit( 1 );
}

// ouverture du tube en écriture:

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 );
}

printf ("Processus : %d \n", getpid() );


printf ("Ecriture du tube %s réussie \n", arg[1]);
printf ("Processus : %d, descripteur associé : %d \n", getpid(), p );

// é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>

int main(int narg, char *arg[])


{
// descripteur tube:
int p;
char buf[256];

// ctrl. arguments:
if ( narg != 2 ) {
printf( "usage: lecture-tube nom_du_tube \n" );
exit( 1 );
}

// ouverture du tube en lecture:


if ( ( p=open( arg[1], O_RDONLY ) ) == -1 ) {
printf( "Echec lors de l’ouverture du tube %s \n", arg[1] );
perror( "Erreur: " );
exit( 2 );
}

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’;

printf ("Lecture du tube nommé %s réalisée\n", arg[1]);


printf ("Message lu dans le tube: %s\n", buf);

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