Académique Documents
Professionnel Documents
Culture Documents
Département Maths-Info
Programmation Système 1
1
Les processus
http://manpagesfr.free.fr/consulter.html
Programmation Système 2
2
Définition des processus
Programmation Système 3
3
Arbre des processus d'un système Unix
Programmation Système 4
4
Arbre des processus d'un système Unix (suite)
Le schéma ci-dessous présente l'arborescence des processus
d'un système Unix
Programmation Système 5
5
Les processus : la commande pstree
Programmation Système 6
6
Les différents états d’un processus
Programmation Système 7
7
Concept de processus
Prêt
processeur
alloué
admit
interruption
en sortie
terminé
exécution
Nouveau occurrence d’un
événement
en attente
d’un événement
Bloqué
Programmation Système 8
8
Création d’un processus
• Les processus des utilisateurs sont lancés par un interprète de
commande (shell). Ils peuvent eux même lancer ensuite d’autres
processus
P1
P2 P3
P4 P5 P6
Programmation Système 9
9
Destruction d’un processus
Programmation Système 10
10
10
Mise en oeuvre
Programmation Système 11
11
11
Structure d’un processus
Programmation Système 12
12
12
Priorités
Programmation Système 13
13
13
Un exemple : schéma d’un processus Unix
UID = 106 répertoire courant
GID = 104 PID = 36 /usr/c1
fichiers ouverts
0 <- /dev/term/c4
signaux traités cmd1 1 -> /dev/term/c4
2 -> /dev/term/c4
3 <-> /tmp/toto
umask = 027
ulimit = 2048 /dev/term/c4 priorité = 19
temps = 0.3
Ce processus a le numéro 36. Il a été lancé par l’utilisateur qui a 106 pour UID.
Il est entrain d’exécuter le programme ‘cmd1’. Il a consommé 0.3 seconde, avec
une priorité de 19. Son masque de création est 027. Son terminal de contrôle
est /dev/term/c4. Son répertoire courant est /usr/c1. Il a 4 fichiers ouverts :
0, 1, 2, et 3.
Programmation Système 14
14
14
Structure d’un processus Unix
• Le PPID est le PID du processus père
• Le processus fils hérite de tout l’environnement du processus père, sauf bien
sûr du PID, du PPID et des temps d’exécution
• Le père du processus 36 est le processus 27, et celui de 27 est le processus 1
• Seul le fils 36 a ouvert le fichier /tmp/toto
Père Fils
Programmation Système 15
15
15
Les processus : la commande ps
• Un processus est un programme qui est en cours d’exécution
• La commande ps donne un ensemble de renseignements sur les
processus en court d’exécution
• Syntaxe : ps options
• Options :
• -a : affiche des renseignement sur tous les processus attachés à
un terminal
• -l : donne, pour chaque processus, le nom de l’utilisateur (user),
le pourcentage de cpu (%cpu), la taille totale du processus dans la
mémoire (size), la mémoire réservée (rss) en Ko …
• -x : affiche également des informations sur les processus non liés au
terminal
• -w : affiche sur 132 colonnes, utile pour voir le nom complet de la
commande associée à chaque processus
Programmation Système 16
16
16
Les processus : la commande ps
% ps
PID TTY STAT TIME CMD
746 pts/3 S 00:00:00 -bash
749 pts/3 S 00:00:02 gs
848 pts/3 S 00:03:28 mozilla-bin
965 pts/3 S 00:00:00 ps
Programmation Système 17
17
17
Arrêt d’un processus : kill
• Syntaxes :
kill -signal pid
kill -l
• Options :
• -9 : demande l’arrêt du processus désigné par son pid
• -l : affiche la liste des signaux disponibles
$ kill -l
1) HUP 2) INT 3) QUIT …
7) EMT 8) FPE 9) KILL …
$ kill -9 1635
Cette commande tue le processus dont le numéro PID est 1635
Programmation Système 18
18
18
Les signaux
Programmation Système 19
19
19
Gestion des Jobs
20
20
Linux : Gestion des Services
La commande service : manipule les services de la machine
# service --status-all
rpc.mountd (pid 1541) is running...
nfsd (pid 1529) is running...
1528 (pid 1527) is running...
1526 (pid 1525) is running...
1524 (pid 1523) is running...
1522 (pid ) is running...
lockd (pid 1126) is running...
rpc.statd (pid 937) is running...
ntpd (pid 1232) is running...
numlock is enabled
partmon has been startedportmap (pid 839) is running...
The random data source exists
Sound loaded
syslogd (pid 864) is running...
http://www.fevrierdorian.com/wiki/Commandes_utiles_(Linux)
Programmation Système 21
21
21
Concepts et outils
Programmation Système 22
22
22
Généralités sur le développement sous linux
La base du système est le noyau=Linux, fonctionnant en arrière-plan
pour surveiller les applications des utilisateurs :
• C’est un ensemble cohérent de routines fournissant des services
aux applications, en s’assurant de conserver l’intégrité du
système.
• Pour le développeur, le noyau est surtout une interface entre son
application et la machine physique.
Le noyau fournit donc des points d’entrée, qu’on nomme « appels-
système », et que le programmeur invoque comme des sous-routines
offrant des services variés.
Il existe une centaine d’appels-systèmes sous Linux. Ils effectuent des
tâches très variées, allant de l’allocation mémoire aux entrées-sorties
directes sur un périphérique, en passant par la gestion du système de
fichiers, le lancement d’applications ou la communication réseau.
Il existe une couche supérieure avec des fonctions qui viennent
compléter les appels-système. Cette interface est constituée par la
bibliothèque C, qui regroupe des fonctionnalités complémentaires de
celles qui sont assurées par le noyau
Programmation Système 23
23
23
Généralités sur le développement sous linux (suite)
– Cette bibliothèque regroupe par exemple toutes les fonctions
mathématiques.
– La bibliothèque C permet aussi d’encapsuler les appels-systèmes dans des
routines de plus haut niveau, qui sont donc plus aisément portables d’une
machine à l’autre.
Il y a plusieurs bibliothèques C successivement utilisées sous Linux :
• Les versions 1 à 5 de la libc Linux,
• A partir de la version 2.0 du noyau Linux, toutes les distributions ont
basculé vers une autre version de la bibliothèque, la GlibC, issue du projet
GNU. Elle parfois nommée –abusivement- libc 6.
La commande ldd permet de connaitre la liste des bibliothèques liées à un
fichier exécutable:
#ldd libc.so.6
linux-vdso.so.1 (0x00007ffe841f2000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fed3e29b000)
/lib64/ld-linux-x86-64.so.2 (0x00005602f6f7c000)
la commande #uname –a donne le numéro de noyau
Les fonctions de la bibliothèque GlibC et les appels-systèmes représentent un
ensemble minimal de fonctionnalités indispensables pour le développement
d’application. Ils sont pourtant très limités en termes d’interface utilisateur.
Programmation Système 24
24
24
conception du système
Programmation Système 25
25
25
Architecture logique d’un système UNIX
Programmation Système 26
26
26
La démarche de développement
Programmation Système 27
27
27
Compilateur, éditeur de liens
Programmation Système 28
28
28
La programmation C
Compilation du programme
cc -o teste teste.c
ou cc teste.c -o teste
ou gcc -o teste teste.c
ou gcc teste.c -o teste
Exécution
Si la compilation s’est fait sans erreur on exécute :
./teste
Programmation Système 29
29
29
Exemple
Programmation Système 30
30
30
Compilation du programme
La compilation du fichier prog.c se déroule en général en deux étapes :
1.traduction du langage C dans le langage de la machine (binaire) sous forme d’un fichier
objet prog.o :
gcc -O3 -Wall -c prog.c
2.regroupement des variables et des fonctions du programme avec les fonctions fournies
par des bibliothèques du système ou faites par l’utilisateur (edition de liens) :
gcc -o prog prog.o
Ces deux commandes ont donc permis de générer l’exécutable prog qui peut alors est lancé
(./prog dans une fenêtre de shell).
Si un seul fichier source est utilisé pour générer l’exécutable, les deux commandes
précédentes peuvent être regroupées en une seule :
gcc -O3 -Wall prog.c -o prog
Quelques explications sur les options de gcc s’imposent :
-O : effectue des optimisations sur le code généré. La compilation peut alors prendre plus
de temps et utiliser plus de mémoire. Un niveau, variant de 0 à 3 spécifie le degré
d’optimisation demandé. Ainsi, l’option -O3 permet d’obtenir le code le plus optimisé.
-Wall : active des messages d’avertissements (warnings) supplémentaires ;
-c : demande au compilateur de ne faire que la première étape de compilation ;
-o : permet de préciser le nom du fichier produit.
Programmation Système 31
31
31
Compilation du programme
il est important de noter que l’option -Wall est indispensable puisqu’elle permet de détecter
la plupart des erreurs d’inattention, de mauvaise pratique du langage ou d’usage
involontaire de conversion implicite.
Il existe bien sûr de nombreuses autres options disponibles. En voici certaines qui pourront
être utiles :
-Idir : ajoute le répertoire dir dans la liste des répertoires ou se trouvent des fichiers de
header .h. Exemple : -Iinclude/
-Werror : considère les messages d’avertissements (warnings) comme des erreurs
-g : est nécessaire pour l’utilisation d’un debugger. Il est possible de fournir plus ou moins
d’informations au debugger. Ce critère est caractérisé par un niveau variant entre 1 et 3 (2
par défaut). Ainsi, l’utilisation du niveau maximal (option -g3) assure que le maximum
d’informations sera fourni au debugger ;
-pg : nécessaire pour l’utilisation du profiler GNU : gprof5 (qui permet de déterminer par
exemple quelles sont les parties du code qui prennent le plus de temps d’exécution et qui
doivent donc être optimisées).
Enfin, il est possible d’automatiser la compilation de l’ensemble des fichiers d’un projet à
l’aide de l’utilitaire make
Programmation Système 32
32
32
La compilation séparée
Ce n’est pas le tout de bien fragmenter son code en plusieurs fichiers, encore
faut-il les compiler pour obtenir un exécutable.
La méthode consiste à générer un fichier objet par module (option -c de gcc) :
gcc -O3 -Wall -I. -c module_1.c
gcc -O3 -Wall -I. -c module_2.c
...
gcc -O3 -Wall -I. -c module_n.c
Ces commandes générent n fichiers objets module_i.o.
L’option -I de gcc permet d’ajouter un répertoire en première position de la liste
des répertoires où sont cherchés les fichiers en-tête. (ici, le répertoire courant, ./ :
l’option -I est donc en fait facultative dans ce cas). Cette option
est utile lorsque, pour des raisons de lisibilité dans l’architecture des fichiers
sources, un répertoire Include est créé pour contenir tous les fichiers en-tête du
programme. La compilation avec gcc comportera alors l’option -IInclude.
Une passe d’´edition de lien entre ces fichiers objets en ensuite nécessaire pour
générer l’exécutable final toto.exe :
gcc -o toto.exe module_1.o module_2.o ... module_n.o
Programmation Système 33
33
33
Création d’un fichier makefile
Programmation Système 34
34
34
Programmation Système 35
35
35
Programmation Système 36
36
36
Programmation Système 37
37
37
Programmation Système 38
38
38
Programmation Système 39
39
39
Les paramètres de "main()"
main() est une fonction comme une autre. Elle reçoit donc des
paramètres et renvoie une valeur.
Programmation Système 40
40
40
Les paramètres de "main()" (suite)
main() est une fonction comme une autre. Elle reçoit donc des
paramètres et renvoie une valeur.
41
41
Les paramètres de "main()" (Exemple)
Programmation Système 42
42
42
Programmation Système 43
43
43
La variable PATH
La variable PATH contient une série de chemin vers des
répertoires qui contiennent des exécutables ou des scripts de
commande.
Comme toute variable qui se respecte, on peut afficher sa valeur
avec la commande :
44
44
ACCES A L'ENVIRONNEMENT
Programmation Système 45
45
45
L'environnement
Une application peut être exécutée dans des contextes différents :
terminaux, répertoire de travail...
C'est pourquoi le programmeur système a souvent besoin d'accéder à
l'environnement. Celui-ci est définit sous la forme de variables
d'environnement.
Les Variables d'environnement sont définies sous la forme suivante :
NOM = VALEUR.
Lorsqu'un programme en C démarre, ces variables sont
automatiquement copiées dans un tableau de char. Vous pouvez y
accéder en déclarant la variable externe globale environ au début de
votre fichier, comme ceci :
46
46
L'environnement
Programmation Système 47
47
47
Exemple
Programmation Système 48
48
48
Voici un exemple de résultat :
Programmation Système 49
49
49
Variables d'environnement classiques
Programmation Système 50
50
50
Créer, modifier, rechercher et supprimer
une variable
‒ La fonction :
int putenv(const char *string);
sert à créer une variable d'environnement. Elle prend en argument une
chaîne de caractère du type « NOM=VALEUR ».
‒ La fonction :
int setenv(const char *name, const char *value, int overwrite);
sert à modifier une variable d'environnement. Elle prend en argument le
nom de la variable, la valeur à affecter et si on écrase la précédente valeur
de la variable (si il y en a une) ou pas (1 pour l'écraser, 0 sinon).
‒ La fonction getenv, ainsi déclarée dans stdlib.h :
char *getenv(const char *name);
permet de rechercher une variable d'environnement.
‒ Enfin, la fonction :
void unsetenv(const char *name);
permet de supprimer une variable d'environnement.
Programmation Système 51
51
51
Valeur des variables d’environnement
La primitive getenv() permet d'obtenir la valeur courante d'une
variable d'environnement particulière.
#include <stdlib.h>
char* getenv (char *string);
Programmation Système 52
52
52
Valeur des variables d’environnement
Exemple :
#include <stdlib.h> /* Standards librairies "C" */
main()
{
char *pt; /* Ptr récupérant le résultat de "getenv()" */
pt=getenv("PATH");
printf("PATH = %s\n ", pt);
}
Programmation Système 53
53
53
Exercice
Programmation Système 54
54
54
Exemple
Programmation Système 55
55
55
Les appels système en C
Programmation Système 56
56
56
L’appel système fork
de création de processus
Programmation Système 57
57
57
Lancement d’un nouveau programme
Chaque programme (fichier exécutable ou script shell) en cours
d’exécution dans le système correspond à un (parfois plusieurs)
processus du système. Chaque processus possède un numéro de
processus (PID).
Les deux processus pouvant être distingués par leur numéro
d’identification PID (Process IDentifier), il est possible d’exécuter
deux codes différents au retour de l’appel-système fork(). Par
exemple, le processus fils peut demander à être remplacé par le code
d’un autre programme exécutable se trouvant sur le disque. C’est
exactement ce que fait un shell habituellement.
Programmation Système 58
58
58
Présentation des processus
Le premier processus du système, init, est créé directement par le
noyau au démarrage.
La seule manière, ensuite de créer un nouveau processus est d’appeler
l’appel-système fork(), qui va dupliquer le processus appelant.
Au retour de cet appel-système, deux processus identiques
continueront d’exécuter le code à la suite de fork().
La différence essentielle entre ces deux processus est un numéro
d’identification PID (Process IDentifier). On distingue ainsi le
processus original, qu’on nomme traditionnellement le processus père,
et la nouvelle copie, le processus fils.
L’appel-système fork() est déclaré dans <unistd.h>, ainsi :
pid_t fork(void);
Il est possible d’exécuter deux codes différents au retour de l’appel-
système fork().
Par exemple, le processus fils peut demander à être remplacé par le
code d’un autre programme exécutable se trouvant sur le disque. C’est
exactement ce que fait un shell habituellement.
Programmation Système 59
59
59
Présentation des processus (suite)
Programmation Système 60
60
60
Présentation des processus (suite)
Programmation Système 61
61
61
Présentation des processus (suite)
Lorsqu’un processus est créé par fork(), il dispose d’une copie des
données de son père, mais également de l’environnement de celui-ci et
d’un certain nombre d’autres éléments (table des descripteurs de
fichiers, etc.). On parle alors d’héritage du père.
En cas d’erreur, fork() renvoie la valeur –1, et la variable globale errno
contient le code d’erreur, défini dans <errno.h>. Ce code d’erreur peut
être soit ENOMEM, qui indique que le noyau n’a plus assez de mémoire
disponible pour créer un nouveau processus, soit EAGAIN, qui signale
que le système n’a plus de place libre dans sa table des processus, mais
qu’il y en aura probablement sous peu. Un processus est donc autorisé à
réitérer sa demande de duplication lorsqu’il a obtenu un code d’erreur
EAGAIN.
Voici à présent un exemple de création d’un processus fils par l’appel-
système fork().
Programmation Système 62
62
62
Un exemple simple
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
int
main (void)
{
pid_t pid_fils;
do {
pid_fils = fork();
} while ((pid_fils == -1) && (errno == EAGAIN));
if (pid_fils == -1){
fprintf(stderr, "fork() impossible, errno=%d\n", errno);
return 1;
}
if (pid_fils == 0) {
/* processus fils */
fprintf(stdout, "Fils : PID=%ld, PPID=%ld\n",
(long)getpid(), (long)getppid());
return 0;
} else {
/* processus père */
fprintf(stdout, "Pere : PID=%ld, PPID=%ld, PID fils=%ld\n",
(long)getpid(), (long)getppid(), (long)pid_fils);
return 0;
}
}
Programmation Système 63
63
63
fork() Exemple (prog.c)
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main()
{
pid_t pid; /* could be int */
int i;
pid = fork();
if( pid > 0 )
{
/* parent */
for( i=0; i < 1000; i++ )
printf(“\t\t\tPARENT %d\n”, i);
}
Programmation Système 64
64
64
else
{
/* child */
for( i=0; I < 1000; i++ )
printf( “CHILD %d\n”, i );
}
return 0;
}
Programmation Système 65
65
65
Sortie possible
CHILD 0
CHILD 1
CHILD 2
PARENT 0
PARENT 1
PARENT 2
PARENT 3
CHILD 3
CHILD 4
PARENT 4
:
Programmation Système 66
66
66
Attendre un processus
Le shell effectue un fork pour exécuter une commande, puis attend la
fin de ce processus.
En C, la fonction wait() attend et retourne la valeur passée à exit() par
le fils.
67
67
On crée deux processus séquentiels
chargés
• pour le premier d'exécuter la
commande pwd
• et pour le second la commande
ls -li
Programmation Système 68
68
68
Le répertoire /proc
Parmi les différents répertoires qui
constitues l'arborescence d'un système Linux typique,
l'un d'entre eux permet d'obtenir des informations sur
le système. Ce répertoire /proc contient des fichiers
comportant des informations classées par type.
C'est un pseudo système de fichiers généré de manière
dynamique au démarrage.
Les tailles de ses fichiers sont à 0 généralement, car ils
ne consomme pas d'espace disque mais seulement de
la mémoire. ce sont des liens symboliques spéciaux.
Un sous-répertoire nommé d'après le numéro ID du
processus est créé chaque fois qu'un programme est
lancé. Ce répertoire contient toutes les informations
utiles sur le processus en question.
$ man 5 proc
Programmation Système 69
69
69
Le répertoire /proc
Un exemple de fichiers d’un sous répertoire de /proc :
➢ stat contient des statistiques et des informations sur
le statut du processus. Ce sont les mêmes données
que celles présentées dans le fichier status, mais au
format numérique brut, sur une seule ligne. Ce
format est difficile à lire mais plus adapté au
traitement automatique par des programmes. Si vous
voulez utiliser le fichier stat dans vos programmes,
consultez la page de manuel de proc qui décrit son
contenu en invoquant man 5 proc.
➢ status contient des informations statistiques et statut
sur le processus formatée de façon à être lisibles par
un humain.
Programmation Système 70
70
70
Les zombies
Programmation Système 71
71
71
Rendre les fils autonomes
Programmation Système 72
72
72
Exécution des programmes
Programmation Système 73
73
73
La fonction system()
Programmation Système 74
74
74
Les primitives system()
La fonction system de la bibliothèque standard propose une manière simple d'exécuter une
commande depuis un programme, comme si la commande avait été tapée dans un shell. En
fait, system crée un sous-processus dans lequel s'exécute le shell Bourne standard (/bin/sh)
et passe la commande à ce shell pour qu'il l'exécute.
int main ()
{
int return_value ;
return_value = system ("ls -l /");
return return_value ;
}
Comme la fonction system utilise un shell pour invoquer votre commande elle est soumises
aux fonctionnalités, limitations et failles de sécurité du shell système. Vous ne pouvez pas
vous reposer sur la disponibilité d'une version spécifique du shell Bourne. Sur beaucoup de
systèmes UNIX, /bin/sh est en fait un lien symbolique vers un autre shell. Par exemple, sur
la plupart des systèmes GNU/Linux, /bin/sh pointe vers bash (le Bourne-Again SHell) et des
distributions
différentes de GNU/Linux utilisent différentes version de bash. Invoquer un programme
avec les privilèges root via la fonction system peut donner des résultats différents selon le
système GNU/Linux. Il est donc préférable d'utiliser la méthode de fork et exec pour créer
des processus.
Programmation Système 75
75
75
L’appel système exec
Appel avec listes (execl, execlp, execle)
Appel avec tableaux (execv, execvp, execve)
Programmation Système 76
76
76
Créer un nouveau processus
fork, wait et exit ne permettent pas d'exécuter de nouveaux
programmes.
Les appels système exec* permettent de remplacer l'image
courante du processus par un autre programme.
Programmation Système 77
77
77
Les primitives exec()
78
78
Les primitives execl(),execle() et execlp()
int execl(char *path, char *arg0, char *arg1,..., char *argn, NULL)
Dans execl et execle, path est une chaîne indiquant le chemin exact d’un
programme. Un exemple est "/bin/ls". Dans execlp, le « p » correspond à path et
signifie que les chemins de recherche de l’environnement sont utilisés. Par
conséquent, il n’est plus nécessaire d’indiquer le chemin complet. Le premier
paramètre de execlp pourra par exemple être "ls".
Le second paramètre des trois fonctions exec est le nom de la commande lancée et
reprend donc une partie du premier paramètre. Si le premier paramètre est
"/bin/ls", le second doit être "ls".
Pour la troisième commande, le second paramètre est en général identique au
premier si aucun chemin explicite n’a été donné.
79
79
Afficher la date avec le fils
Programmation Système 80
80
80
Afficher la date avec le fils
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
int
main (void)
{
pid_t pid_fils;
do {
pid_fils = fork();
} while ((pid_fils == -1) && (errno == EAGAIN));
if (pid_fils == -1){
fprintf(stderr, "fork() impossible, errno=%d\n", errno);
return 1;
}
if (pid_fils == 0) {
/* processus fils */
fprintf(stdout, "Fils : PID=%ld, PPID=%ld\n",
(long)getpid(), (long)getppid());
execl("/bin/date","date",(char *)0);
return 0;
} else {
/* processus père */
fprintf(stdout, "Pere : PID=%ld, PPID=%ld, PID fils=%ld\n",
(long)getpid(), (long)getppid(), (long)pid_fils);
return 0;
}
}
Programmation Système 81
81
81
Les primitives execv(),execve() et execvp()
Second groupe d’exec(). Les arguments sont passés sous forme de tableau :
int execv(char *path,char *argv[])
/* argv : pointeur vers le tableau contenant les arguments */
int execve(char *path,char *argv[],char *envp[])
int execvp(char *file,char *argv[])
path et file ont la même signification que dans le premier groupe de commandes.
Les autres paramètres du premier groupe de commandes sont regroupés dans des
tableaux dans le second groupe. La dernière case du tableau doit être un pointeur
nul, car cela permet d’identifier le nombre d’éléments utiles dans le tableau.
Pour execle et execve, le dernier paramètre correspond à un tableau de chaînes de
caractères, chacune correspondant à une variable d’environnement. On trouvera
plus de détails dans le manuel en ligne.
Programmation Système 82
82
82
Recap de la syntaxe de execve()
// exemple :
char *argv[] = {"mv","prog.c","prog.d",NULL};
char *envp[] = {"PATH=/bin",NULL};
execve("/bin/mv",argv,envp);
Programmation Système 83
83
83
Exemple execvp()
Programmation Système 84
84
84
menu.c
#include <stdio.h>
#include <unistd.h>
void main()
{ char *cmd[] = {“who”, “ls”, “date”};
int i;
printf(“0=who 1=ls 2=date : “);
scanf(“%d”, &i);
85
85
Execution
menu
cmd[i]
execlp()
printf()
n’est pas exécutée
que s’il y a un
problème avec
execlp()
Programmation Système 86
86
86
fork() et execv()
➢ execv(new_program, argv[ ])
Processus initiale
execv(nouveau programme)
Programmation Système 87
87
87
wait()
➢ #include <sys/types.h>
#include <sys/wait.h>
pid_t wait(int *statloc);
Programmation Système 88
88
88
wait() Actions
Programmation Système 89
89
89
menushell.c
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
void main()
{
char *cmd[] = {“who”, “ls”, “date”};
int i;
while( 1 )
{
printf( “0=who 1=ls 2=date : “ );
scanf( “%d”, &i );
90
if(fork() == 0)
{ /* child */
execlp( cmd[i], cmd[i], (char *)0 );
printf( “execlp failed\n” );
exit(1);
}
else
{ /* parent */
wait( (int *)0 );
printf( “child finished\n” );
}
} /* while */
} /* main */
Programmation Système 91
91
91
Execution
menushell
fork() child
cmd[i]
wait() execlp()
Programmation Système 92
92
92
MANIPULATION DE FICHIERS
Programmation Système 93
93
93
Fichiers bruts
C'est la méthode la plus efficace et rapide pour stocker et récupérer
des données sur fichier (mais aussi la moins pratique). On accède au
fichier par lecture ou écriture de blocs (groupe d'octets de taille
définie par le programmeur). C'est au programmeur de préparer et
gérer ses blocs. On choisira en général une taille de bloc constante
pour tout le fichier, et correspondant à la taille d'un enregistrement
physique (secteur, cluster...). On traite les fichiers par l'intermédiaire
de fonctions, prototypées dans stdio.h (ouverture et fermeture) et dans
unistd.h.
La première opération à effectuer est d'ouvrir le fichier. Ceci consiste
à définir le nom du fichier (comment il s'appelle sous le système) et
comment on veut l'utiliser. On appelle pour cela la fonction :
94
94
Descripteurs de fichiers
Un descripteur est un entier compris entre 0 et la valeur de la
constante OPEN_MAX qui est définie dans <limits.h> (1024 octet
sous Linux). Les descripteurs 0, 1 et 2 sont réservés respectivement
pour l’entrée et la sortie standard, ainsi que pour la sortie d’erreur.
On peut toutefois remplacer ces valeurs à profit par les constantes
symboliques :
STDIN_FILENO, STDOUT_FILENO et STDERR_FILENO
qui sont définies dans <unistd.h>.
Il existe pour cela deux appels-système, open() et creat(), le premier
est un prototype avec un argument optionnel :
int open (const char * nom_fichier, int attributs, ... /* mode_t mode */);
int creat (const char * nom_fichier, mode_t mode);
Programmation Système 95
95
95
Descripteurs de fichiers
SYNOPSIS
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);
int creat(const char *pathname, mode_t mode);
Programmation Système 96
96
96
Descripteurs de fichiers
int open (const char * nom_fichier, int attributs, ... /* mode_t mode */);
Exemple :
open (nom_fichier, O_CREAT | O_WRONLY | O_TRUNC, mode);
Programmation Système 97
97
97
Descripteurs de fichiers
• O_APPEND : positionnement en fin de fichier ;
• O_CREAT : créer le fichier s’il n’existe pas ;
• O_TRUNC : vide le fichier s’il existait ;
• O_EXCL : renvoie une erreur si le fichier existe.
Ces constantes sont définies dans fcntl.h (fcntl c.à.d. file control)
En cas de création de fichier, un troisième paramètre mode_t
mode indique les permissions si un fichier est créé, facultatif si le
fichier n’est pas créer.
La liste des modes disponibles étant longue, je vous invite à consulter
la page de manuel de open.
Exemple :
open (nom_fichier, O_CREAT | O_WRONLY | O_TRUNC, mode);
98
98
Quelques valeurs pour le mode
99
99
Pour les fonctions que nous avons étudiées, il faut inclure les
fichiers suivants :
100
100
Exemple
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/stat.h>
int main(void)
{
int fd; // Notre file descriptor
if (-1 == (fd = open("alphabet.txt", O_WRONLY | O_CREAT,
S_IRWXU))) // Création du fichier et test
{
perror("open() ");
exit(EXIT_FAILURE);
}
return EXIT_SUCCESS;
}
Programmation Système 101
101
101
La structure "stat"
La manipulation "externe" des fichiers (sans
préoccupation du contenu) se fait en utilisant son
inode.
La taille de cet inode est de 64 octets. Il contient
divers renseignements sur le fichier en lui-même
tels que ses droits d’accès, ses dates de
modification, sa taille et ses adresses de stockage
sur le disque. La structure stat définie dans
sys/stat.h permet d'accéder à ces informations.
102
102
La structure stat
struct stat {
dev_t st_dev; /* device de montage */
ino_t st_ino; /* numéro de l’inode */
ushort st_mode; /* mode de l’inode (type et droits) */
short st_nlink; /* nombre de liens */
ushort st_uid; /* numéro du propriétaire */
ushort st_gid; /* numéro du groupe */
dev_t st_rdev; /* noeuds majeur/mineur */
off_t st_size; /* taille du fichier en octet */
time_t st_atime; /* date du dernier accès */
time_t st_mtime; /* date de la dernière modification */
time_t st_ctime; /* date de création/modif. d'inode */
};
103
103
La structure stat :
❑ umode_t st_mode : droits associés au fichier :
- S_ISLNK (st_mode) : lien symbolique.
- S_ISREG (st_mode) : un fichier régulier.
- S_ISDIR (st_mode) : un répertoire.
- S_ISCHR (st_mode) : un péripherique en mode caractère.
- S_ISBLK (st_mode) : un périphérique en mode blocs.
- S_ISFIFO (st_mode) : une FIFO.
- S_ISSOCK (st_mode) : une socket.
❑ nlink_t st_nlink : nombre de liens matériels sur le fichier.
❑ uid_t st_uid : numéro du propriétaire (UID).
❑ gid_t st_gid : numéro de groupe du propriétaire (GID).
❑ off_t st_size : taille totale en octets.
❑ time_t st_atime : date du dernier accès.
❑ time_t st_mtime : date de la dernière modification.
❑ time_t st_ctime : date du dernier changement.
Programmation Système 104
104
104
Exemple 2
#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>
#include<time.h>
int main (int argc, char * argv[])
{
int st;
struct stat infos;
char * date;
st= stat(argv[1], &infos);
if (st< 0) return (-1);
/* affichage des informations. */
if (S_ISLNK (infos.st_mode))printf(" lien symbolique\n");
else if (S_ISREG (infos.st_mode))printf(" fichier régulier.");
else if (S_ISDIR (infos.st_mode))printf(" répertoire.");
else if (S_ISCHR (infos.st_mode))printf(" péripherique caractère.");
else if (S_ISBLK (infos.st_mode))printf(" périphérique blocs.");
else if (S_ISFIFO(infos.st_mode))printf("e FIFO.");
else if (S_ISSOCK(infos.st_mode))printf("e Socket\n");
printf ("liens = %d\n", (int)infos.st_nlink);
printf ("UID = %d\n", (int)infos.st_uid);
printf ("GID = %d\n", (int)infos.st_gid);
printf ("Taille= %d\n", (int)infos.st_size);
printf ("Accès = %s\n", (char *)ctime (& infos.st_atime));
printf ("Modif = %s\n", (char *)ctime (& infos.st_mtime));
printf ("Changé= %s\n", (char *)ctime (& infos.st_ctime));
return (0);
}/*main*/
Programmation Système 105
105
105
Exemple 1
#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <unistd.h>
int
main (int nb_args, char * args [])
{
struct stat sts;
if (nb_args != 2) {
fprintf (stderr, "syntaxe : %s <fichier>\n", args [0]);
exit (1);
}
if ( stat (args [1], & sts) != 0) {
printf (" erreur \n");
exit (1);
}
fprintf (stdout,"Périphérique : %d\n",sts.st_dev);
fprintf (stdout,"Noeud : %ld\n",sts.st_ino);
fprintf (stdout,"Protection : %o\n",sts.st_mode);
fprintf (stdout,"nb liens matériels: %d\n",sts.st_nlink);
fprintf (stdout,"ID propriétaire : %d\n",sts.st_uid);
fprintf (stdout,"ID groupe: %d\n",sts . st_gid);
fprintf (stdout,"Taille : %lu octets\n",sts.st_size);
fprintf (stdout,"Taille de bloc : %lu\n",sts.st_blksize);
fprintf (stdout,"Nombre de blocs : %lu\n",sts.st_blocks);
}
Programmation Système 106
106
106
La différence entre st_ctime et st_mtime
107
107
Ecriture dans un fichier
brute via l’appel système
write()
108
108
write()
La fonction open( ), rend un entier positif dont on se servira par la
suite pour accéder au fichier, ou -1 en cas d'erreur. Dans ce cas, le
type d'erreur est donné dans la variable errno, détaillée dans errno.h.
109
109
Exercice
110
110
#include <stdlib.h>
#include <stdio.h>
#include <fcntl.h>
Correction
#include <unistd.h>
#include <sys/stat.h>
int main (int argc, char * argv [])
{
int fd; // Notre file descriptor
if (argc != 2) {
fprintf (stderr, "syntaxe : %s <fichier>\n", argv [0]);
exit (1);
}
if (-1 == (fd = open(argv[1], O_WRONLY | O_CREAT, S_IRWXU))) // Création du fichier et test
{
perror("open() ");
exit(EXIT_FAILURE);
}
char c = 'a'; // Octet à écrire
int i; // compteur
for (i = 0 ; i < 26 ; i++)
{
write(fd, &c, sizeof(char)); // Ecriture, Il faut bien passer l'adresse de c, car write() attend un pointeur
!
c++; // Incrément
}
return EXIT_SUCCESS;
} Programmation Système 111
111
111
read()
ssize_t read (int fd, void * buf, size_t count);
lit dans le fichier désigné par son descripteur fd, et le met dans le
buf dont on donne l'adresse et la taille. La fonction retourne le
nombre d'octets lus, 0 si on était déjà sur la fin du fichier, -1 si
erreur.
int eof(int fd)
dit si on se trouve (1) ou non (0) sur la fin du fichier.
Lorsque l'on ne se sert plus du fichier, il faut le fermer (obligatoire
pour que le fichier soit utilisable par le système d'exploitation, entre
autre mise à jour de sa taille :
int close(int fd)
fermeture, rend 0 si ok, -1 si erreur.
112
112
Exercice
113
113
Correction
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/stat.h>
#define taillebloc 1024
int main(int argc,char *argv[])
{
int df;
char buffer[taillebloc];
int nb_lus, nb_ecrits;
if (argc!=2) {puts("erreur arguments");return(1);}
if((df=open(argv[1],O_RDONLY))<0)
{puts("erreur ouverture");return(2);}
114
114
Correction (suite)
do
{
nb_lus=read(df,(char *)buffer, taillebloc);
if (nb_lus>0) nb_ecrits= write(1,(char*)buffer, nb_lus);
}
while ((nb_lus==taillebloc)&&(nb_ecrits>0));
close(df);
return(0);
}
115
115
Exercice
116
116
Exemple
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/stat.h>
#define taillebloc 1024
int main(int argc,char *argv[])
{
int source, destination;
char buffer[taillebloc];
int nb_lus,nb_ecrits;
if (argc!=3) {puts("erreur arguments");return(1);}
if((source=open(argv[1],O_RDONLY))<0)
{puts("erreur ouverture");return(2);}
117
117
Exemple (suite)
if((destination=open(argv[2], O_WRONLY| O_CREAT| O_TRUNC,
S_IREAD| S_IWRITE| S_IEXEC))<0)
{puts("erreur ouverture");return(2);}
do
{
nb_lus=read(source,(char *)buffer, taillebloc);
if (nb_lus>0) nb_ecrits= write(destination,(char*)buffer, nb_lus);
}
while ((nb_lus==taillebloc)&&(nb_ecrits>0));
close(source);
close(destination);
return(0);
}
118
118
Déplacement dans le fichier
Le fichier peut être utilisé séquentiellement (le "pointeur de fichier"
est toujours placé derrière le bloc que l'on vient de traiter, pour
pouvoir traiter le suivant). Pour déplacer le pointeur de fichier en
n'importe que autre endroit, on appelle la fonction :
119
119
Fichiers bufférisés
Les opérations d'entrée / sortie sur ces fichiers se font par
l'intermédiaire d'un "buffer" (bloc en mémoire) géré
automatiquement. Ceci signifie qu'une instruction d'écriture
n'impliquera pas une écriture physique sur le disque mais dans le
buffer, avec écriture sur disque uniquement quand le buffer est plein.
Les fichiers sont identifiés non par un entier mais par un pointeur sur
une structure FILE (définie par un typedef dans stdio.h). Les
fonctions disponibles (prototypées dans stdio.h) sont :
FILE *fopen(char *nomfic, char *mode)
120
120
Les modes d'ouvertures possibles
- "r" : lecture seule. Vous pourrez lire le contenu du fichier, mais pas écrire
dedans. Le fichier doit avoir été créé au préalable.
- "w" : écriture seule. Vous pourrez écrire dans le fichier, mais pas lire son
contenu. Si le fichier n'existe pas, il sera créé.
- "a" : mode d'ajout. Vous écrirez dans le fichier, en partant de la fin du
fichier. Vous rajouterez donc du texte à la fin du fichier. Si le fichier
n'existe pas, il sera créé.
- "r+" : lecture et écriture. Vous pourrez lire et écrire dans le fichier. Le
fichier doit avoir été créé au préalable.
- "w+" : lecture et écriture, avec suppression du contenu au préalable. Le
fichier est donc d'abord vidé de son contenu, et vous écrivez et lisez
ensuite dedans. Si le fichier n'existe pas, il sera créé.
- "a+" : ajout en lecture / écriture à la fin. Vous écrivez et lisez du texte à
partir de la fin du fichier. Si le fichier n'existe pas, il sera créé.
121
121
fopen()
#include <stdio.h>
#include <stdlib.h>
return 0;
}
Le pointeur est initialisé à NULL dès le début. Je vous
rappelle que c'est une règle fondamentale que d'initialiser ses
pointeurs à NULL dès le début.
Programmation Système 122
122
122
lecture / écriture
123
123
Ecrire dans le fichier
Il existe plusieurs fonctions capables d'écrire dans un fichier. Ce sera
à vous de choisir celle qui est la plus adaptée à votre cas.
Voici les 3 fonctions que nous allons étudier :
124
124
fputc()
int main(int argc, char *argv[])
{
FILE* fichier = NULL;
if (fichier != NULL)
{
fputc('A', fichier); // Ecriture du caractère A
fclose(fichier);
}
return 0;
}
Programmation Système 125
125
125
fputs()
int main(int argc, char *argv[])
{
FILE* fichier = NULL;
if (fichier != NULL)
{
fputs("Salut\nComment allez-vous ?", fichier);
fclose(fichier);
}
return 0;
}
Programmation Système 126
126
126
fprintf()
int main(int argc, char *argv[])
{
FILE* fichier = NULL;
int age = 0;
fichier = fopen("test.txt", "w");
if (fichier != NULL)
{
// On demande l'âge
printf("Quel age avez-vous ? ");
scanf("%d", &age);
// On l'écrit dans le fichier
fprintf(fichier, "Le Monsieur qui utilise le programme, il a %d ans\n", age);
fclose(fichier);
}
return 0;
}
Programmation Système 127
127
127
Lire dans un fichier
Nous pouvons utiliser quasiment les mêmes fonctions que pour
l'écriture, le nom change juste un petit peu :
128
128
fgetc()
int main(int argc, char *argv[])
{
FILE* fichier = NULL;
int caractereActuel = 0;
fichier = fopen("test.txt", "r");
if (fichier != NULL)
{
caractereActuel = fgetc(fichier); // On initialise caractereActuel
// Boucle de lecture des caractères un à un
while (caractereActuel != EOF) // On continue tant que fgetc n'a pas retourné EOF
{
{ printf("%c", caractereActuel); // On affiche le caractère stocké dans
caractereActuel
caractereActuel = fgetc(fichier); // On lit le caractère suivant
}
fclose(fichier);
}
return 0;
}
129
129
fgets()
#define TAILLE_MAX 1000 // Tableau de taille 1000
130
130
fgets()
#define TAILLE_MAX 1000
int main(int argc, char *argv[])
{
FILE* fichier = NULL;
char chaine[TAILLE_MAX] = "";
fichier = fopen("test.txt", "r");
if (fichier != NULL)
{
while (fgets(chaine, TAILLE_MAX, fichier) != NULL) // On lit le fichier
tant qu'on ne reçoit pas d'erreur (NULL)
{
printf("%s", chaine); // On affiche la chaîne qu'on vient de lire
}
fclose(fichier);
}
return 0;
}
Programmation Système 131
131
131
fscanf()
int main(int argc, char *argv[])
{
FILE* fichier = NULL;
int score[3] = {0}; // Tableau des 3 meilleurs scores
if (fichier != NULL)
{
fscanf(fichier, "%d %d %d", &score[0], &score[1], &score[2]);
printf("Les meilleurs scores sont : %d, %d et %d", score[0], score[1],
score[2]);
fclose(fichier);
}
return 0;
}Programmation Système 132
132
132
Se déplacer dans un fichier
Il existe 3 fonctions à connaître :
133
133
ftell
Cette fonction est très simple à utiliser. Elle renvoie la position
actuelle du curseur sous la forme d'un long :
134
134
Le prototype de fseek est le suivant :
fseek
int fseek(FILE* pointeurSurFichier, long deplacement, int origine);
135
135
fseek
# Le code suivant place le curseur 2 caractères après le début :
fseek(fichier, 2, SEEK_SET);
fseek(fichier, 0, SEEK_END);
Programmation Système 136
136
136
rewind
Cette fonction est équivalente à utiliser fseek pour nous renvoyer à la
position 0 dans le fichier. Si vous avez eu un magnétoscope un jour
dans votre vie, eh bien c'est le même nom que la touche qui permet
de revenir en arrière.
Le prototype est :
137
137
Renommer et supprimer un fichier
Nous terminerons ce chapitre en douceur par l'étude de 2 fonctions
très simples :
- rename : renomme un fichier
- remove : supprime un fichier
138
138
rename
int rename(const char* ancienNom, const char* nouveauNom);
Exemple
return 0;
}
139
139
remove
int remove(const char* fichierASupprimer);
Exemple
return 0;
}
140
140
Le standard POSIX
141
141
Fonctions utiles
142
142
LES TUBES DE COMMUNICATION
Les tubes sont un mécanisme de communication
qui permet de réaliser des communications entre
processus sous forme d’un flot continu d’octets.
143
143
Préambule
Les pipes (tubes) sont bien connus dans le monde d'Unix. En effet, ils
permettent de faire communiquer deux processus entre eux. Ils sont
représentés par le caractère "|". On les utilisent couramment dans les
terminaux pour rediriger la sortie d'une commande vers l'entrée d'une
autre commande, par exemple :
ls | wc
Ce qui est moins courant c'est de les utiliser dans un programme en
langage C.
144
144
Généralités
➢ Un tube est une zone mémoire limitée (en général de 4Ko)
permettant à deux processus de faire transiter des données de l'un
vers l'autre. Son mode de fonctionnement est FIFO (First-In, First-
Out). C'est à dire que le premier élément à entrer dans le tube sera le
premier à en sortir.
➢ Le principe est qu'un processus ira mettre des données dans le tube
tandis qu'un second récupèrera les données émises par le premier.
➢ La lecture est bloquante tant que le tube est vide, l'écriture est
bloquante tant que le tube est plein.
➢ Les tubes sont unidirectionnels. Il faut utiliser deux tubes si, dans
l'échange, chaque processus est à la fois producteur et
consommateur.
Programmation Système 145
145
145
Les deux types de tubes
146
146
Programmation Système 147
147
147
Les tubes ordinaires (anonymes)
148
148
Les deux types de tubes
Les opérations autorisées sur un tube sont la création, la lecture,
l'écriture, et la fermeture.
Lecture/Ecriture/Fermeture
La lecture/écriture/fermeture se font dans un tube, quel qu'il soit, par
l'intermédiaire des primitives read(), write() et close() déjà vues dans la
gestion des fichiers.
Programmation Système 149
149
149
les tubes "mémoire"
150
150
Les tubes anonymes
Créer un pipe dans un processus unique n'a pas beaucoup d'intérêt mais
cela nous permet de comprendre ce qui caractérise un pipe :
➢ Un pipe possède deux extrémités.
➢ Il n'est possible de faire passer des informations que dans un
sens unique. On peut donc écrire des informations à l'entrée et en
lire à la sortie.
➢ Les deux extrémités sont référencées par des descripteurs de
fichiers (des entiers stockés dans la variable fd).
151
151
Exemple 1
#include <stdio.h>
#include <memory.h>
#include <unistd.h>
int main( int argc, char ** argv )
{
char buffer[BUFSIZ+1];
int fd[2],w,r;
pipe(fd); /* create the cd prog pipe */
152
152
Création d'un pipe dans un processus ayant un fils
153
153
Exemple 1
#include <stdio.h>
int pip[2]; /* descripteur de pipe */
char buf [6];
int main()
{pipe(pip); /* creation pipe
*/
switch (fork())
{case -1: perror("fork"); exit(1);
case 0: fils();
default: pere();}
int pere(){write (pip[1],”hello”,5); return 0;} /* écriture pipe
*/
int fils() {read (pip[0],buf,5); printf("%s",buf); return 1;}
/* lecture pipe */
}
Programmation Système 154
154
154
Création d'un pipe dans un processus ayant un fils
Pour être certain de qui va écrire et qui va lire dans le pipe, il faut
que les processus ferment les extrémités qu'ils n'utilisent pas.
De cette façon le processus père peut être certain que s'il écrit dans le
pipe (“fd[1]”), le fils va recevoir l'information en lecture (“fd[0]”).
155
155
Exercice
156
156
Exemple 3 (1/3)
#include <stdio.h>
#include <memory.h>
#include <unistd.h>
157
157
Exemple3 (2/3)
/* create the child */
int pid;
if ((pid = fork()) < 0)
{
printf("fork failed\n");
return 2;
}
if (pid == 0)
{ /* child */
char buffer[BUFSIZ];
close(pfd[1]); /* close write side */
/* read some data and print the result on screen */
while (read(pfd[0], buffer, BUFSIZ) != 0)
printf("child reads %s", buffer);
close(pfd[0]); /* close the pipe */
}
Programmation Système 158
158
158
Exemple3 (3/3)
else
{
/* parent */
char buffer[BUFSIZ];
return 0;
}
Programmation Système 159
159
159
Les tubes nommés par défaut
160
160
Les tubes nommés par défaut
161
161
Dupliquer un descripteur de fichier
NOM
dup, dup2 - Dupliquer un descripteur de fichier
SYNOPSIS
#include <unistd.h>
int dup(int oldfd);
int dup2(int oldfd, int newfd);
DESCRIPTION
dup() et dup2() créent une copie du descripteur de fichier oldfd.
Après un appel réussi à dup() ou dup2(), l'ancien et le nouveau
descripteurs peuvent être utilisés de manière interchangeable. Ils font
référence à la même description de fichier ouvert (voir open(2)) et
ainsi partagent les pointeurs de position et les drapeaux. Par exemple,
si le pointeur de position est modifié en utilisant lseek (2) sur l'un des
descripteurs, la position est également changée pour l'autre.
Programmation Système 162
162
162
Dupliquer un descripteur de fichier
DESCRIPTION (suite)
VALEUR RENVOYÉE
dup() et dup2() renvoient le nouveau descripteur, ou -1 s'ils échouent,
auquel cas errno contient le code d'erreur.
163
163
Exemple pour Dupliquer un descripteur de fichier
164
164
Uilisation de dup() pour rediriger la sortie standard
DUP()
descripteurs du processus A
1) on crée un tube:
0 STDI
N - deux descripteurs: 3 et 4 qui pointent sur la table des
1 STDOUT fichiers: ici tube
2 STDERR 2) on ferme le descripteur 1
3 tube input
- l’entrée 1 est libre
4 tube output
3) on duplique le descripteur 4 avec retour = dup (4)
5
- le descripteur 4 est recopié dans le descripteur 1 (dup
6 prend la pemière entrée libre)
- valeur de retour: le nouveau descripteur ici le 1
4) on ferme les descripteurs 3 et 4 qui ne servent plus
5) tout envoi vers le descripteur 1 concernera le tube
165
165
Ensuite il faut que stdout du premier processus (fd1) soit connecté à l'entrée
de notre pipe (fd3) et que la sortie de notre pipe (fd4) soit connecté à stdin
de notre second processus (fd0). On appellera deux fois la fonction
“dup2(param1, param2)” pour connecter fd1 à fd3 et fd4 à fd0. “dup2” prend
en argument deux paramètres, ce sont des descripteurs de fichiers : param1
vaudra fd3 et param2 vaudra fd1 car on veut que fd3 soit assimilé (ou
connecté) à fd1. Pour le second processus, param1 vaudra fd4 et parm2
vaudra fd0.
Programmation Système 166
166
166
Finalement il faut fermer les extrémités de notre pipe pour éviter les
comportements étranges, c'est un peu le même problème que dans la
section précédente. On utilisera la commande “close(fd)” pour fermer
les extrémités de notre pipe.
167
167
Programmation Système 168
168
168
Programme c : (1/3)
#include <stdio.h>
#include <memory.h>
#include <unistd.h>
169
169
Programme c : (2/3)
/* create the child */
int pid;
if ((pid = fork()) < 0)
{
printf("fork failed\n");
return 2;
}
if (pid == 0)
{ /* child */
close(pfd[1]); /* close the unused write side */
dup2(pfd[0], 0); /* connect the read side with stdin */
close(pfd[0]); /* close the read side */
/* execute the process (wc command) */
execlp("wc", "wc", (char *) 0);
printf("wc failed"); /* if execlp returns, it's an error */
return 3;
}
Programmation Système 170
170
170
Programme c : (3/3)
else
{
/* parent */
close(pfd[0]); /* close the unused read side */
dup2(pfd[1], 1); /* connect the write side with stdout */
close(pfd[1]); /* close the write side */
/* execute the process (ls command) */
execlp("ls", "ls", (char *)0);
printf("ls failed"); /* if execlp returns, it's an error */
return 4;
}
return 0;
}
171
171
Les tubes nommés
$ mkfifo tube_test
$ ls -l
172
172
Les tubes nommés
173
173
Créer les tubes nommés
174
174
Lecture dans les tubes nommés
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
int main (void)
{
FILE *fichier;
int fd;
char *nom_tube = "tube_test2";
char buffer[128];
fd = open (nom_tube, O_RDONLY);
fichier = fdopen (fd, "r");
while (fgets (buffer, 128, fichier) != NULL)
{ printf ("%s", buffer);
}
fclose (fichier);
return (0); }
Programmation Système 175
175
175
Les tubes nommés
Les tube nommés sont des tubes (pipe) qui existent dans le système de
fichiers, et donc peuvent être ouverts grâce à une référence.
Il faut préalablement créer le tube nommé dans le système de fichiers,
grâce à la primitive mkfifo, avant de pouvoir l’ouvrir avec la primitive
open.
176
176
EXEMPLE TUBE NOMMÉ
/* Processus ecrivain */
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
main()
{mode_t mode;
int tub;
mode = S_IRUSR | S_IWUSR;
mkfifo (“fictub”,mode); /* création fichier FIFO */
tub = open(“fictub”,O_WRONLY); /* ouverture fichier */
write (tub,”0123456789”,10); /* écriture dans fichier
*/
close (tub);
exit(0);}
177
177
/* Processus lecteur */
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
main()
{int tub;
char buf[11];
178
178
Exercice
179
179
Les signaux
180
180
Les signaux
181
181
Les signaux
182
182
Les signaux
183
183
Le comportement par
défaut
Lorsqu’un processus reçoit un signal, plusieurs comportements sont
possibles.
• Le signal termine l’exécution du processus. Dans certain cas, le
système écrit une image de l’état du processus dans le fichier core,
qu’on peut examiner plus tard avec un debugger ; c’est ce qu’on
appelle un core dump.
• Le signal suspend l’exécution du processus, mais le garde en
mémoire. Le processus père (typiquement, un shell) est prévenu de
ce fait, et peut choisir de terminer le processus, ou de le redémarrer
en tâche de fond, en lui envoyant d’autres signaux.
• Rien ne se passe. Le signal est complètement ignoré.
• Le signal provoque l’exécution d’une fonction qui lui a été associée
dans le programme. L’exécution normale du programme reprend
lorsque la fonction retourne.
184
184
Principe
185
185
Liste des signaux
➢ Il s'agit de messages asynchrones envoyés à un processus
– soit par le système
– soit par d'autres processus
➢ La liste des signaux peut être obtenue par la commande "kill -l" :
$ kill -l
1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL
5) SIGTRAP 6) SIGABRT 7) SIGBUS 8) SIGFPE
9) SIGKILL 10) SIGUSR1 11) SIGSEGV 12) SIGUSR2
13) SIGPIPE 14) SIGALRM 15) SIGTERM 17) SIGCHLD
18) SIGCONT 19) SIGSTOP 20) SIGTSTP 21) SIGTTIN
22) SIGTTOU 23) SIGURG 24) SIGXCPU 25) SIGXFSZ
26) SIGVTALRM 27) SIGPROF 28) SIGWINCH 29) SIGIO
30) SIGPWR 31) SIGSYS 32) SIGRTMIN 33) SIGRTMIN+1
34) SIGRTMIN+2 35) SIGRTMIN+3 36) SIGRTMIN+4 37) SIGRTMIN+5
38) SIGRTMIN+6 39) SIGRTMIN+7 40) SIGRTMIN+8 41) SIGRTMIN+9
42) SIGRTMIN+10 43) SIGRTMIN+11 44) SIGRTMIN+12 45) SIGRTMIN+13
46) SIGRTMIN+14 47) SIGRTMIN+15 48) SIGRTMAX-15 49) SIGRTMAX-14
50) SIGRTMAX-13 51) SIGRTMAX-12 52) SIGRTMAX-11 53) SIGRTMAX-10
54) SIGRTMAX-9 55) SIGRTMAX-8 56) SIGRTMAX-7 57) SIGRTMAX-6
58) SIGRTMAX-5 59) SIGRTMAX-4 60) SIGRTMAX-3 61) SIGRTMAX-2
62) SIGRTMAX-1 63) SIGRTMAX
186
186
Liste des signaux
Nom N° Rôle Core Ignoré
SIGTRAP 5 envoyé après chaque instruction. Utilisé par les programmes de mise au X
point
SIGIOT 6 émis en cas de problème matériel X
SIGEMT 7 utilise pour les calculs en virgule flottante
SIGFPE 8 erreur sur les flottants X
SIGKILL 9 émis pour tuer le processus
SIGBUS 10 émis en cas d’erreur sur le bus X
SIGSEGV 11 émis lors d’une violation de mémoire X
SIGSYS 12 émis lors d’une erreur dans les paramètres transmis à une primitive X
187
187
Liste des signaux
188
188
Les signaux CTRL-c et CTRL-z
189
189
Exemple des signaux CTRL-c et CTRL-z
190
190
Core et Ignore
191
191
Les principaux signaux
SIGHUP | 1 | A | Raccrochement (déconnexion) sur terminal de
| | | contrôle, ou mort du processus de contrôle.
SIGINT | 2 | A | Interruption depuis le clavier (CTRL C)
SIGQUIT | 3 | A | Demande 'Quitter' depuis le clavier (CTRL \)
SIGILL | 4 | A | Instruction illégale.
SIGABRT | 6 | C | Signal d'arrêt depuis abort(3).
SIGFPE | 8 | C | Erreur mathématique virgule flottante.
SIGKILL | 9 | AEF | Signal 'KILL'.
SIGSEGV | 11 | C | Référence mémoire invalide.
SIGALRM | 14 | A | Temporisation alarm(2) écoulée.
SIGTERM | 15 | A | Signal de fin.
SIGUSR1 | 30,10,16 | A | Signal utilisateur 1.
SIGUSR2 | 31,12,17 | A | Signal utilisateur 2.
SIGCHLD | 20,17,18 | B | Fils arrêté ou terminé.
SIGCONT | 19,18,25 | | Continuer si arrêté (commandes bg ou fg)
SIGSTOP | 17,19,23 | DEF | Arrêt du processus (CTRL z)
192
192
Traitement des signaux
➢ Le noyau prévoit une réaction par défaut à chaque
signal (routine handler). Cette réaction est en
général d'arrêter le processus recevant ce signal,
mais le comportement du processus à la réception
d'un signal peut être configurable.
➢ Le processus peut ainsi se prémunir contre l'effet de
certains signaux.
➢ lorsqu'un processus reçoit un signal, il exécute le
traitant correspondant (traitant=handler)
➢ le comportement "par défaut" dépend du signal:
– soit ignoré
– soit terminaison du processus
– soit terminaison du processus avec création d'un fichier
"image mémoire" (core)
193
193
Interface de programmation
➢ L'appel système signal(SIG_NUM, handler)
permet de modifier la gestion du signal
SIG_NUM :
➢ handler peut être :
– SIG_IGN : le signal sera ignoré
– SIG_DFL : traitement par défaut
– l'adresse d'une fonction fournie par le programme :
cette fonction sera appelée quand le signal sera reçu
➢ l'appel à signal() retourne l'adresse de la
fonction de traitement courante, pour pouvoir
la restaurer plus tard.
194
Signaux sous Unix
195
195
Interface de programmation
➢ l'appel système kill(PID, SIG_NUM) permet d'envoyer le signal
SIG_NUM au processus de PID PID.
➢ La fonction raise(SIG_NUM) permet à un processus de
s'envoyer le signal SIG_NUM.
➢ la fonction alarm(sec) demande au système d'envoyer un
signal SIG_ALRM au processus appelant après sec secondes.
– Si sec = 0, toute alarme demandée précédemment est
annulée
➢ la fonction pause() met le processus courant à l'état "endormi"
en attente de signaux. Quand un signal est reçu, il est traité,
puis l'exécution reprend en sortie de la fonction pause().
196
196
signaux : exemple 1
#include<stdio.h>
#include<signal.h>
void hand()
{
printf("Envoyer le signal SIGKILL pour me
tuer\n");
}
main()
{
printf("mon PID est %d\n",getpid());
signal(SIGINT,hand);
signal(SIGQUIT,hand);
for(;;);
}
197
197
Fonctionnement d'un signal
198
198
Qu’est-ce qu’un signal?
– interruption d’un processus
– fonctions utiles
• traitement à attente
effectuer
• attente du signal
• envoi du signal
traitement
signal
199
199
Émission d’un signal sous Linux
Pour envoyer un signal à un processus, on utilise l’appel système kill(), qui est
particulièrement mal nommé car il ne tue que rarement l’application cible. Cet
appel système est déclaré ainsi :
200
200
Fonctions de gestions des signaux
201
201
Intérêt du signal numéro 0
202
202
Linux Error Codes
The perror tool can be used to find the error message which
is associated with a given error code.
https://mariadb.com/kb/en/library/operating-system-error-codes/
Programmation Système 203
203
203
Fonctions de gestions des signaux
204
204
Appel système signal()
205
205
Appel système signal()
206
206
Exemple
#include <signal.h>
void trt_sig(int sig) /* Traitement du signal reçu */
{
signal(sig, trt_sig); /* Réarmement de la fonction */
printf("J'ai reçu le signal %u\n", sig);
}
207
207
Appel système pause()
#include <unistd.h>
int pause (void);
208
208
Appel système alarm ()
#include <unistd.h>
int alarm (int sec);
209
209
Appel système alarm ()
#include <unistd.h>
int alarm (int sec);
210
210