système d'exploitation2-TP, Study Guides, Projects, Research of Operating Systems

fork(),thread(),sémaphores,commandes

Typology: Study Guides, Projects, Research

2017/2018

Uploaded on 12/15/2018

unknown user
unknown user 🇩🇿

1 / 40

Toggle sidebar

This page cannot be seen from the preview

Don't miss anything!

bg1
Les threads C-POSIX
Lakhdar Loukil
Maître de conférences
Département d’informatique
Faculté des sciences exactes et appliquées
Semestre 1, 2018-2019
1
pf3
pf4
pf5
pf8
pf9
pfa
pfd
pfe
pff
pf12
pf13
pf14
pf15
pf16
pf17
pf18
pf19
pf1a
pf1b
pf1c
pf1d
pf1e
pf1f
pf20
pf21
pf22
pf23
pf24
pf25
pf26
pf27
pf28

Partial preview of the text

Download système d'exploitation2-TP and more Study Guides, Projects, Research Operating Systems in PDF only on Docsity!

Les threads C-POSIX

Lakhdar Loukil

[email protected] / [email protected]

Maître de conférences

Département d’informatique

Faculté des sciences exactes et appliquées

Semestre 1, 2018-

Contents

  • 1 Commandes Linux pour afficher des informations sur les processus
    • 1.1 La commande ps
      • 1.1.1 Liste des processus du système
      • 1.1.2 Liste complète des processus
      • 1.1.3 Liste des processus d’un utilisateur spécifique
      • 1.1.4 Liste des processus par nom ou par PID
      • 1.1.5 Tri des processus par utilisation du CPU et de la mémoire
    • 1.2 La commande pstree
    • 1.3 La commande top
    • 1.4 Le pseudo-système de fichiers proc
    • 1.5 Exercices
  • 2 Les fonctions systèmes getpid(), getppid()
    • 2.1 Les fonctions systèmes getpid() et getppid()
  • 3 Les fonctions systèmes fork(), wait() et exec()
    • 3.1 La fonction fork()
    • 3.2 Les fonctions systèmes wait() et waidpid()
    • 3.3 Les fonctions exec()
    • 3.4 Exercices
  • 4 Le multithreading
    • 4.1 Introduction
    • 4.2 Les threads POSIX (Pthreads)
    • 4.3 Exercices
  • 5 Les mutex et les sémaphores
    • 5.1 Les mutex
    • 5.2 Les sémaphores
    • 5.3 Exercices
  • 6 Références

1.1.2 Liste complète des processus

L’option -f (full ) permet d’afficher d’autres informations (colonnes) sur un processus. En plus des champs affichés par l’option -e, on trouve UID (identifiant de l’utilisateur), PPID (identifiant du processus père), C (rapport du temps d’utilisation du CPU sur le temps d’exécution) et STIME (date de démarrage du processus). L’option -f peut être combinée avec d’autres options pour afficher des colonnes addi- tionnelles.

Exemple:

laloukil@laloukil:~$ ps -ef UID PID PPID C STIME TTY TIME CMD root 1 0 0 19:24? 00:00:01 /sbin/init root 2 0 0 19:24? 00:00:00 [kthreadd] root 3 2 0 19:24? 00:00:00 [ksoftirqd/0] root 5 2 0 19:24? 00:00:00 [kworker/0:0H] root 7 2 0 19:24? 00:00:01 [rcu_sched] root 8 2 0 19:24? 00:00:00 [rcuos/0] root 9 2 0 19:24? 00:00:00 [rcuos/1] root 10 2 0 19:24? 00:00:00 [rcuos/2] root 11 2 0 19:24? 00:00:00 [rcuos/3] .....

Remarque: Il est parfois utile de relier en pipe la commande ps avec la commande more, less ou grep pour prendre le temps de consulter les longues listes de processus (cas des commandes more et less) ou pour filtrer le résultat de la commande ps et n’afficher que les informations utiles (cas de la commande grep).

Exemples:

  1. ps en pipe avec more affiche la liste des processus par page. Pour défiler la liste par ligne (resp. par page), appuyer sur la touche Entree (resp. Barre d’espacement):

laloukil@laloukil:~$ ps -A | more

  1. ps en pipe avec less permet de défiler la liste les processus vers le haut et vers le bas à l’aide des touches Haut et Bas du clavier. Pour quitter la commande, il suffit d’appuyer sur la touche Q:

laloukil@laloukil:~$ ps -A | less

  1. La commande suivante permet de vérifier si le processus firefox est en cours d’exécution:

laloukil@laloukil:~$ ps -A | grep firefox 12494? 00:01:20 firefox

1.1.3 Liste des processus d’un utilisateur spécifique

Pour sélectionner les processus attachés à un utilisateur spécifique, on utilise l’option -u suivie du nom de l’utilisateur. Plusieurs utilisateurs peuvent être indiqués, il suffit de les séparer par une virgule.

La commande suivante permet d’afficher les processus de l’utilisateur root:

laloukil@laloukil:~$ ps -f -u root UID PID PPID C STIME TTY TIME CMD root 1 0 0 10:10? 00:00:01 /sbin/init root 2 0 0 10:10? 00:00:00 [kthreadd] root 3 2 0 10:10? 00:00:00 [ksoftirqd/0] root 4 2 0 10:10? 00:00:00 [kworker/0:0] .....

1.1.4 Liste des processus par nom ou par PID

L’option -C filtre les processus par commande ou nom de processus. La commande suivante affiche tous les processus getty:

laloukil@laloukil:~$ ps -f -C getty UID PID PPID C STIME TTY TIME CMD root 901 1 0 10:10 tty4 00:00:00 /sbin/getty -8 38400 tty root 905 1 0 10:10 tty5 00:00:00 /sbin/getty -8 38400 tty root 911 1 0 10:10 tty2 00:00:00 /sbin/getty -8 38400 tty root 912 1 0 10:10 tty3 00:00:00 /sbin/getty -8 38400 tty root 915 1 0 10:10 tty6 00:00:00 /sbin/getty -8 38400 tty root 1059 1 0 10:10 tty1 00:00:00 /sbin/getty -8 38400 tty

L’option -p filtre les processus par leur PID. La commande suivante affiche les processus de PID 3564 et 3582 :

laloukil@laloukil:~$ ps -f -p 3564, UID PID PPID C STIME TTY TIME CMD root 3564 2 0 11:14? 00:00:00 [kworker/1:0] root 3582 2 0 11:23? 00:00:00 [kworker/0:2]

1.1.5 Tri des processus par utilisation du CPU et de la mémoire

Un administrateur système a souvent besoin de connaître les processus gourmands en temps CPU et en mémoire. L’option --sort permet de trier la liste des processus sur un champ donné ou un paramètre particulier.

Plusieurs champs peuvent être spécifiés avec l’option --sort. Les champs doivent être dans ce cas séparés par une virgule. Les champs peuvent être préfixés par le signe "-"

Comme pour la commande ps, pstree peut être reliée en pipe avec les commandes more, less ou grep pour consulter ou chercher un processus dans l’arborescence.

1.3 La commande top

La commande top offre une vue dynamique et en temps réel de l’activité du système. Elle affiche des informations de synthèse sur l’utilisation de la mémoire et du processeur, suivies de la liste des processus et threads actuellement gérés par le noyau Linux. Les informations sur le système, le type, l’ordre et la taille des informations sur les processus peuvent être configurés par l’utilisateur. La commande fournit une interface interactive pour la manipulation des processus. Elle permet de trier les processus par utilisation du CPU, utilisation de la mémoire, etc. (voir manuel de la commande pour plus de détails).

Un exemple d’informations affichées par top est donné ci-dessous:

La commande top organise les informations affichées en deux zones: zone de synthèse et zone des tâches.

La zone de synthèse donne des informations sur l’état général du système telles que le nombre total de processus, le nombre de processus en exécution, nombre de processus endormis, etc. Nous mettons l’accent sur la troisième, quatrième et cinquième ligne qui donnent des informations respectivement sur l’utilisation du CPU, de la mémoire physique et de la mémoire virtuelle.

