Vous êtes sur la page 1sur 210

Programmation Système

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

Un processus est l’image en mémoire d’un programme en


cours d’exécution, il a un cycle de vie : il naît, il meurt.
Un même programme lancé deux fois génère deux processus
différents. Un processus peut créer d’autres processus, on les
appelle ses « fils ».

Un processus est identifié en mémoire par un numéro unique


défini par le système d’exploitation appelé le PID (Processus
IDentifier).

Programmation Système 3

3
Arbre des processus d'un système Unix

init est le processus parent de tous les autres. Il permet


d'attribuer le nom de la machine, d'initialiser la date et
l'heure et de contrôler si le système a bien été arrêté. Il lance
un processus de surveillance getty qui permet d'afficher le
message "login :" sur un écran terminal et d'attendre une
réponse. Il lance également un processus de surveillance du
réseau : inetd. Les processus dont le nom se termine par "d"
sont appelés processus démons. Ce sont des processus
serveurs qui tournent en boucle afin d'offrir le même service
à plusieurs clients.

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

Arborescence des processus Unix

Programmation Système 5

5
Les processus : la commande pstree

Programmation Système 6

6
Les différents états d’un processus

Comme nous l’avons énoncé précédemment, un processus


a un cycle de vie, ses 4 états principaux sont :
─ Initial : le processus est nouvellement créé et se
trouve dans un état de transition,
─ Actif : le processus s’exécute,
─ En attente : le processus est suspendu, il libère
alors l’UC pour un autre processus,
─ Final (zombie) : le processus a terminé son
exécution.

Programmation Système 7

7
Concept de processus

• Le processeur traite une tâche à la fois, s’interrompe et passe à la


suivante

• Le diagramme d’état du 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

• Ces processus doivent ensuite pouvoir communiquer entre eux

• Le processus créateur = le père


• Les processus crées = les fils

• Les processus peuvent se structurer sous la forme d’une arborescence

P1

P2 P3

P4 P5 P6

Programmation Système 9

9
Destruction d’un processus

• 3 possibilités pour l’arrêt d’un processus


• Normal : par lui même en ayant terminé ses opérations
• Autorisé : par son père qui exécute une commande appropriée
• Anormal : par le système
• temps d’exécution dépassé
• mémoire demandée non disponible
• instruction invalide
• etc.

• Le processus créateur est le seul à pouvoir exécuter l’arrêt de ses fils

• Dans plusieurs systèmes, la destruction d’un processus père entraîne


la destruction de tous ses fils

Programmation Système 10

10

10
Mise en oeuvre

• Pour mettre en oeuvre le modèle des processus, le système


d’exploitation construit une table, appelé table des processus, dont
chaque entrée correspond à un processus particulier

• Chaque entrée comporte des informations sur :


• l’état du processus
• son compteur ordinal : contient l’adresse de la prochaine instruction
à extraire de la mémoire
• son pointeur de pile : contient l’adresse courante du sommet de pile
en mémoire
• son allocation mémoire
• l’état de ses fichiers ouverts
• et tous ce qui peut être sauvegardé lorsqu’un processus passe de
l’état élu à l’état prêt

Programmation Système 11

11

11
Structure d’un processus

• L’environnement d’un processus comprend :


• un numéro d’identification unique appelé PID (Process IDentifier)
• le numéro d’identification de l’utilisateur qui a lancé ce processus,
appelé UID (User IDentifier), et le numéro du groupe auquel
appartient cet utilisateur, appelé GID (Group IDentifier)
• le répertoire courant
• les fichiers ouverts par ce processus
• le masque de création de fichier, appelé umask
• la taille maximale des fichiers que ce processus peut créer,
appelé ulimit
• la priorité
• les temps d’exécution
• le terminal de contrôle, c’est à dire le terminal à partir duquel la
commande a été lancée, appelé TTY

Programmation Système 12

12

12
Priorités

Chaque processus a une priorité


d’exécution
Sous linux les priorité vont de –20 à 19
Plus la valeur est grande plus la priorité
est petite
La commande nice affecte la priorité
$ nice –5 find / -name *.c

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

PID = 27 répertoire PID = 36 répertoire


UID = 106 PPID = 1 UID = 106 PPID = 27
GID = 104 courant GID = 104 courant
/usr/c1 /usr/c1

fichiers ouverts fichiers ouverts


signaux 0 <- /dev/term/c4 signaux
traités ksh traités cmd1 0 <- /dev/term/c4
1 -> /dev/term/c4 1 -> /dev/term/c4
2 -> /dev/term/c4 2 -> /dev/term/c4
umask = 027 umask = 027 3 <-> /tmp/toto
ulimit = 2048 ulimit = 2048

/dev/term/c4 priorité = 19 /dev/term/c4 priorité = 19


temps = 0.1 temps = 0.3

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

• PID : le numéro d’identification du processus


• TTY : le terminal depuis lequel le processus a été lancé
• STAT : l’état du processus au moment du lancement de la commande
• R : le processus est en cours d’exécution
• T : le processus est stoppé
• S : le processus dort depuis moins de 20 secondes
• Z : le processus en attente d’un message du noyau (zombie)
• TIME : le temps d ’exécution de la commande
• CMD : le libellé de la commande lancée

Programmation Système 17

17

17
Arrêt d’un processus : kill

• La commande kill permet d’envoyer un signal au processus

• 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

•Chaque processus peut recevoir des signanux


•Chaque signal a une signification particulière
•Pour envoyer un signal on utilise la commande kill
•Exemple kill –9 2345

Nom du signal Numéro Description

SIGINT 2 touche Ctrl-C, termine le processus

SIGKILL 9 arrêter tout programme car il ne peut être géré


différemment que le comportement par défaut.
L'arrêt du programme est brutal.
SIGTERM 15 Arrête le processus, mais permet d’effectuer
des opérations avant l’arrêt.
SIGCHLD 17 Informe le père de la mort de son fils

Programmation Système 19

19

19
Gestion des Jobs

•Un job est un ensemble de 1 ou plusieurs commande


