Table des matières


Exercice 1.

On souhaite écrire un programme my-pipe cmd1 cmd2 [liste d’arguments] qui effectue l’équivalent de la commande shell : Il s’agit ici d’utiliser un tube pour relier la sortie de la commande cmd1 à l’entrée de la commande cmd2.

  1. Tester votre programme avec “ls | wc -l”.

  2. Faire en sorte que la commande my-pipe retourne la valeur de sortie de la première commande lorsque celle-ci n’est pas nulle et celle de la seconde autrement.

  3. Prendre en compte la terminaison anticipée de cmd2. Tester votre programme avec “dmesg | more” en appuyant sur la touche “q” pour quitter le programme more.

Correction préliminaire

/**
 * TP Communications : Excercice 1.1
 */
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/types.h>

#define TUBE_LECTURE 0
#define TUBE_ECRITURE 1

int main(int argc, char *argv[]) {
	int tube[2];
	pid_t pid;
	char *cmd1[] = {argv[1], NULL}; // {"/bin/ls", NULL}
	char **cmd2  = argv+2; // {"/usr/bin/wc", "-l", NULL}
	
	/* Creation du tube */
	pipe(tube);

	/* Creation du fils */
	if ((pid = fork()) == 0) {
		/* Nous sommes dans le fils */
		
		/* Fermeture du tube en lecture */
		close(tube[TUBE_LECTURE]);

		/* Ecrasement des descripteurs des sorties (STDOUT et STDERR) */
		dup2(tube[TUBE_ECRITURE], STDOUT_FILENO);

		/* Fermeture du tube en ecriture */
		close(tube[TUBE_ECRITURE]);

		/* Execution de ls */
		execve(cmd1[0], cmd1, NULL);
  } else if (pid > 0) {
		/* Nous sommes dans le père */
		/* Fermeture du tube en ecriture */
		close(tube[TUBE_ECRITURE]);
		
		/* Ecrasement du descripteur d'entree standard (STDIN) */
		dup2(tube[TUBE_LECTURE], STDIN_FILENO);

		/* Fermeture du tube en lecture */
		close(tube[TUBE_LECTURE]);

		/* Execution de ls */
		execve(cmd2[0], cmd2, NULL);
  }
  return EXIT_SUCCESS;
}
/**
 * TP Communications : Excercice 1.3
 */
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/types.h>

#define TUBE_LECTURE 0
#define TUBE_ECRITURE 1

