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
.
Tester votre programme avec “ls | wc -l
”.
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.
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
.
/**
* 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);
}
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 :
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.
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
.
En utilisant tee
, donnez une version plus simple du programme log
.
É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é.
#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;
}
É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
.
É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.
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.
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:
Écrivez un programme tstsig_1.c
qui boucle infiniment et affiche :
.
’ lorsqu’il reçoit un signal SIGUSR1 ;-
’ lorsqu’il reçoit un signal SIGUSR2 ;
’ lorsqu’il reçoit un signal SIGALRM.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.
É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
.
Écrivez un programme serveur.c
et un programme client.c
avec les
fonctionnalités suivantes :
Le serveur itère en demandant un message (directement en morse, ou
en format texte si vous avez le temps de programmer la conversion en
fin de séance) et un numéro de pid
, puis envoie le message au
processus client correspondant en utilisant les 4 signaux ci-dessus
avec les mêmes conventions de signification.
Le client est une version étendue de tstsig_2.c
. Il prend le
pid
du serveur en argument de ligne de commande puis reçoit et
affiche un message envoyé par le serveur.