La troisième ligne donne différents pourcentages d’utilisation du CPU. Nous expliquons ci-après la signification des différentes abréviations:

  • ut: temps passé à exécuter des processus utilisateurs (processus de basse priorité). Sous Linux, l’échelle des priorités d’un processus varie de -20 (priorité la plus élevée) à +19 (priorité la plus basse). Le niveau de priorité par défaut d’un processus est celui de son processus parent, et vaut généralement zéro.
  • sy: temps passé à exécuter des processus du noyau Linux.
  • ni: temps passé à exécuter des processus utilisateurs de priorité élevée.
  • id: temps d’inactivité du processeur.
  • wa: temps passé à attendre des E/S.
  • hi: temps passé à servir les interruptions matérielles.
  • si: temps passé à servir les interruptions logicielles.
  • st: temps volé par cette VM par l’hyperviseur.

La quatrième ligne reflète la mémoire physique. Les champs affichés sont total, used (mémoire utilisée incluant le cache disque), free (libre) et buffers (mémoire utilisée pour les E/S).

La cinquième ligne reflète la mémoire virtuelle. Le champ cached est la taille de la mémoire utilisée par le cache disque.

La zone des tâches affiche une liste triée des processus en cours d’exécution sur votre système. Par défaut, la liste est triée par ordre décroissant d’utilisation du processeur; la liste peut cependant être triée sur d’autres champs tels que le taux d’occupation de la mémoire, par exemple. Différents champs peuvent être affichés dans la zone des tâches; les champs par défaut sont:

  • PID: numéro d’identification du processus.
  • USER: nom de l’utilisateur qui exécute le processus.
  • PR: priorité relative du processus.

noyau Linux en cours d’exécution. Beaucoup d’utilitaires systèmes sont simplement des appels à des fichiers de /proc.

Dans ce qui suit, nous allons décrire quelques fichiers et répertoires sous /proc. Pour de plus amples détails, se référer aux pages du manuel de proc.

  • /proc/[pid]: il y a un sous-répertoire pour chaque processus en exécution. Le nom du sous-répertoire correspond au PID du processus.
  • /proc/devices: affiche les informations sur les périphériques connectés.
  • /proc/modules: affiche la liste des modules chargés par le système.
  • /proc/mounts: liste des systèmes de fichiers actuellement montés par le système.
  • /proc/partitions: contient les numéros de début et de fin, le nombre de blocs et le nom de chaque partition.
  • /proc/pci/devices: informations sur les périphériques PCI.
  • /proc/cpuinfo: donne des informations le processeur (modèle, fréquence, taille du cache, nombre de coeurs, tailles des adresses, etc.)
  • /proc/meminfo: informations sur la mémoire physique (RAM) et la mémoire d’échange (swap).
  • /proc/filesystems: liste des systèmes de fichiers supportés par le kernel.

1.5 Exercices

  1. Afficher les processus attachés à votre terminal.
  2. Utiliser la commande ps pour afficher les processus bash lancés par tous les utilisateurs.
  3. Utiliser la commande ps pour afficher les processus lancés par l’utilisateur root. Pour chaque processus, on souhaiterait voir les colonnes PID, PPID et STIME.
  4. Utiliser les commandes ps et head pour afficher la liste des 5 processus les plus gourmands en CPU.
  5. Utiliser les commandes ps et head pour afficher la liste des 5 processus qui occu- pent le plus de mémoire.
  6. Comment vérifier qu’un processus de nom donné (httpd, par exemple) est en cours d’exécution?
  7. Donner la ligne de commandes Linux qui donne le nombre de processus qui tour- nent sur votre machine (indication : utiliser les commandes ps et wc).
  1. A l’aide de la commande top, afficher les processus de l’utilisateur info triée par ordre décroissant sur le champ %MEM.
  2. Trier la liste affichée par la commande top par ordre décroissant de la mémoire (colonne %MEM).
  3. Comment ajouter de nouveaux champs (le champ PPID par exemple) aux champs affichés par la commande top?
  4. Sous Linux, comment connaître le nombre cœurs du processeur de votre machine?
  5. Quelle commande Linux permet d’afficher la version de Linux installé sur votre ordinateur?
  6. Quelle commande Linux permet d’afficher la taille de la mémoire physique de votre ordinateur?

appelant est appelé processus père, le processus créé par le processus père est appelé processus fils. Au moment de l’appel de fork(), les espaces mémoires du père et du fils ont le même contenu. Par la suite, les écritures mémoires effectuées par l’un des processus n’affectent pas la mémoire de l’autre processus.

