• Aucun résultat trouvé

Processus dans GNU/Linux

N/A
N/A
Protected

Academic year: 2022

Partager "Processus dans GNU/Linux"

Copied!
72
0
0

Texte intégral

(1)

5, AG 20215, AG 2021

Processus dans GNU/Linux

(2)

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

(3)

5, AG 20215, AG 2021

Fichier binaire

Code Données

Pile

Système exec*

Chargeur

(4)

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)

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

(6)

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

(7)

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

(8)

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

(9)

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

(10)

2015, AG 2021 2015, AG 2021

(11)

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

(12)

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

(13)

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

(14)

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

(15)

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

(16)

2015, AG 2021 2015, AG 2021

Chargeur

Fichier binaire à points externes résolus

Mémoire Code

Pile

Système Données

(17)

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

(18)

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.

(19)

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

(20)

2015, AG 2021 2015, AG 2021

(21)

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

(22)

2015, AG 2021 2015, AG 2021

(23)

5, AG 20215, AG 2021

(24)

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

(25)

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

(26)

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

(27)

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

(28)

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é

(29)

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

(30)

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

(31)

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>

(32)

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

(33)

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[ ])

(34)

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

(35)

5, AG 20215, AG 2021

Source : WIKIPEDIA

(36)

2015, AG 2021 2015, AG 2021

Processus UNIX - GNU/Linux

Modèle

Création

Terminaison

Recouvrement

(37)

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

(38)

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

(39)

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

(40)

2015, AG 2021 2015, AG 2021

C D P S Père

fork

C D P S Fils

(41)

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

(42)

2015, AG 2021 2015, AG 2021

C D P S Père

S Fils

(43)

5, AG 20215, AG 2021

C D P S

Père

S Fils

D P

(44)

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

(45)

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

(46)

2015, AG 2021 2015, AG 2021

//Exécuté par les deux processus fprintf(stdout,

"Je suis le processus = %d\n",processus);

return 0;

}

(47)

5, AG 20215, AG 2021

(48)

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

(49)

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,

(50)

2015, AG 2021 2015, AG 2021

(51)

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é

(52)

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;

(53)

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;

}

(54)

2015, AG 2021 2015, AG 2021

(55)

5, AG 20215, AG 2021

Processus UNIX - GNU/Linux

Modèle

Création

Terminaison

Recouvrement

(56)

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

(57)

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'à

(58)

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

(59)

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

(60)

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

(61)

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

(62)

2015, AG 2021 2015, AG 2021

(63)

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

(64)

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

(65)

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

(66)

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

(67)

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

(68)

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

(69)

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

(70)

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

(71)

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;

(72)

2015, AG 2021 2015, AG 2021

Références

Documents relatifs

C- Dans le cas d'un système en multiprogrammation, qu'est-ce qui empêche d'utiliser cette fonction pour aider à optimiser la durée d'exécution d'un

Exécutez puis commentez le résultat de l’exécution de ce code.. Que représente la valeur de returnWait et celle

La seule chose évidente est que la somme des 5 éléments ne varie pas ; en particulier, partant d’un quintuplet à somme , le processus ne peut pas s’arrêter.. Inversement, sur

La tribu des événements anté- rieurs à un temps d’arrêt 03C4 est définie exactement comme dans le cas uni- dimensionnel et sera notée Les propriétés

Annales de l’Institut Henri Poincaré - Section B.. PROCESSUS DE POINTS MARQUÉS ET PROCESSUS RAMIFIÉS 265. A étant un borélien. La mesure sur R s’exprime par

Une condition nécessaire et suffisante pour que M soit le tué d’un processus M de Feller, conservatif, par une fonctionnelle additive. continue de À-potentiel fini,

On va lancer la commande sleep en arrière plan, puis on va la visualiser afin de la repasser en premier plan puis la remettre en arrière plan... Exemple : Ci dessous, on voit

 Arborescence de processus avec un rapport père - fils entre processus créateur et processus crée. Processus pid 15 Processus