•Une commande peut etre aussi un programme à lancer
$ ls | wc est un job
$ emacs

•Lancement des jobs en arrière plan avec &


$ emacs &

•jobs : affiche les jobs lancés sur le shell


•bg : reprendre l’exécution d’un job arrêté;
$ bg %n
•fg : remet en avant plan un job;
$ fg %n
Avec n le numéro du job
Programmation Système 20

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

la conception du système repose sur différents niveaux bien


distincts: le noyau, un interpréteur de commandes (le shell),
des bibliothèques et un nombre important d’utilitaires.

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

Le développement en C sous Linux comme sur la plupart des autres


systèmes d’exploitation met en œuvre principalement trois étapes :
• L’éditeur de texte,
• Le compilateur, qui permet de passer d’un fichier source à un
fichier objet,
• L’éditeur de liens, qui assure le regroupement des fichiers objet
provenant des différents modules et les associe avec les
bibliothèques utilisées pour l’application. Nous obtenons ici un
fichier exécutable.

Programmation Système 27

27

27
Compilateur, éditeur de liens

Le compilateur C utilisé sous Linux est le gcc (Gnu Compiler Collection).:


• L’éditeur de texte,
• Le compilateur, qui permet de passer d’un fichier source à un
fichier objet,
• L’éditeur de liens, qui assure le regroupement des fichiers objet
provenant des différents modules et les associe avec les
bibliothèques utilisées pour l’application. Nous obtenons ici un
fichier exécutable.

Programmation Système 28

28

28
La programmation C

Sous le shell on écrit le programme avec un éditeur de texte (vi


exemple) et on l’enregistre avec l’extension .c. Exemple teste.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.

int main (int argc, char *argv[], char *envp[]);

int argc (vient de "argument count") : Nombre d'arguments passés


au programme ; y compris le nom du programme lui-même.
char *argv[](vient de "argument value") : Pointeur vers un tableau
de pointeurs, chacun de ceux-ci pointant vers un tableau de
caractères au format chaîne. De plus, argv[0] contiendra le nom du
programme lui-même. Enfin, ce tableau contiendra un pointeur
supplémentaire de valeur nulle. On remarquera que cette valeur
nulle se trouve dans la variable argv[argc].

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.

int main (int argc, char *argv[ ], char *envp[ ]);

char *envp[ ] : (vient de "environment program"). De même format


que "argv", cette variable pointe vers un tableau de chaînes de
caractères. Chaque chaîne contiendra la valeur de chaque variable
d'environnement qui a été exportée par un des processus ascendants
et donc accessible au programme. Cette chaîne est de la forme
variable=valeur. De même que pour "argv", un dernier pointeur de
ce tableau a pour valeur "zéro".
Valeur renvoyée (int) : un entier utilisable par le processus appelant
le programme (récupéré par $ ? en shell).
Programmation Système 41

41

41
Les paramètres de "main()" (Exemple)

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


{
printf("Je me nomme %s et on m'a envoyé %d
paramètres\n", argv[0], argc – 1);
return(0);
}

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 :

Lorsqu'on lance une commande dans la console, le système va


chercher l'exécutable dans les chemins donnés dans le PATH.
Pour le PATH donné en exemple, le noyau ira chercher
l'exécutable dans /usr/local/sbin, puis dans /usr/local/bin, etc... En
conséquence, si deux commandes portent le même nom, c'est la
première trouvée qui sera exécutée.
Programmation Système 44

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 :

La commande suivante permet d’afficher les variables


d'environnement :
# env 46
Programmation Système

46

46
L'environnement

Ce tableau contient des chaînes de caractères se terminant par


NULL, et lui-même se termine par un pointeur nul. Un petit schéma
peut-être ?

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

Voici une explication des variables d'environnement couramment


utilisées :

HOME : contient le répertoire personnel de l'utilisateur ;


PATH : contient une série de chemin vers des répertoires qui
contiennent des exécutables ;
PWD : contient le répertoire de travail ;
USER (ou LOGNAME) : nom de l'utilisateur ;
TERM : type de terminal utilisé ;
SHELL : shell de connexion utilisé.

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

Explication des paramètres :


char *string : Chaîne contenant la variable dont on veut obtenir la
valeur

Valeur renvoyée (char*) : un pointeur sur une zone mémoire


statique contenant la chaîne correspondant à la variable demandée
si celle-ci existe

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

Créez un programme permettant de rechercher


les variables d'environnement passées en
paramètre de la fonction main.

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)

Pour connaître son propre identifiant PID, on utilise l’appel-système


getpid(), qui ne prend pas d’argument et renvoie une valeur de type
pid_t. Il s’agit, bien entendu, du PID du processus appelant. Cet appel-
système, déclaré dans <unistd.h> :
pid_t getpid(void);
Ce numéro de PID est celui que nous avons vu affiché en première
colonne de la commande ps.
Sous Linux, le type pid_t est un entier sur 64 bits, mais ce n’est pas le
cas pour tous les Unix.
Pour assurer une bonne portabilité lors de l’affichage d’un PID, nous
utiliserons la conversion %ld de printf(), et nous ferons explicitement
une conversion de type en long int ainsi :
fprintf(stdout, "Mon PID est : %ld\n", (long) getpid());

Programmation Système 60

60

60
Présentation des processus (suite)

La distinction entre processus père et fils peut se faire directement


au retour de l’appel fork().
Celui-ci, en effet, renvoie une valeur de type pid_t, qui vaut zéro si on
se trouve dans le processus fils, est négative en cas d’erreur, et
correspond au PID du fils si on se trouve dans le processus père.
Voici en effet un point important : dans la plupart des applications
courantes, la création d’un processus fils a pour but de faire dialoguer
deux parties indépendante du programme (à l’aide de signaux, de tubes,
de mémoire partagée…). Le processus fils peut aisément accéder au
PID de son père (noté PPID pour Parent PID) grâce à l’appel-système
getppid(), déclaré dans <unistd.h> :
pid_t getppid(void);
Cette routine se comporte comme getpid(), mais renvoie le PID du père
du processus appelant.
En revanche, le processus père ne peut connaître le numéro du nouveau
processus créé qu’au moment du retour du fork().

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.