Le processus fils est une copie exacte du processus père sauf sur les points suivants (voir le manuel de fork() pour plus de détails):

  • Le processus fils n’hérite pas des verrous mémoire du père.
  • Les utilisations des ressources de processus et les compteurs de temps CPU sont remises à zéro dans le fils.
  • L’ensemble des signaux en attente dans le fils est initialement vide.
  • Le fils n’héritent pas des opérations d’E/S asynchrones de son père
  • Le fils n’hérite pas des ajustements sémaphore de son père.
  • L’enfant n’hérite pas des timers de son père.

Synopsis 1 #i n c l u d e 2 3 pid_t f o r k ( v o i d ) ;

En cas de succès, fork() retourne le PID du processus fils dans le processus père, et la valeur 0 dans le processus fils. La valeur retournée par fork() permet donc au programmeur de distinguer la partie de code exécutée par le processus père de celle exécutée par le processus fils. En cas d’échec, -1 est retourné dans le père et aucun processus fils n’est créé.

Exemple 3. Dans l’exemple suivant, le processus main crée un processus fils, affiche Hello, s’endort pendant 100 secondes et se termine. Le processus fils créé par fork() affiche également Hello, s’endort pendant 100 secondes et se termine. Vous allez constater que deux Hello seront affichés à l’écran. 1 #i n c l u d e 2 #i n c l u d e < s t d l i b. h> 3 #i n c l u d e <s y s / t y p e s. h> 4 #i n c l u d e 5 6 i n t main ( ) { 7 f o r k ( ) ; 8 p r i n t f ( " H e l l o du p r o c e s s u s %d dont l e p e r e e s t %d\n" , g e t p i d ( ) , g e t p p i d ( ) ) ; 9 s l e e p ( 1 0 0 ) ; 10 r e t u r n 0 ;

11 }

Exemple 3. Dans cet exemple, le processus main crée un processus fils, affiche son PID, s’endort pendant 600 secondes, affiche un message de fin d’exécution et se termine. En cas de succès de l’appel fork(), le processus fils affiche son PID, s’endort pendant 10 secondes, affiche un message de fin d’exécution et se termine. En cas d’échec de l’appel de fork(), un message d’erreur est affiché et le programme se termine. 1 #i n c l u d e 2 #i n c l u d e <s y s / t y p e s. h> 3 #i n c l u d e 4 #i n c l u d e < s t d l i b. h> 5 6 i n t main ( v o i d ) { 7 pid_t p id ; 8 p i d = f o r k ( ) ; 9 s w i t c h ( pi d ) { 10 c a s e − 1 : // e c h e c dans f o r k ( ) 11 f p r i n t f ( s t d e r r , " e c h e c du f o r k. \ n" ) ; 12 e x i t ( 1 ) ; 13 break ; 14 c a s e 0 : // p id == 0 : p a r t i e du code e x e c u t e e par l e f i l s 15 f p r i n t f ( stdout , " F i l s : j e demarre. Mon PID e s t %u. \ n" , g e t p i d ( ) ) ; 16 s l e e p ( 1 0 ) ; 17 f p r i n t f ( stdout , " F i l s : j e t e r m i n e. \ n" ) ; 18 e x i t ( 0 ) ; 19 break ; 20 d e f a u l t : // p i d > 0 : p a r t i e du code e x e c u t e e par l e p r o c e s s u s p e r e 21 f p r i n t f ( stdout , " Pere : j e demarre. Mon PID e s t %u. \ n" , g e t p i d ( ) ) ; 22 s l e e p ( 6 0 0 ) ; // S i m u l e r une e x e c u t i o n de 600 s e c. ∗/ 23 f p r i n t f ( stdout , " Pere : j e t e r m i n e. \ n" ) ; 24 e x i t ( 0 ) ; 25 break ; 26 } 27 r e t u r n ( 0 ) ; 28 }

3.2 Les fonctions systèmes wait() et waidpid()

Les fonctions wait() et waitpid() permettent de suspendre le processus appelant jusqu’à ce que l’un de ses processus fils se termine.

Synopsis: 1 #i n c l u d e <s y s / t y p e s. h> 2 #i n c l u d e <s y s / wait. h> 3 4 pid_t w ait ( i n t ∗ s t a t u s ) ;

