5, AG 20215, AG 2021
Processus dans GNU/Linux
2015, AG 2021 2015, AG 2021
Mémoire virtuelle
Composé de 4 segments :
– Code, Données, Pile
– Système
Espace d'adressage continu pour le
processus mais disjoint en mémoire centrale grâce à la mémoire virtuelle
Les 3 premiers segments sont accessibles en mode utilisateur
Le segment système en mode noyau lors de l'exécution des primitives système
5, AG 20215, AG 2021
Fichier binaire
Code Données
Pile
Système exec*
Chargeur
2015, AG 2021 2015, AG 2021
Contient les instructions exécutées par le processus
Accessible en lecture seulement pour permettre :
– la réentrance : partage de code de fonctions de bibliothèque par
exemple
● Tentative d'écriture → Signal → Arrêt du processus
5, AG 20215, AG 2021
Contient toutes les données créées et éventuellement initialisées lors du
chargement du fichier binaire
La durée de vie des ces données est celle du processus
Accessible en lecture/écriture Taille fixe
2015, AG 2021 2015, AG 2021
Contient la pile et le tas
La pile est utilisée lors de l'exécution du processus
Données volatiles : allouées à l'entrée d'un bloc, disparaissant à la fin du bloc
Utilisée pour le passage de paramètres Accessible en lecture/écriture
Taille variable modifié par les différents
appels d'allocation ou libération de mémoire malloc , free
5, AG 20215, AG 2021
Le tas est utilisé pour l'allocation dynamique d'espace lors de l'exécution du processus Primitives système :
– #include <unistd.h>
– int brk(void *addr);
– void *sbrk(intptr_t increment);
Utilisation déconseillée car non portable
2015, AG 2021 2015, AG 2021
Fonctions de bibliothèque : Allouer :
void *malloc(size_t size);
Libérer
void free(void *ptr);
Allocation d'un tableau d'éléments
void *calloc(size_t nmemb, size_t size);
Modification de la taille d'un espace alloué void *realloc(void *ptr, size_t size);
Recopie éventuelle de la zone initiale
5, AG 20215, AG 2021
Contient toutes les informations caractérisant le processus utilisées par le système pour
gérer le processus
N'est pas accessible directement
Un appel de primitive système fait basculer le fonctionnement du processeur en mode privilégié modifiant la manière dont sont calculées les adresses et les droits
Accessible en lecture écriture
2015, AG 2021 2015, AG 2021
int main(){
int erreur;
char * errAlloc="Allocation du tampon impossible\n";
/* Allocation du tampon */
char *tampon= malloc(4096);
if ( tampon == NULL) { perror("Allocation");
write( STDERR_FILENO, errAlloc, strlen(errAlloc));
return EXIT_FAILURE;
}
fprintf(stdout,"La sortie standard\n");
if ( (erreur = open("/tmp/stdout.txt",
O_WRONLY|O_CREAT|O_TRUNC, S_IRUSR|S_IWUSR)) < 0) { perror("Echec de l'ouverture de /tmp/stdout.txt");
return EXIT_FAILURE;
}
if (dup2(erreur, STDOUT_FILENO) < 0 ) { perror("dup2");
return EXIT_FAILURE;
}
close(erreur);
CALL malloc ST R1,p
...
libc pile
tas
errno erreur tampon
système /users/tpreseau HOME
2015, AG 2021 2015, AG 2021
Un fichier exécutable est toujours obtenu par une compilation suivie d'une édition de lien
Un fichier de commande SHELL n'est pas un fichier exécutable
C'est une donnée pour l'exécutable qui est le SHELL ( /bin/sh, /bin/bash, …)
Le droit x permet juste remplacer l'appel sh fichier par fichier
Il en est de même pour tous les langages interprétés, les sources sont lus par
l'interpréteur qui est lui un binaire
5, AG 20215, AG 2021
Fichiers source Compilateur
Editeur de lien
Bibliothèques
Fichiers binaires relatifs à
points externes irrésolus
Fichier binaire relatif à points externes résolus Compilateur Compilateur Compilateur
2015, AG 2021 2015, AG 2021
Un fichier source ne peut être exécuté il faut le transformer en un fichier binaire composé d'instructions binaires exécutables par le
processeur cible
Un changement de processeur implique de changer de compilateur donc de fichier
binaire sauf si les processeurs sont
compatibles : AMD et Intel architecture X86 par exemple
Un binaire de Windows n'est pas exécutable sous GNU/Linux et inversement les modèles d'exécutable et de processus sont différents
5, AG 20215, AG 2021
Le fichier binaire est lu par le chargeur ( loader) qui créé l'image mémoire du processus
Si l'on est pas en mémoire virtuelle le code exécutable est absolutisé pour refléter son implantation en mémoire à partir d'une
adresse donnée
En mémoire virtuelle les adresses virtuelles seront traduites lors de l'exécution des
instructions
Si les bibliothèques sont partagées les liens avec les parties déjà présentes en mémoire
2015, AG 2021 2015, AG 2021
Chargeur
Fichier binaire à points externes résolus
Mémoire Code
Pile
Système Données
5, AG 20215, AG 2021
cc, gcc, c++, g++ ne sont pas des compilateurs
Ce sont des utilitaires permettant d'enchaîner automatiquement les différentes phases de la compilation :
– Pré-traitement : cc -E
– Analyse syntaxique, sémantique
– Génération, optimisation du code : cc - S
– Assemblage : cc -c Édition de lien : ld
2015, AG 2021 2015, AG 2021
Prologue d'un programme C
– Met en forme la ligne de commande en réalité les paramètres de la
primitive exec utilisée pour créer le processus : créé argc, argv, env
– Appelle main
Épilogue du programme
– Exécuté lorsque main exécute return/exit val
– Créé le code de retour du processus, contenant val, retourné au processus père.
5, AG 20215, AG 2021
Fichier source C
Traite toutes les lignes #...
Pré- processeur
Compilateur t.c
Fichier source C
Fichier en langage d'assemblage
Génération du code, optmisation
Analyse syntaxique, sémantique,
Fichiers d'inclusion se trouvant /usr/include, ...
Fichier source C t.h
2015, AG 2021 2015, AG 2021
5, AG 20215, AG 2021
Fichier source
libc Fichier binaire
relatif à
points externes irrésolus
Editeur de lien crtn.o : prologue
crtend.o : épilogue
Fichier binaire relatif à
points externes résolus
2015, AG 2021 2015, AG 2021
5, AG 20215, AG 2021
2015, AG 2021 2015, AG 2021
Structure arborescente des processus
Deux processus sont créés par le noyau lorsqu'il a terminé son initialisation
Processus 0 : swapper Processus 1 : init
Chaque processus possède :
– Un identifiant : PID
● Processus IDentifier
– Celui de son père est le PPID
● Parent PID
5, AG 20215, AG 2021
Chargé de réaliser les échanges mémoire primaire ( centrale ) ↔ mémoire secondaire (disque : swap )
Est activé :
– Lors de la libération de la place
occupée en mémoire centrale par un processus inactif
– Lorsqu'un processus est réintégré en mémoire centrale pour reprendre son exécution
Opération coûteuse en temps en raison des
2015, AG 2021 2015, AG 2021
Chargé d'initialiser le niveau de fonctionnement du SE : runlevel
– Runlevel 0 est l'arrêt du système
– Runlevel 1 mono-utilisateur
– Runlevels 2-5 multi-utilisateur
– Runlevel 6 réinitialisation du système Lit le contenu du fichier /etc/inittab pour
lancer tous les processus nécessaires init créé les processus en utilisant les primitives de création dynamique de processus
5, AG 20215, AG 2021
PID, PPID : des entiers sur 32 bits
– #include <sys/types.h>
– #include <unistd.h>
– pid_t getpid(void);
– pid_t getppid(void);
Proriétaires rééls ( créateur ) et effectifs (droits ) :
– #include <unistd.h>
– #include <sys/types.h>
uid_t getuid(void);
2015, AG 2021 2015, AG 2021
Groupe réels et effectifs :
– #include <unistd.h>
– #include <sys/types.h>
– gid_t getgid(void);
– gid_t getegid(void);
Comme pour les propriétaires l'identificateur réel caractérise le créateur, l'effectif est
utilisé pour déterminer les droits
En général effectif = réel mais cela est
modifiable par utilisation de primitives ou du mécanisme de prise d'identité
5, AG 20215, AG 2021
Les processus sont organisés en groupe
Un groupe de processus est un sous-arbre de l'arborescence des processus
Le processus à la racine de sous-arbre est appelé processus chef de groupe
Un signal envoyé à un chef de groupe est transmis à tous les processus du groupe
Terminaison de tous les processus lors de la déconnexion
Utiliser par les shells pour le contrôle de processus
2015, AG 2021 2015, AG 2021
Accès et modification
– #include <unistd.h>
– int setpgid(pid_t pid, pid_t pgid);
– pid_t getpgid(pid_t pid);
– pid_t getpgrp(void); /* version POSIX.1 */
– pid_t getpgrp(pid_t pid); /* version BSD*/
– int setpgrp(void); /* version System V */
– int setpgrp(pid_t pid, pid_t pgid); /*
version BSD */
5, AG 20215, AG 2021
Une session est un ensemble de groupes de processus
Utiliser par les shells, les gestionnaires de fenêtres et les démons
Une session est attaché à un terminal de contrôle
Création d'une session par un processus non chef de groupe
Primitives :
– #include <unistd.h>
2015, AG 2021 2015, AG 2021
Répertoire de travail :
– #include <unistd.h>
– char *getcwd(char *buf, size_t size);
– char *getwd(char *buf);
– char *get_current_dir_name(void);
Modification :
– #include <unistd.h>
– int chdir(const char *path);
– int fchdir(int fd);
5, AG 20215, AG 2021
L'environnement est un ensemble de
variables et leurs valeurs passés par un processus père à un processus fils
Il n'est pas accessible par appel d'une
primitive mais passé en paramètre lors de l'appel de main
Il est accessible et modifiable directement ou en utilisant des fonctions de bibliothèque
int main ( int narg, char * arg[ ], char * env[ ])
2015, AG 2021 2015, AG 2021
Ensemble de chaîne de caractères variable=valeur
Accès :
– #include <stdlib.h>
– char *getenv(const char *name);
– int putenv(char *string);
– int setenv(const char *name, const char *value, int overwrite);
– int unsetenv(const char *name);
– int clearenv(void);
5, AG 20215, AG 2021
Source : WIKIPEDIA
2015, AG 2021 2015, AG 2021
Processus UNIX - GNU/Linux
– Modèle
– Création
– Terminaison
– Recouvrement
5, AG 20215, AG 2021
La création de processus se fait par duplication d'un processus existant Primitive :
#include <unistd.h>
pid_t fork(void);
L'exécution de cette primitive par un
processus duplique le processus appelant qui devient le processus père, le processus créé étant le processus fils
L'ensemble des segments hormis le segment de pile sont dupliqués et un nouveau
2015, AG 2021 2015, AG 2021
Le processus fils hérite de son père la
majorité des caractéristiques sauf le PID, le PPID et traitement des signaux
L'héritage comprends l'ensemble des descripteurs ouverts ce qui permet la redirection des fichiers d'E/S standard
5, AG 20215, AG 2021
A l'issue du fork il existe deux processus exécutant le même code
La distinction processus père, processus fils est réalisée par la valeur retournée par fork qui est différente selon le processus
Père :
– -1 : indique que la primitive a échoué
– > 0 : un processus fils a été créé et son PID est la valeur retournée
Fils : 0
2015, AG 2021 2015, AG 2021
C D P S Père
fork
C D P S Fils
5, AG 20215, AG 2021
Pour accélérer le processus de création les segments ne sont pas effectivement
dupliqués mais simplement partagés par les processus
La duplication est effective avec la première écriture dans un segment
Le segment de code n'est jamais dupliqué car accessible en lecture seulement
2015, AG 2021 2015, AG 2021
C D P S Père
S Fils
5, AG 20215, AG 2021
C D P S
Père
S Fils
D P
2015, AG 2021 2015, AG 2021
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
int main(){
pid_t fils, processus, pere;
fils=fork();
switch ( fils ) { case -1 :
Echec du fork return -1;
break;
case 0 :
Exécuté par le processus fils break;
default :
Exécuté par le père }
Exécuté par les deux processus
return 0;
Père
Père
Fils
Fils Fils
5, AG 20215, AG 2021
#include <stdio.h>
#include <stdlib.h>
int main(){
pid_t fils, processus, pere;
fils=fork();
switch ( fils ) {
case -1 : fprintf(stderr,"Pere : echec du fork\n");
return -1;
break;
case 0 : //processus fils processus = getpid();
pere=getppid();
fprintf(stdout,
"Processus fils : mon no = %d mon pere = %d\n",processus,pere);
break;
default : //Processus pere processus = getpid();
pere=getppid();
2015, AG 2021 2015, AG 2021
//Exécuté par les deux processus fprintf(stdout,
"Je suis le processus = %d\n",processus);
return 0;
}
5, AG 20215, AG 2021
2015, AG 2021 2015, AG 2021
A l'issue du fork il existe deux processus le SE doit allouer le processeur à l'un des deux La norme POSIX ne précise rien sur l'ordre d'exécution des processus
Plusieurs exécutions donnent des résultats différents
Si le processus père s'exécute en premier le processus fils est « adopté » par le
processus 1 : init
Le caractère d'appel du shell peut être affiché au milieu des affichages des processus
5, AG 20215, AG 2021
#include <stdio.h>
#include <stdlib.h>
int main(){
pid_t fils, processus, pere;
fils=fork();
switch ( fils ) {
case -1 : fprintf(stderr,"Pere : echec du fork\n");
return -1;
break;
case 0 : //processus fils processus = getpid();
pere=getppid();
fprintf(stdout,
"Processus fils : mon no = %d mon pere = %d\n",processus,pere);
return 0;
} //Processus pere processus = getpid();
pere=getppid();
fprintf(stdout,
2015, AG 2021 2015, AG 2021
5, AG 20215, AG 2021
A l'issue du fork le fils existe mais n'a pas exécuté les instructions précédent le fork mais lors du fork les segments sont
partagés et les variables ont les mêmes valeurs dans le père et le fils
Ces variables sont différentes car l'espace d'adressage est propre au processus
Dès que l'un des processus modifie une variable, écriture, le segment la contenant est dupliqué
2015, AG 2021 2015, AG 2021
#include <stdio.h>
#include <stdlib.h>
int main(){
pid_t fils, processus, pere;
int i;
i = 1;
fils=fork();
switch ( fils ) {
case -1 : fprintf(stderr,"Pere : echec du fork\n");
return -1;
break;
case 0 : //processus fils processus = getpid();
pere=getppid();
fprintf(stdout,
"Processus fils : mon no = %d mon pere = %d\n",processus,pere);
fprintf(stdout,
"Processus fils : i = %d\n",i);
i= 10;
fprintf(stdout,
"Processus fils : i = %d\n",i);
return 0;
5, AG 20215, AG 2021
default :
processus = getpid();
pere=getppid();
fprintf(stdout,
"Processus pere : mon no = %d mon pere = %d\n",processus,pere);
fprintf(stdout,
"Processus pere : i = %d\n",i);
}
//Processus pere fprintf(stdout,
"Processus pere : i = %d\n",i);
return 0;
}
2015, AG 2021 2015, AG 2021
5, AG 20215, AG 2021
Processus UNIX - GNU/Linux
– Modèle
– Création
– Terminaison
– Recouvrement
2015, AG 2021 2015, AG 2021
Deux cas possible fin anormale et normale Quelque soit le cas de terminaison
l'ensemble des ressources allouées au
processus sont libérées, seul le descripteur du processus est conservé jusqu'à ce que le processus père acquiert le code de retour ou status
Terminaison anormale : durant son exécution le processus commet une faute le système arrête alors son exécution immédiatement Terminaison normale : le processus se
termine par un appel direct ou indirect à _exit
5, AG 20215, AG 2021
Deux cas les deux cas :
– L'ensemble des ressources allouées au processus sont libérées
– Le descripteur du processeur est mis à jour en y écrivant le code de retour du processus un entier de 16 bits < 0
= fin anormale , > 0 = fin normale Le processus est retiré de la liste des
processus candidat à l'allocation du processeur
Le processus passe à l'état zombie jusqu'à
2015, AG 2021 2015, AG 2021
Durant son exécution le processus tente d'effectuer une opération illégale :
– accès mémoire à une partie de la mémoire non allouée
– tentative d'écriture dans un segment en lecture seulement
– ...
Le système arrête alors le processus immédiatement sans aucune opération
supplémentaire hormis la transmission au
processus père d'un code de retour indiquant la nature de la faute = numéro du signal
5, AG 20215, AG 2021
Durant son exécution le processus effectue un appel direct ou indirect à la primitive _exit void _exit(int status) ou status est le code de retour du processus
La fonction de bibliothèque exit permettant d'exécuter une partie de code associée au processus par la fonction atexit
void exit(int status)
int atexit(void (*function)(void))
2015, AG 2021 2015, AG 2021
Dans un programme C l'instruction return expression ;
terminant la fonction main est un appel de
l'épilogue du programme qui lui même fait un appel à la fonction exit
Dans tous les cas le code de retour :
paramètre d'exit ou expression suivant return est le code de retour transmis au processus père qui pourra être le processus numéro 1, init, si le processus père se termine avant son fils
5, AG 20215, AG 2021
return;
}
int main(int narg, char* arg[],char *env[]) { int d;
if ( narg != 2 ) {
fprintf(stderr,"Il manque l'aurgument\n");
return EXIT_FAILURE;
}
(void) atexit(fin);
fprintf(stderr,"Tentative d'ouverture en creation du fichier : %s\n",arg[1]);
d = open(arg[1],O_CREAT|O_WRONLY,S_IRWXU);
if ( d < 0) { fprintf(stderr,
"Echec de l'ouverture de %s en écriture avec création\n", arg[1]);
perror("Echec open : ");
}
d = creat(arg[1],S_IRWXU);
if ( d < 0) {
fprintf(stderr,"Echec de création de %s\n", arg[1]);
perror("Echec creat : ");
2015, AG 2021 2015, AG 2021
5, AG 20215, AG 2021
Il est souvent utile qu'un processus père ne reprenne son exécution qu'après la fin du processus fils
Fonctionnement classique du shell
lorsqu'une commande est exécutée en avant plan
Le primitive wait permet de mettre en attente un processus jusqu'à la fin d'un de ces
processus fils
Si un processus fils s'est terminé avant l'appel de wait le père n'est pas mis en attente
2015, AG 2021 2015, AG 2021
Lorsqu'un processus se termine toutes ses ressources sont libérées seul son descripteur est conservé car il contient son code de
retour
Le processus est dans l'état zombie jusqu'à ce que son père exécute wait pour obtenir son code de retour
Code de retour : valeur retournée par la
fonction main ou passée en paramètre à la primitive exit (déconseillé)
5, AG 20215, AG 2021
– #include <sys/types.h>
– #include <sys/wait.h>
– pid_t wait(int *status);
Résultat retourné :
– -1 en cas d'erreur, le processus n'a pas de fils
– Le PID du processus qui s'est terminé
– Si status est ≠ NULL *status
contiendra le code de retour du processus
2015, AG 2021 2015, AG 2021
– #include <sys/types.h>
– #include <sys/wait.h>
– pid_t waitpid(pid_t pid, int *status, int options);
– int waitid(idtype_t idtype, id_t id, siginfo_t *infop, int options);
Voir les pages du manuel en ligne man 2 wait
5, AG 20215, AG 2021
Le code de retour passé au père indique la manière dont s'est terminé le fils qui peut être :
– Normale : la fonction main a exécuté return puis exit contenu dans
l'épilogue du programme ajoutée lors de l'édition de lien
– Anormale : le processus a été arrêté par le SE sans exécuter le return de main
2015, AG 2021 2015, AG 2021
Les macros suivantes permettent de tester la valeur du code de retour:
WIFEXITED(status) : vrai si le processus s'est terminé
normalement
WEXITSTATUS(status) : retourne les 8 bits de droite de status
Équivalent à status && 0x00FF
WIFSIGNALED(status) : vrai si le processus s'est terminé sur
reception d'un signal
5, AG 20215, AG 2021
Les macros suivantes :
WTERMSIG(status) : numéro du signal ayant provoqué l'arrêt
WCOREDUMP(status) : vrai si une image mémoire du processus à été créée ( fichier core )
– WIFSTOPPED(status) : vrai si le processus est actuellement arrêté
– WSTOPSIG(status) : numéro du signal ayant causé l'arrêt
– WIFCONTINUED(status):vrai si le
A 2015, AG 2021A 2015, AG 2021
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
int main(){
pid_t fils, processus, pere;
int i, status;
i = 1;
fils=fork();
switch ( fils ) {
case -1 : fprintf(stderr,"Pere : echec du fork\n");
return -1;
break;
case 0 : //processus fils processus = getpid();
pere=getppid();
fprintf(stdout,
"Processus fils : mon no = %d mon pere = %d\n",processus,pere);
fprintf(stdout,
"Processus fils : i = %d\n",i);
i= 10;
fprintf(stdout,
"Processus fils : i = %d\n",i);
5, AG 20215, AG 2021
//Processus pere
processus = getpid();
pere=getppid();
// Attente du fils
fils = wait ( &status);
fprintf(stdout,
"Processus pere : mon no = %d mon pere = %d\n",processus,pere);
fprintf(stdout,
"Processus pere : i = %d\n",i);
fprintf(stdout,
"Processus pere : mon fils %d s'est terminé \n",fils);
if( WIFEXITED(status) ) fprintf(stdout,
"Processus pere : mon fils s'est terminé normalement\n");
else
fprintf(stdout,
"Processus pere : mon fils s'est terminé anormalement\n");
return EXIT_SUCCESS;
2015, AG 2021 2015, AG 2021