pid_t wait(int *);


// attend la fin de n'importe quel fils
// int * : valeur de retour passée à exit par le fils
pid_t waitpid(pid_t, int *, int);
// attend la fin d'un processus donné
// le 3e paramètre permet de passer des flags

wait est bloquante mais retourne immédiatement si le fils a terminé son


exécution ou est devenu zombie.
waitpid ne bloque pas avec l'option WNOHANG

Remarque : Le processus père tourne sans arrêt. Quand il lance un fils,


il récupère un int status.
Programmation Système 67

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

➢ Un processus qui meurt devient un zombie


jusqu'au wait.
– Si le père meurt avant son fils, il est adopté par
un autre processus (celui de PID 1 en général).
– Si le père n'attend pas la mort de son fils, alors
son fils reste un zombie.
➢ Sur certains systèmes d'exploitation, les
zombies ne peuvent pas être supprimés, il
faut donc y faire attention.
➢ Si un père a de nombreux fils, il vaut mieux
utiliser waitpid() qui permet d'attendre un
fils en particulier.

Programmation Système 71

71

71
Rendre les fils autonomes

➢ Une méthode pour dissocier père et fils :


– Le processus original fait un fork()
– Le fils fait un fork() à son tour
– Le fils meurt avec exit()

➢ Le processus original et le petit fils sont


alors indépendants :
– Pas besoin d'attendre la mort du petit fils
– Pas besoin de s'inquiéter quand cela arrive.

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.

exec* ne retourne jamais. Il en existe plusieurs variantes


(execlp, execve...) pour lesquelles il faut combiner les lettres
suivantes :
v : si des tableaux sont passés en argument
l : si des listes sont passées
p : si seul le nom de fichier est donné
e : si l'env est passé en argument

Programmation Système 77

77

77
Les primitives exec()

Il s’agit d’une famille de primitives permettant le lancement de l’exécution d’un


programme externe. Il n’y a pas création d’un nouveau processus, mais
simplement changement de programme.
Il y a six primitives exec() que l’on peut répartir dans deux groupes : les execl(),
pour lesquels le nombre des arguments du programme lancé est connu, puis les
execv() où il ne l’est pas. En outre toutes ces primitives se distinguent par le type
et le nombre de paramètres passés.
Premier groupe d’exec(). Les arguments sont passés sous forme de liste :
int execl(char *path, char *arg0, char *arg1,..., char *argn,NULL)
/* exécute un programme */
/* path : chemin du fichier programme */
/* arg0 : premier argument */
/* ... */
/* argn : (n+1)ième argument */
int execle(char *path,char *arg0,char *arg1,...,char *argn,NULL,char *envp[])
/* envp : pointeur sur l’environnement */
int execlp(char *file, char *arg0, char *arg1,...,char *argn,NULL)
Les tableaux argv[ ] et envp[ ] doivent se terminer par des pointeurs NULL.
Programmation Système 78

78

78
Les primitives execl(),execle() et execlp()

int execl(char *path, char *arg0, char *arg1,..., char *argn, NULL)

int execle(char *path,char *arg0,char *arg1,...,char *argn,NULL,char *envp[])

int execlp(char *file, 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é.

Exemple : execlp("ls", "ls", "*.c","/ ", NULL) ;


Programmation Système 79

79

79
Afficher la date avec le fils

L'exécution d'une commande telle que "date" sous Unix agit


de cette manière :

✓ le shell est dupliqué,


✓ la copie du shell père est transformé en la commande
date,
✓ date termine et le shell principal reprend la main.

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

int execve(char *path, char *argv[], char *envp[]);


// path : chemin vers exécutable
// argv : arguments, se terminant par NULL
// envp : variables système (forme var=val)

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

execlp( cmd[i], cmd[i], (char *)0 );


printf( “execlp failed\n” );
}
Programmation Système 85

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

fork() fork() returns pid=0 et


produit un clone du
Retourne un programme parent jusqu’à
nouveau PID ce que execv est exécuté

Processus originale Nouvelle


Nouveau programme
Continue copie du
(replacement)
père

execv(nouveau programme)
Programmation Système 87

87

87
wait()
➢ #include <sys/types.h>
#include <sys/wait.h>
pid_t wait(int *statloc);

➢ Suspend le processus appelant jusqu'à ce que


l'enfant ait fini. Retourne l'identifiant du processus
fils terminé si ok, -1 on error.

➢ statloc peut être (int *) 0 ou une variable qui


sera lié aux informations de statut. à propos de
l'enfant.

Programmation Système 88

88

88
wait() Actions

➢ Un processus qui appelle wait () peut :


– suspendre (bloc) si tous ses enfants sont encore
en cours d'exécution, ou

– Retourner immédiatement avec le statut final


d'un enfant, ou

– Retourner immédiatement avec une erreur si il


n'y a pas de processus enfants.

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

Programmation Système continued90


90

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 :

int open(char *nomfic, int mode);


Programmation Système 94

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 */);

La fonction open() prend en premier argument le nom d’un fichier à


ouvrir.
Le second argument est une combinaison de plusieurs éléments
assemblés par un OU binaire c.à.d. :( | ), permet de définir comment
on utilisera le fichier.
Tout d’abord, il faut impérativement utiliser l’une des trois constantes
suivantes :
• O_RDONLY : fichier ouvert en lecture seule ;
• O_WRONLY : fichier ouvert en écriture seule ;
• O_RDWR : fichier ouvert à la fois en lecture et en écriture.

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

Le 3° argument (mode) : prend les valeurs S_IREAD | S_IWRITE |


S_EXEC declarés dans stat.h .
Programmation Système 98

98

98
Quelques valeurs pour le mode

Les constantes symboliques ci-dessous donne un exemple de valeurs pour