int main(int argc, char *argv[]) {
	int tube[2];
	pid_t pid1, pid2;
	char *cmd1[] = {"/bin/ls", "-aaaadw1OQ23", "/", NULL};
	char *cmd2[]  = {"/usr/bin/more",NULL};

	pipe(tube);

	if ((pid1 = fork()) == 0) {
        // Processus pour cmd1
		close(tube[TUBE_LECTURE]);
        dup2(tube[TUBE_ECRITURE], STDOUT_FILENO);
        close(tube[TUBE_ECRITURE]);
        execve(cmd1[0], cmd1, NULL);
    } else if (pid1 > 0) {
        if ((pid2 = fork()) == 0) {
            // Processus pour cmd2
    		close(tube[TUBE_ECRITURE]);
	    	dup2(tube[TUBE_LECTURE], STDIN_FILENO);
            close(tube[TUBE_LECTURE]);
            execve(cmd2[0], cmd2, NULL);
        } else if (pid2 > 0) {
            printf ("Création de deux processus : %d (cmd1) et %d (cmd2)\n", pid1, pid2);
            int s1 = 0, s2 = 0;
            int w1 = 0, w2 = 0;
            do {
                w1 = waitpid(pid1, &s1, WNOHANG);
                w2 = waitpid(pid2, &s2, WNOHANG);
            } while ( (!w1 && !w2) || (w1 == -1 || w2 == -1);
            if (WIFEXITED(s1) && WIFEXITED(s2)) {
                if (WEXITSTATUS(s1)) {
                    exit(WEXITSTATUS(s1));
                } else exit(WEXITSTATUS(s2));
            } else    
                exit(EXIT_FAILURE);
        }
  }
  exit(EXIT_SUCCESS);
}

Exercice 2.

On souhaite disposer d’une commande log grâce à laquelle on pourrait exécuter un programme quelconque en récupérant automatiquement une copie de sa sortie standard dans un fichier. En quelque sorte, il s’agit de dédoubler chacun des caractères envoyés sur sa sortie standard : un exemplaire est reproduit sur la sortie standard par défaut alors que l’autre est enregistré dans un fichier.

Le programme log prend en premier argument le nom du fichier à utiliser pour la sortie. Les arguments suivants constituent la commande à exécuter :

Question 1

Pour réaliser le dédoublement de la sortie standard d’un processus, une solution est de rediriger sa sortie standard vers un tube à l’extrémité duquel un second processus pourra alors facilement extraire tous les caractères et les sauver dans un fichier en même temps qu’il les affichera sur sa sortie standard.

Le programme log va donc créer un processus fils et un tube partagé par le père et le fils. Le fils, par exemple, pourra exécuter la commande spécifiée en paramètre, tandis que le père se chargera de l’affichage et de l’écriture dans le fichier. Donnez le code complet du programme log en utilisant autant que possible des fonctions d’entrée/sortie de haut niveau.

Question 2

Une solution plus modulaire serait d’utiliser un programme intermédiaire (que l’on appelera tee) dont la tâche serait simplement de lire son entrée standard et de la reproduire à la fois sur la sortie standard et dans un fichier dont le nom serait passé en paramètre. Voici un exemple d’utilisation de tee :

    $ echo Salut a tous | tee toto
    Salut a tous
    $ cat toto
    Salut a tous
    $

Donnez le code du programme tee.

Question 3

En utilisant tee, donnez une version plus simple du programme log.

Excerice 3

Écrire un programme qui boucle tout en affichant ctrl-c lorsque le processus qui l’exécute reçoit le signal SIGINT. Dans un second temps on fera en sorte que seul le premier signal SIGINT soit traité.

Correction

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <string.h>
 
static void foo (int sig) {
    printf ("Coucou\n");
}
 
int main (int argc, char *argv[]) {
	struct sigaction act;
 
	act.sa_handler = foo;
    sigemptyset(&act.sa_mask);
	sigaction(SIGINT, &act, NULL);
 
    printf ("Pid : %d\n", getpid());
	
    while (1) ;

	return EXIT_SUCCESS;
}

Exercice 4

Écrire une commande recevoir-signaux qui affiche son pid puis boucle tout en affichant en les numérotant (dénombrant) les signaux que son processus reçoit. Tester cette commande en utilisant la commande unix kill.

Exercice 5

Écrire une commande emettre-signaux pid-cible k s1 s2 ... sn qui émet à destination du processus cible k fois la séquence de signaux donnée en paramètre. Utiliser cette commande pour «bombarder» de signaux un processus exécutant la commande de l’exercice précédent.

Exercice 6

Il s’agit maintenant de faire dialoguer un père et son fils à l’aide de signaux. Pour cela on va écrire une commande signaux-pere-fils k s1 s2 ... sn où le fils va émettre vers son père k fois la séquence de signaux donnée en paramètre. On utilisera le signal KILL pour terminer le dialogue. Afin de ne pas perdre de signaux on va mettre en œuvre le protocole suivant : le père devra envoyer au fils le signal USR1 pour acquitter chaque réception, de son coté le fils devra attendre l’acquittement du père pour poursuivre l’émission.

Exercice 7

Cet exercice a pour but de réaliser une communication utilisant le code morse (ou un quelconque code similaire) entre deux processus — un client et un serveur — au moyen de signaux. Un résumé du codage morse est disponible dans le fichier morse.txt. Pour information, il faut y ajouter les particularités suivantes:

Question 1

Écrivez un programme tstsig_1.c qui boucle infiniment et affiche :

Vous utiliserez la fonction sigaction(3) pour gérer la mise en place des gestionnaires (handler) de signaux. Vous pourrez utiliser la commande kill(1) pour envoyer des signaux au processus depuis un terminal.

Question 2

Écrivez un programme tstsig_2.c à partir de tstsig_1.c qui — en plus des fonctionnalités de tstsig_1.c — se termine en affichant le message “[over]” lorsqu’il reçoit un signal SIGTERM.

Question 3

Écrivez un programme serveur.c et un programme client.c avec les fonctionnalités suivantes :