7 i n t main ( v o i d ) { 8 pid_t p id ; 9 f p r i n t f ( stdout , " Pere : j e demarre. Mon PID e s t %u. \ n" , g e t p i d ( ) ) ; 10 p i d = f o r k ( ) ; 11 s w i t c h ( pi d ) { 12 c a s e − 1 : // e c h e c de f o r k ( ) 13 f p r i n t f ( s t d e r r , " e c h e c du f o r k. \ n" ) ; 14 e x i t ( 1 ) ; 15 break ; 16 c a s e 0 : // p id == 0 : p a r t i e du code e x e c u t e e par l e f i l s 17 f p r i n t f ( stdout , " F i l s : j e demarre. Mon PID e s t %u. \ n" , g e t p i d ( ) ) ; 18 s l e e p ( rand ( ) %20) ; 19 f p r i n t f ( stdout , " F i l s : j e t e r m i n e e t j e s o r s. \ n" ) ; 20 e x i t ( 0 ) ; 21 break ; 22 d e f a u l t : // p i d > 0 : p a r t i e du code e x e c u t e e par l e p e r e 23 f p r i n t f ( stdout , " Pere : J ’ a t t e n d s l a t e r m i n a i s o n de mon f i l s %u\n" , p i d ) ; 24 wai t (NULL) ; 25 f p r i n t f ( stdout , " Pere : Mon f i l s %u s ’ e s t t e r m i n e. Je s o r s. \ n" , p i d ) ; 26 e x i t ( 0 ) ; 27 break ; 28 } 29 r e t u r n ( 0 ) ; 30 }

3.3 Les fonctions exec()

Lorsqu’un processus crée un processus fils avec fork(), le processus fils hérite et exécute le même code que le processus père. Souvent, on a besoin de faire exécuter par le processus fils un programme différent du programme exécuté par le père. Ceci peut être réalisé avec les appels systèmes exec(). Lorsqu’un processus exécute exec(), il remplace le code courant par le code du programme dont le chemin est indiqué en premier paramètre de la fonction exec(). Dans ce recueil de travaux pratiques, nous nous focalisons sur 2 fonctions: execl() et execv().

Synopsis 1 #i n c l u d e 2 3 i n t e x e c l ( c o n s t char ∗ path , c o n s t char ∗ arg0 ,... , char ∗ argn ) ; 4 i n t execv ( c o n s t char ∗ path , char ∗ c o n s t argv [ ] ) ;

Ces fonctions renvoient –1 en cas d’échec. Les fonctions execl() et execv() sont iden- tiques et ne diffèrent que de la façon dont les arguments sont fournis à la fonction.

Pour execl(), les arguments sont fournis sous forme d’une liste terminée par le pointeur NULL:

  • path: chaîne de caractères indiquant le chemin absolu du nouveau programme à charger et à exécuter.
  • arg0, arg1, ..., argn: les arguments du programme : arg0 reprend le nom du programme. arg1, ..., argn-1 sont les arguments du programme et argn=NULL.

Pour la fonction execv(), les arguments sont fournis dans un vecteur de chaînes de caractères dont le dernier argument est la valeur NULL :

  • path: chaîne de caractères donnant le chemin absolu du nouveau programme à substituer et à exécuter.
  • argv[]: la liste des arguments.

En cas de succès de l’appel de execl()(ou execv()), le processus charge puis exécute le code du programme dont le chemin est indiqué comme premier argument de l’appel exec() et les instructions qui suivent l’appel de exec() ne sont pas exécutées. En cas d’échec de recouvrement de code (renvoie de la valeur -1), l’exécution se poursuit à partir de l’instruction qui suit l’appel de exec().

Il existe de nombreuses autres fonctions de la famille de fonctions exec(): execle(), execve(), execlp(), execvp(). Pour plus de détails, consulter le manuel de ces fonc- tions.

Exemple 3. Dans cet exemple, le processus courant est remplacée par le processus ls. En cas de succès de l’appel de execl(), c’est la commande ls -l qui sera exécutée. En cas d’échec, c’est le message "Erreur lors de l’exécution de ls." qui sera affiché à l’écran.

1 #i n c l u d e 2 #i n c l u d e 3 4 i n t main ( ) { 5 e x e c l ( "/ b i n / l s " , " l s " , "−l " , NULL) ; 6 p r i n t f ( " E r r e u r l o r s de l ’ e x c u t i o n de l s. \n" ) ; 7 r e t u r n 0 ; 8 }

Exemple 3. Le même exemple que précédemment avec l’utilisation de la fonction execv().

1 #i n c l u d e 2 #i n c l u d e 3 4 #d e f i n e NMAX 5 5

11 p r i n t f ( "Avant f o r k : Je s u i s l e p r o c e s s u s main , mon p i d e s t = %d\n" , p i d ) ; 12 s l e e p ( 2 0 ) ; 13 p r i n t f ( " Appel de f o r k par l e p r o c e s s u s main de PID = %d... \n" , g e t p i d ( ) ) ; 14 p i d = f o r k ( ) ; // Clonage du p r o c e s s u s 15 s w i t c h ( p id ) { 16 c a s e −1: // p id = −1: e r r e u r du f o r k ( ) 17 f p r i n t f ( s t d e r r , " e r r e u r du f o r k. \ n" ) ; 18 e x i t ( 1 ) ; 19 break ; 20 21 c a s e 0 : // p i d = 0 : on e s t dans l e f i l s 22 f p r i n t f ( stdout , " Fork r e u s s i... Je s u i s l e p r o c e s s u s f i l s c r e e par f o r k : mon PID e s t = %d , l e PID de mon p e r e e s t = %d\n" , g e t p i d ( ) , g e t p p i d ( ) ) ; 23 s l e e p ( 3 0 ) ; 24 f p r i n t f ( stdout , " Fin du f i l s ( PID=%d ) , i=%d... \n" , g e t p i d ( ) , i ) ; 25 e x i t ( 0 ) ; 26 break ; 27 28 d e f a u l t : // p id > 0 : on e s t dans l e p e r e 29 // M o d i f i c a t i o n de l a v a r i a b l e i par l e p e r e 30 i = 2 ; 31 f p r i n t f ( stdout , " Je s u i s l e p r o c e s s u s pere , j e v i e n s de m o d i f i e r l a v a r i a b l e i , i= %d\n" , i ) ; 32 s l e e p ( 1 0 ) ; 33 p r i n t f ( " Fin du p e r e ( PID=%d ) , i = %d... \n" , g e t p i d ( ) , i ) ; 34 e x i t ( 0 ) ; 35 break ; 36 } 37 }

(a) Compilez puis exécutez le programme. (b) Quelle est la valeur de la variable i affichée dans le fils? Quelle est la valeur de la variable i affichée dans le père? Sont-elles égales? Pourquoi?

  1. Écrire un programme C dans lequel le processus crée un fils, affiche son PID et le PID du processus fils créé, s’endort pendant 30 secondes (sleep(30)) et se termine (exit(0)). Le processus fils affiche son PID, appelle la fonction executerQuelqueChose(), affiche son PID et se termine. Le code de la fonction executerQuelqueChose() est donné ci-dessous: 1 #d e f i n e NB_ITERS 5 2 3 v o i d ex e c u te r Q ue l q ue C h os e ( v o i d ) { 4 f o r ( i n t i = 0 ; i <NB_ITERS ; i ++){ 5 p r i n t f ( " i = %d\n" , i ) ; 6 s l e e p ( rand ( ) %4) ;

7 } 8 }

(a) Exécutez le programme, ouvrez un second terminal et exécuter la commande ps -a. Que remarquez-vous? (b) Mettez en commentaire l’instruction sleep(30) et exécuter la commande ps -a. Que remarquez-vous?

  1. Pour chacun des fragments de programmes C suivants et avant de l’exécuter, dites combien de "Hello!" sont affichés à l’écran et combien de processus sont créés. Dessinez l’arborescence des processus créés.

(a) hello1.c 1 #i n c l u d e 2 3 i n t main ( ) { 4 p r i n t f ( " H e l l o! \ n" ) ; 5 r e t u r n 0 ; 6 }

(b) hello2.c 1 #i n c l u d e 2 #i n c l u d e 3 4 i n t main ( ) { 5 p r i n t f ( " H e l l o! \ n" ) ; 6 f o r k ( ) ; 7 r e t u r n 0 ; 8 }

(c) hello3.c 1 #i n c l u d e 2 #i n c l u d e 3 4 i n t main ( ) { 5 f o r k ( ) ; 6 p r i n t f ( " H e l l o! \ n" ) ; 7 r e t u r n 0 ; 8 }

(d) hello4.c