le mode des fichiers :
S_IRWXU 00700 user (file owner) has read, write and execute
permission
S_IRUSR 00400 user has read permission
S_IWUSR 00200 user has write permission
S_IXUSR 00100 user has execute permission
S_IRWXG 00070 group has read, write and execute permission
S_IRGRP 00040 group has read permission
S_IWGRP 00020 group has write permission
S_IXGRP 00010 group has execute permission
S_IRWXO 00007 others have read, write and execute permission
S_IROTH 00004 others have read permission
S_IWOTH 00002 others have write permission
S_IXOTH 00001 others have execute permission
Programmation Système 99

99

99
Pour les fonctions que nous avons étudiées, il faut inclure les
fichiers suivants :

Il est donc conseillé d’inclure systématiquement ces quatre


fichiers en début de programme pour pouvoir utiliser les
descripteurs avec le maximum de portabilité.

Programmation Système 100

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.

Programmation Système 102

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 */
};

Programmation Système 103

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

st_mtime : est mis à jour lorsque le contenu du fichier


change.
st_ctime : est mis à jour quand les métadonnées du fichier
(propriétaire, groupe, permissions, nombre de liens, etc.)
changements.

Programmation Système 107

107

107
Ecriture dans un fichier
brute via l’appel système
write()

Programmation Système 108

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.

On peut ensuite, suivant le mode d'ouverture, soit lire soit écrire un


bloc (l'opération est alors directement effectuée sur disque) :

ssize_t write (int fd, const void * buf, size_t count);

Rapide petite description :


- int fd : Notre File Descriptor, la valeur renvoyée par open
- const void * buf : Les données à écrire.
- size_t count : La taille des données à écrire (en octets).
write() renvoie le nombre d'octets écrits ou -1 si il échoue.
Programmation Système 109

109

109
Exercice

Ecrire un programme en c pour créer un fichier texte


qui contient les 26 lettres de l'alphabet !

Programmation Système 110

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.

Programmation Système 112

112

112
Exercice

Ecrire un programme en c pour lire un fichier texte !

Programmation Système 113

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

Programmation Système 114

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

Programmation Système 115

115

115
Exercice

Ecrire un programme en c pour copier un fichier source


dans un fichier destination !

Programmation Système 116

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

Programmation Système 117

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

Programmation Système 118

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 :

long lseek(int df, long combien, int code);

déplace le pointeur de fichier de combien octets, à partir de : début


du fichier si code=0, position actuelle si 1, fin du fichier si 2. La
fonction retourne la position atteinte (en nb d'octets), -1 si erreur.

long filelength(int df);

rend la taille d'un fichier (sans déplacer le pointeur de fichier).


Programmation Système 119

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)

Cette fonction attend 2 paramètres :


* Le nom du fichier à ouvrir.
* Le mode d'ouverture du fichier.
Cette fonction renvoie un pointeur sur FILE
Programmation Système 120

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

Programmation Système 121

121

121
fopen()
#include <stdio.h>
#include <stdlib.h>

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


{
FILE* fichier = NULL;

fichier = fopen("test.txt", "r+");

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

Programmation Système 123

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 :

- fputc : écrit un caractère dans le fichier (UN SEUL caractère à


la fois).
- fputs : écrit une chaîne dans le fichier
- fprintf : écrit une chaîne "formatée" dans le fichier,
fonctionnement quasi-identique à printf

Programmation Système 124

124

124
fputc()
int main(int argc, char *argv[])
{
FILE* fichier = NULL;

fichier = fopen("test.txt", "w");

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;

fichier = fopen("test.txt", "w");

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 :

- fgetc : lit un caractère


- fgets : lit une chaîne
- fscanf : lit une chaîne formatée

Programmation Système 128

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

Programmation Système 129

129

129
fgets()
#define TAILLE_MAX 1000 // Tableau de taille 1000

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


{
FILE* fichier = NULL;
char chaine[TAILLE_MAX] = ""; // Chaîne vide de taille TAILLE_MAX

fichier = fopen("test.txt", "r");


if (fichier != NULL)
{
fgets(chaine, TAILLE_MAX, fichier); // On lit maximum TAILLE_MAX
caractères du fichier, on stocke le tout dans "chaine"
printf("%s", chaine); // On affiche la chaîne
fclose(fichier);
}
return 0;
}

Programmation Système 130

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

fichier = fopen("test.txt", "r");

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 :

- ftell : indique à quelle position vous êtes actuellement dans le


fichier
- fseek : positionne le curseur à un endroit précis
- rewind : remet le curseur au début du fichier (c'est équivalent
à demander à la fonction fseek de positionner le curseur au
début).

Programmation Système 133

133

133
ftell
Cette fonction est très simple à utiliser. Elle renvoie la position
actuelle du curseur sous la forme d'un long :

long ftell(FILE* pointeurSurFichier);

Le nombre renvoyé indique donc la position du curseur dans le


fichier.

Programmation Système 134

134

134
Le prototype de fseek est le suivant :
fseek
int fseek(FILE* pointeurSurFichier, long deplacement, int origine);

La fonction fseek permet de déplacer le "curseur" d'un certain


nombre de caractères (indiqué par déplacement) à partir de la
position indiquée par origine.
• Le nombre déplacement peut être un nombre positif (pour se
déplacer en avant), nul (= 0) ou négatif (pour se déplacer en
arrière).
• Quant au nombre origine, vous pouvez mettre comme valeur
l'une des 3 constantes (généralement des defines) listées ci-
dessous :
o SEEK_SET : indique le début du fichier.
o SEEK_CUR : indique la position actuelle du curseur.
o SEEK_END : indique la fin du fichier.
Programmation Système 135

135

135
fseek
# Le code suivant place le curseur 2 caractères après le début :

fseek(fichier, 2, SEEK_SET);

# Le code suivant place le curseur 4 caractères avant la position


courante :

fseek(fichier, -4, SEEK_CUR);

(remarquez que déplacement est négatif car on se déplace en arrière)


# Le code suivant place le curseur à la fin du fichier :

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 :

void rewind(FILE* pointeurSurFichier);

Programmation Système 137

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

La particularité de ces fonctions est qu'elles ne nécessitent pas de


pointeur de fichier pour fonctionner. Il suffira juste d'indiquer le nom
du fichier à renommer / supprimer.

Programmation Système 138

138

138
rename
int rename(const char* ancienNom, const char* nouveauNom);

Exemple

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


{
rename("test.txt", "test_renomme.txt");

return 0;
}

Programmation Système 139

139

139
remove
int remove(const char* fichierASupprimer);

Exemple

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


{
remove("test.txt");

return 0;
}

Programmation Système 140

140

140
Le standard POSIX

Au début des années 1990, Unix s’était morcelé en un certain


nombre de systèmes semblables mais incompatibles. L’écriture
d’un programme portable entre les différents dialectes d’Unix
était devenue un exercice inutilement excitant.
Le standard IEEE 1003, communément appelé POSIX (Portable
Operating System Interface) vise à créer un ensemble commun de
fonctions disponibles sur tous les systèmes Unix.

Programmation Système 141

141

141
Fonctions utiles

Fonctions norme POSIX


open, read, write, stat, pipe, mkfifo, dup, dup2, fcntl,...
descripteur = int

Fonctions de la librairie C (surcouche)


fopen, fread, fwrite, opendir, readdir,...
descripteur = FILE *

Faire un : man syscalls pour lister les 150 appels disponibles

Programmation Système 142

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.

Programmation Système 143

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.

C'est cela que nous allons traiter dans cette partie.

Programmation Système 144

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

Il y a deux types de tubes :


➢ tube "mémoire" ou "ordinaire" ou "ordinaire" : est un
fichier particulier qui permet de transférer des données entre
processus. Il est créé dans la mémoire du processus qui les
génèrent. Cela implique que les deux processus qui
communiqueront via ce système soient descendants d'un même
processus créateur (fork()) et que le tube ait été créé avant la
duplication pour que les deux processus le connaissent.

➢ les tubes "nommés" : ils correspondent à un fichier (inode)


dans lequel les processus pourront lire et écrire. Cela implique
opération sur périphérique mais deux processus indépendants
pourront se transmettre des informations.

Programmation Système 146

146

146
Programmation Système 147

147

147
Les tubes ordinaires (anonymes)

Un tube est matérialisé par deux entrées de la table des ouvertures


de fichiers, une de ces entrées est ouverte en écriture (l’entrée du
tube), l’autre en lecture (la sortie du tube).
Ces deux entrées de la table des fichiers ouverts nous donnent le
nombre de descripteurs qui pointent sur elles. Ces valeurs peuvent
être traduites comme :
nombre de lecteurs = nombre de descripteurs associés à l’entrée
ouverte en lecture. On ne peut pas écrire dans un tube sans lecteur.
nombre d’écrivains = nombre de descripteurs associés `a l’entrée
ouverte en écriture. La nullité
de ce nombre définit le comportement de la primitive read lorsque le
tube est vide.

Programmation Système 148

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"

La primitive pipe() a pour fonction de créer le tube en mémoire et


ouvrir le tube créé (donc, la primitive open() est inutile dans un pipe
mémoire).
#include <unistd.h>
int pipe (int fd[2]);

Explication des paramètres :


int fd[2] : pointeur vers un tableau de deux entiers qui seront remplis
par la fonction. A la fin de l'exécution de celle-ci, on aura :
➢ fd[0] contiendra un descripteur permettant l'accès au tube en
lecture
➢ fd[1] contiendra un descripteur permettant l'accès au tube en
écriture

Programmation Système 150

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).

Programmation Système 151

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

/* write into the pipe */


w =write(fd[1], "Hello World\n", strlen("Hello World\n"));

/* read the pipe and print the read value */


r= read(fd[0], buffer, BUFSIZ);
printf("%s", buffer);
}
Programmation Système 152

152

152
Création d'un pipe dans un processus ayant un fils

La différence avec l'exemple précédent est que, en plus de créer un


pipe, notre processus crée un fils. Le pipe est alors automatiquement
partagé entre le père et le fils.
Si l'un écrit dans le pipe alors on ne sait pas lequel des deux va
recevoir l'information. Ceci peut donner des résultats inattendus.

Programmation Système 153

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]”).

Programmation Système 155

155

155
Exercice

Réécrire le programme de l’exemple1 en utilisant un


tube anonyme entre père et fils pour envoyer le message
"HelloWorld" du père vers le fils

Programmation Système 156

156

156
Exemple 3 (1/3)
#include <stdio.h>
#include <memory.h>
#include <unistd.h>

int main( int argc, char ** argv )


{
/* create the pipe */
int pfd[2];
if (pipe(pfd) == -1)
{
printf("pipe failed\n");
return 1;
}

Programmation Système 157

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

close(pfd[0]); /* close read side */

/* send some data into the pipe */


strcpy(buffer, "HelloWorld\n");
write(pfd[1], buffer, strlen(buffer)+1);

close(pfd[1]); /* close the pipe */


}

return 0;
}
Programmation Système 159

159

159
Les tubes nommés par défaut

Chaque processus possède à sa création trois pipes nommés stdin,


stdout, et stderr.
Ces trois pipes sont par défaut créés dans chaque processus. Le
premier, stdin, est branché par défaut sur l'entrée clavier tandis que
stdout et stderr sont eux branchés sur la sortie écran. Des descripteurs
de fichiers par défaut leur sont associés : 0 pour stdin, 1 pour stdout, et
2 pour stderr.

Programmation Système 160

160

160
Les tubes nommés par défaut

Maintenant que l'on connaît le principe de communication par pipe,


on est tenté de connecter ces pipes entre eux.
Par exemple essayons de connecter stdout d'un premier processus
avec le stdin d'un second. Cette opération est effectuée par le shell à
chaque fois que deux commandes séparées par un pipe "|" sont
exécutées, par exemple pour relier la sortie stdout de la commande
ls avec l'entrée stdin de la commande wc, il faut taper ceci dans un
terminal :
ls | wc
Pour réaliser ceci dans un programme en langage C, il faut procéder
en plusieurs étapes.
Il faut commencer par créer un pipe vide : “fd=3” en lecture et
“fd=4” en écriture. On utilise donc la fonction “pipe(fd)” comme
vue ci dessus.
Programmation Système 161

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)

dup() utilise le plus petit numéro inutilisé pour le nouveau


descripteur.

dup2() transforme newfd en une copie de oldfd, fermant auparavant


newfd si besoin.

VALEUR RENVOYÉE
dup() et dup2() renvoient le nouveau descripteur, ou -1 s'ils échouent,
auquel cas errno contient le code d'erreur.

Programmation Système 163

163

163
Exemple pour Dupliquer un descripteur de fichier

Programmation Système 164

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

Programmation Système 165

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.

Programmation Système 167

167

167
Programmation Système 168

168

168
Programme c : (1/3)
#include <stdio.h>
#include <memory.h>
#include <unistd.h>

int main( int argc, char ** argv )


{
/* create the pipe */
int pfd[2];
if (pipe(pfd) == -1)
{
printf("pipe failed\n");
return 1;
}

Programmation Système 169

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

Programmation Système 171

171

171
Les tubes nommés

D'un point de vue purement visuel, un tube nommé est un simple


nœud dans le système de fichiers. Lorsqu'on ouvre ce nœud, le
kernel crée un canal de communication en mémoire. Après la
création du nœud dans le système de fichiers, les applications
clientes et serveurs peuvent communiquer, utilisant ainsi le tube
comme point de rendez-vous.
De plus, il n'est pas nécessaire que les applications soient
exécutées par le même utilisateur.

$ mkfifo tube_test
$ ls -l

tube_test prw-r--r-- 1 root root 0 Dec 17 16:17 tube_test

Programmation Système 172

172

172
Les tubes nommés

Lancez une commande générant une sortie continu de texte et


renvoyez la vers le tube
$ ping 127.0.0.1 > test_tube

Rien n'apparait à l'écran comme de bien entendu.


Sur une autre console ou dans un autre xterm, faites un cat du tube :

$ cat test_tube PING 127.0.0.1 (127.0.0.1): 56 data bytes


64 bytes from 127.0.0.1: icmp_seq=0 ttl=255 time=0.384 ms
64 bytes from 127.0.0.1: icmp_seq=1 ttl=255 time=0.188 ms
64 bytes from 127.0.0.1: icmp_seq=2 ttl=255 time=0.181 ms
64 bytes from 127.0.0.1: icmp_seq=3 ttl=255 time=0.187 ms
64 bytes from 127.0.0.1: icmp_seq=4 ttl=255 time=0.184 ms
64 bytes from 127.0.0.1: icmp_seq=5 ttl=255 time=0.187 ms
64 bytes from 127.0.0.1: icmp_seq=6 ttl=255 time=0.175 ms
Programmation Système 173

173

173
Créer les tubes nommés

Avant de pouvoir utiliser un tube nommé, encore faut-il qu'il


soit créé.
La fonction mkfifo réalise ce travail pour nous. Celle-ci
accepte deux arguments, le premier est le chemin ou se trouve
le tube dans l'arborescence du système de fichiers et le second
définit le mode (permissions d'accès, identiques à celle d'un
fichier classique) :
#include <sys/types.h>
#include <sys/stat.h>
int main (void)
{ char *nom_tube = "tube_test2";
mkfifo (nom_tube, 0644);
return (0);
}
Programmation Système 174

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.

int mkfifo(const char *ref, mode_t mode);

mode est construit comme le paramètre de mode de la fonction open.


En POSIX, un appel simplifié :
#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *ref, mode_t mode);

L’ouverture d’un tube nommé se fait exclusivement soit en mode


O_RDONLY soit en mode O_WRONLY.
Programmation Système 176

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

Programmation Système 177

177

177
/* Processus lecteur */
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
main()
{int tub;
char buf[11];

tub = open(“fictub”,O_RDONLY) /* ouverture fichier */


read (tub,buf,10); /* lecture du fichier */
buf[11]=0;
printf(“J’ai lu %s\n, buf);
close (tub);
exit(0); }
Programmation Système 178

178

178
Exercice

Ecrire un programme C équivalent à la


commande shell:

# cat /etc/passwd | grep 500 | cut -c 1-255

Programmation Système 179

179

179
Les signaux

Programmation Système 180

180

180
Les signaux

Les signaux, sont des événements externes qui changent le


déroulement d’un programme de manière asynchrone, c’est-à-dire à
n’importe quel instant lors de l’exécution du programme.
En ceci les signaux s’opposent aux autres formes de
communications où les programmes doivent explicitement
demander à recevoir les messages externes en attente, par exemple
en faisant une lecture sur un tube.
Les signaux transportent peu d’information (le type du signal et
rien d’autre) et n’ont pas été conçus pour communiquer entre
processus mais pour permettre à un processus de recevoir des
informations atomiques sur l’évolution de l’environnement
extérieur (l’état du système ou d’autres processus).

Programmation Système 181

181

181
Les signaux

Des événements extérieurs ou intérieurs au processus peuvent


survenir à tout moment et provoquer une interruption de
l'exécution du processus en cours. Ces événements peuvent être
d'origine "physique" (coupure de courant, lecteur non prêt, etc.) ou
logique.
• Un signal (ou interruption logicielle) est un message très court
qui permet d’interrompre un processus pour exécuter une autre
tâche.
• Il permet d’avertir (informer) un processus qu’un événement
(particulier, exceptionnel, important, …) c’est produit.
Lors de la réception d'une interruption, le noyau reconnaît l'origine
de celle-ci, sauve le contexte du processus actuel et provoque
l'exécution d'une routine appropriée handler.

Programmation Système 182

182

182
Les signaux

La technique des signaux est utilisée par le noyau pour prévenir un


processus de l'existence d'un événement extérieur le concernant. Elle
correspond à l'utilisation, dans d'autres systèmes d'exploitation, du
principe des interruptions logicielles.
L'origine d'un signal peut être variée, elle peut être d'origine
matérielle, système ou logicielle. Ce peut être :
✓ la fin d'exécution d'un processus fils
✓ l'utilisation d'une instruction illégale dans le programme
✓ la réception d'un signal d'alarme
✓ une demande d'arrêt de la part du processus
✓ etc.
Ils peuvent se produire à tout moment du programme.
les signaux indiquant une interaction avec l’environnement (action sur
le terminal, connexion réseau…) ont une nature fortement
asynchrone.
Programmation Système 183

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.

Programmation Système 184

184

184
Principe

Le principe est a priori simple : un processus peut envoyer sous


certaines conditions un signal à un autre processus (ou à lui-même). Un
signal peut être imaginé comme une sorte d’impulsion qui oblige le
processus cible à prendre immédiatement (aux délais dus à
l’ordonnancement près) une mesure typique.
Le destinataire peut soit ignorer le signal, soit le capturer – c’est-à-dire
dérouter provisoirement son exécution vers une routine particulière
qu’on nomme gestionnaire de signaux –, soit laisser le système traiter le
signal avec un comportement par défaut.
Il existe un nombre déterminé de signaux (32 sous Linux 2.0, 64 depuis
Linux 2.2). Chaque signal dispose d’un nom défini sous forme de
constante symbolique commençant par SIG et d’un numéro associé.
Toutes les définition concernant les signaux se trouvent dans le fichier
d’en-tête <signal.h> (ou dans d’autres fichiers qu’il inclut lui-même).

Programmation Système 185

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

Programmation Système 186

186

186
Liste des signaux
Nom N° Rôle Core Ignoré

SIGHUP 1 émis au processus attaché à un terminal, lorsque celui-ci est déconnecté


(HangUP)
SIGINT 2 émis au processus attaché au terminal sur lequel on frappe <DEL> ou
<SUPPR>
SIGQUIT 3 idem pour la touche <QUIT> X
SIGILL 4 envoyé au processus qui tente d’exécuter une instruction illégale X

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

SIGPIPE 13 émis lors de l’écriture dans un pipe sans lecteur

SIGALRM 14 signal associé à l’horloge système


SIGTERM 15 signal de fin normale d’un processus
SIGUSR1 16 offert aux utilisateurs
SIGUSR2 17 idem
SIGCHLD 18 émis vers le père à la mort du fils X
SIGPWR 19 émis lors d’une coupure de courant X
SIGPOLL 22 événement sélectionné X
Programmation Système 187

187

187
Liste des signaux

Il y a plusieurs types de signaux, indiquant chacun une condition


particulière. En voici quelques-uns, avec le comportement par
défaut associé:
Nom Signification Comportement
sighup Hang-up (fin de connexion) Terminaison
sigint Interruption (ctrl-C) Terminaison
sigquit Interruption forte (ctrl-\) Terminaison + core dump
sigfpe Erreur arithmétique (division par zéro) Terminaison + core dump

sigkill Interruption très forte (ne peut être ignorée) Terminaison

sigsegv Violation des protections mémoire Terminaison + core dump


sigpipe Écriture sur un tuyau sans lecteurs Terminaison
sigalrm Interruption d’horloge Ignoré

sigtstp Arrêt temporaire d’un processus (ctrl-Z) Suspension

sigcont Redémarrage d’un processus arrêté Ignoré


Un des processus fils est mort ou a été
sigchld Ignoré
arrêté

Programmation Système 188

188

188
Les signaux CTRL-c et CTRL-z

• Lorsque vous tapez CTRL-C, vous dites au shell d'envoyer le


signal INT (pour « interrompre ») au job actuel. Vous pouvez
également envoyer au job actuel un signal QUIT en tapant
CTRL-\ (contrôle-backslash); c'est un peu comme une version
«plus forte» de [CTRL-C]. Vous utiliseriez normalement
[CTRL-\] quand (et seulement quand) [CTRL-C] ne fonctionne
pas.
• [CTRL-Z] envoie TSTP (sur la plupart des systèmes, pour
"arrêt terminal").

Programmation Système 189

189

189
Exemple des signaux CTRL-c et CTRL-z

Constat : le signal d’interruption n’est traité que lorsque le


processus cat redevient actif, i.e., il est disponible pour être exécuté.
Déduction : l’information que le signal 2 a été émis est
m´mémorisée par le système.
Programmation Système 190

190

190
Core et Ignore

core : X, ces signaux génèrent un fichier core


Certains signaux génèrent la production d'une image du contexte du
processus lors de son interruption. Cette image est appelée core. Un
message "core dumped" est alors envoyé sur l'écran par le système :
et un fichier core est créé dans le répertoire de travail. Il peut être lu
avec certains outils (débuggeur) pour essayer de comprendre ce qui a
pu générer l'interruption.

ignoré : X, ces signaux sont ignorés par défaut


Chaque système UNIX soutient un nombre différent de signaux. De
plus les numéros n'ont pas la même signification. Il n'y a que les
constantes prédéfinies qui soient portables

Programmation Système 191

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)

A : mort du processus B : signal ignoré C : mort du processus +


core
D : arrêter le processus E : Le signal ne peut pas être intercepté.
F : Le signal ne peut pas être ignoré.

Programmation Système 192

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)

Programmation Système 193

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.

Programmation Système 194


signaux
194

194
Signaux sous Unix

Sur les systèmes supportant les signaux temps-réel, comme Linux, il


existe deux valeurs supplémentaires importantes : SIGRTMIN et
SIGRTMAX. Il s’agit des numéros du plus petit et du plus grand
signal temps-réel. Ces derniers en effet s’étendent sur une plage
continue en dessous de NSIG-1. Sous Linux, l’organisation des
signaux est la suivante :

Programmation Système 195

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

Programmation Système 196

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

Programmation Système 197

197

197
Fonctionnement d'un signal

Envoi d'un signal :


Un signal peut être envoyé par le noyau ou un processus. Ce signal
est mémorisé dans un champ de la table proc du processus récepteur
(on dit que c'est un signal pendant) : chaque signal active un bit
particulier de ce champ.
Réception d'un signal :
En règle générale, la réception d'un signal provoque l'arrêt de
l'exécution d'un processus avec éventuellement génération d'une
image mémoire core et se traduit par l'exécution de la fonction
handler associée.

Programmation Système 198

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

Programmation Système 199

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 :

int kill (pid_t pid, int numero_signal);


Le premier argument correspond au PID du processus visé, et le second au
numéro du signal à envoyer. Rappelons qu’il est essentiel d’utiliser la constante
symbolique correspondant au nom du signal et non la valeur numérique directe.
Il existe une application /bin/kill qui sert de frontal en ligne de commande à
l’appel système kill(). /bin/kill prend en option sur la ligne de commande un
numéro de signal précédé d’un tiret « - » et suivi du ou des PID des processus à
tuer. Le numéro de signal peut être remplacé par le nom symbolique du signal,
avec ou sans le préfixe SIG.
Par exemple, sur Linux version x86, kill -13 1234 est équivalent à kill -
PIPE 1234. Si on ne précise pas de numéro de signal, /bin/kill utilise
SIGTERM par défaut.
Programmation Système 200

200

200
Fonctions de gestions des signaux

La primitive kill() envoie un signal à un processus. Elle prend en


paramètre l'identificateur du processus destinataire et le numéro du
signal. Si la valeur PID=0 est utilisée, le signal est envoyé à tous les
processus du groupe.
#include <signal.h>
int kill (int pid, int sig);
Explication des paramètres :
int pid : numéro du processus où sera envoyé le signal. Ce numéro
peut avoir quatre groupe de valeurs :
➢ Une valeur n positive enverra le signal au processus de pid "n"
➢ La valeur 0 enverra le signal à tous les processus du même
groupe que le processus appelant

Programmation Système 201

201

201
Intérêt du signal numéro 0

Nous voyons ici l’intérêt du signal numéro 0. Il ne s’agit pas vraiment


d’un signal – rien n’est émis –, mais il permet de savoir si un
processus est présent sur le système. Si c’est le cas, l’appel système
kill() réussira si on peut atteindre le processus par un signal, ou il
échouera avec EPERM si on n’en a pas le droit. Si le processus
n’existe pas, il échouera avec ESRCH. Voici par exemple un moyen
de l’employer

int processus_present (pid_t pid)


{
if (kill(pid, 0) == 0) return VRAI;
if (errno == EPERM) return VRAI;
return FAUX;
}
Programmation Système 202

202

202
Linux Error Codes

The perror tool can be used to find the error message which
is associated with a given error code.

Number Error Code Description


Operation not
1 EPERM
permitted
No such file or
2 ENOENT
directory
3 ESRCH No such process
Interrupted system
4 EINTR
call
5 EIO I/O error
No such device or
6 ENXIO
address

https://mariadb.com/kb/en/library/operating-system-error-codes/
Programmation Système 203

203

203
Fonctions de gestions des signaux

➢ La valeur –1 enverra le signal à tous les processus tournant sur


la machine (sauf le premier "init") par ordre de pid décroissant
➢ Une valeur n négative autre que –1 enverra le signal à tous les
processus du même groupe que le processus de pid "-n"
int sig : numéro du signal à envoyer. La valeur 0 est utilisée pour
vérifier que le processus existe.

Valeur renvoyée (int) : 0

Programmation Système 204

204

204
Appel système signal()

L’appel système signal() permet de prémunir un processus contre


l'effet principal de la réception d'un signal quelconque qui est
d'arrêter son exécution. Cette fonction ira installer dans une zone
particulière correspondant au signal attendu une adresse de
déroutement (fonction).
#include <signal.h>
int signal (int sig, void (*pt_fonc)(int));

Explication des paramètres :


int sig : numéro du signal devant être reçu
void (*pt_fonc)(int) : référence (adresse) de la fonction qui sera
exécutée lors de la réception dudit signal. Cette fonction, à charge du
programmeur, doit impérativement être prévue pour recevoir un int
(qui sera, dans la fonction, le signal reçu) et ne doit rien renvoyer
(void).
Programmation Système 205

205

205
Appel système signal()

Deux valeurs particulières peuvent êtres utilisées pour ce paramètre :


- SIG_IGN : le signal est purement et simplement ignoré (mais
il est quand même acquitté). Ce paramètre est utile si la
réception d'un signal ne donne pas lieu à l'appel d'une
fonction particulière
- SIG_DFL : restitue au processus son comportement par défaut
(mourir)

Valeur renvoyée (int) : Valeur précédente de déroutement.

Le signal 9 (SIGKILL) ne peut pas être ignoré ni détourné.

Programmation Système 206

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

main() /* Fonction principale */


{
signal(15, trt_sig) /* Détournement du signal 15 */
 /* Code quelconque */
}

Programmation Système 207

207

207
Appel système pause()

la primitive pause() suspend l'exécution du processus qui l'invoque


jusqu'à l'arrivée d'un signal, quel qu'il soit.

Bien entendu, si le processus n'a pas, au préalable, détourné le signal


arrivant, ce processus ne se réveillera que pour mourir.

#include <unistd.h>
int pause (void);

Programmation Système 208

208

208
Appel système alarm ()

la primitive alarm() duplique son code lors de l'appel (tout comme la


primitive fork()) mais le code dupliqué n'est pas accessible au
programmeur :
✓ Le code père (qui a lancé la primitive) rend immédiatement la
main (et peut donc faire autre-chose
✓ Le code fils (qui vient d'être généré) attendra un certain temps
et, lorsque ce temps sera écoulé, enverra au processus père
(processus qui a lancé la primitive) un signal SIGALRM (et
pas un autre) ; puis se terminera proprement

#include <unistd.h>
int alarm (int sec);

Programmation Système 209

209

209
Appel système alarm ()

#include <unistd.h>
int alarm (int sec);

Explication des paramètres :


int sec : temps d'attente du fils

Cette primitive peut-être utilisée lorsqu'on veut prévenir un


processus de manière automatique (temps écoulé, etc.)

Remarque : lancer une seconde fois cette primitive alors que le


temps de la première n'est pas encore écoulé annule le premier
appel.

Programmation Système 210

210

210

Vous aimerez peut-être aussi