CSC4508 - Systèmes d'exploitation - François Trahay & Gaël thomas 2020 - Télécom SudParis
←
→
Transcription du contenu de la page
Si votre navigateur ne rend pas la page correctement, lisez s'il vous plaît le contenu de la page ci-dessous
CSC4508 – Systèmes d’exploitation CSC4508 – Systèmes d’exploitation
Contents 4 Patterns de synchronisation classiques 31
4.1 Exclusion mutuelle . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31
Licence vii 4.2 Cohorte . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32
4.3 Producteur/Consommateur . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33
Présentation du module 1
4.3.1 Implémentation d’un Producteur/Consommateur . . . . . . . . . . . . . . . . . . . . 33
4.4 Lecteur/Rédacteur . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33
1 Présentation du module 2
1.1 Organisation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2 4.4.1 Implémentation d’un Lecteur/Rédacteur . . . . . . . . . . . . . . . . . . . . . . . . . 34
1.2 Séances kernel: XV6 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
Mécanismes de synchronisation 37
1.3 Évaluation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
1.4 Évaluation du module . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3 Plan du document 38
Threads 5 1 Introduction 38
1 Rôles d’un système d’exploitation 6 2 Opérations atomiques 38
1.1 Pile logicielle . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6 2.1 Motivation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39
1.2 Test du retour des appels système et des fonctions . . . . . . . . . . . . . . . . . . . . . . . 6 2.2 Opérations atomiques . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39
2.3 Test and set . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40
2 Utilisation de la pile 9
2.4 À quoi sert volatile ? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41
2.1 Contenu d’un stack frame . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
2.5 Compare And Swap (CAS) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43
2.2 Buffer overflow . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
2.6 Fetch and Add . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45
2.2.1 Stack overflow . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
2.7 Barrière mémoire / Memory Fence . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46
2.2.2 Comment prévenir les buffer/stack overflow ?. . . . . . . . . . . . . . . . . . . . . . 13
2.8 Modèle mémoire C11 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46
3 Contexte d’exécution d’un processus 13 2.8.1 Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47
3.1 Fil d’exécution . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14 2.8.2 Relaxed . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 48
3.2 Processus multithread . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14 2.8.3 Release Acquire . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 48
3.3 Création d’un Pthread . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15 2.8.4 Release-Consume . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49
3.4 Autres fonctions Pthread . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16 2.8.5 Sequentially Consistent . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49
4 Partage de données 16 3 Primitives de synchronisation 51
4.1 Thread-safe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16 3.1 Synchronisation par attente active . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51
4.2 Réentrant . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17 3.2 Futex . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 52
4.3 TLS – Thread-Local Storage . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19 3.3 Construction d’un mutex à l’aide d’un futex . . . . . . . . . . . . . . . . . . . . . . . . . . . 53
3.4 Construction d’un moniteur à l’aide d’un futex . . . . . . . . . . . . . . . . . . . . . . . . . 54
5 Synchronisation 20
5.1 Mutex . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21 4 De l’utilisation de la synchronisation 57
5.2 Opérations atomiques . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23 4.1 Interblocage / Deadlock . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 57
4.2 Granularité . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 58
Bibliographie du chapitre 24 4.3 Scalabilité d’un système parallèle . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 58
Programmation concurrente 25 Bibliographie du chapitre 59
1 Introduction 26 Appels système 61
2 Mécanismes de synchronisation inter processus 26 1 Le Système d’exploitation 62
2.1 Tubes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26 1.1 Le système d’exploitation (2/2) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 62
2.2 Mémoire partagée . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
2.3 Sémaphore . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27 2 L’interface user/system 62
2.1 L’interface user/system . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 63
3 Mécanismes de synchronisation intra-processus 28 2.2 L’interface user/system . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 63
3.1 Mutex . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28
3.2 Moniteur . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29 Interruptions et communication 65
3.3 Barrière . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30
3.3.1 Read-Write lock . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30 Plan du document 66
Télécom SudParis — François Trahay & Gaël thomas — 2020 — i Télécom SudParis — François Trahay & Gaël thomas — 2020 — iiCSC4508 – Systèmes d’exploitation CSC4508 – Systèmes d’exploitation
1 Les bus de communication 66 3.5 Instructions vectorielles . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 93
1.1 Les bus de communication . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 66
1.2 Le bus mémoire . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 67 4 Parallel Processing 94
1.2.1 DMA: Direct Memory Access . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 67 4.1 Hyperthreading / SMT . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 95
1.2.2 MMIO: Memory-Mapped IO . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 68 4.2 Processeurs multi-cœurs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 95
1.3 Le bus d’entrées/sorties . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 68 4.3 Architectures SMP . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 96
1.4 Le bus d’interruptions – principe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 69 4.4 Architectures NUMA . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 96
2 Interruptions 69 5 Hiérarchie mémoire 97
2.1 Réception d’une interruption . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 70 5.1 Enjeux . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 97
2.2 Réception d’une interruption : exemple . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 70 5.2 Caches . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 98
2.3 Réception d’une interruption (suite) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 71 5.3 Memory Management Unit (MMU) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 99
2.4 Interruptions et multicœurs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 71 5.3.1 Fully-associative caches . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 100
2.5 MSI: Message Signaling Interrupt . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72 5.3.2 Direct-mapped caches . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 100
2.6 Communication inter cœur . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72 5.3.3 Set-associative caches . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 101
2.7 La table IDT . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 73 5.3.4 Cohérence de cache . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 101
2.8 Gestion du temps : deux sources . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 73
Bibliographie du chapitre 102
Mémoire virtuelle 75
Entrées/sorties 103
1 Introduction 76
Plan du document 104
2 Pagination 76
2.1 Généralités . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 77 1 IO bufferisée / non bufferisée 105
2.2 État des pages mémoire . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 77
2.3 Adresse logique (ou virtuelle) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 78 2 Primitives Unix d’entrée-sortie 105
2.4 Table des pages . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 79 2.1 Ouverture/fermeture de fichier . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 105
2.5 Mise en œuvre sur un pentium 64 bits . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 79 2.2 Lecture sur descripteur . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 106
2.6 Translation Lookaside Buffer (TLB) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 79 2.3 Écriture sur descripteur . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 107
2.4 Duplication de descripteur . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 109
3 Le point de vue utilisateur 80
3.1 Espace mémoire d’un processus . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 80 3 Entrées-sorties et concurrence 109
3.2 Mapping mémoire . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 81 3.1 Verrouillage de fichier . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 110
3.3 Allocation mémoire . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 81 3.2 Manipulation de l’offset . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 112
3.4 Le point de vue libc . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 83
4 Améliorer les performances des entrées-sorties 112
4 Politiques d’allocation mémoire 83 4.1 Conseil au noyau pour les lectures . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 112
4.1 Non-Uniform Memory Access . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 84 4.2 IO asynchrones . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 113
4.2 Politique d’allocation First touch . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 84 4.3 mmap . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 113
4.3 Politique d’allocation Interleaved . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 85
Systèmes de fichier 115
4.4 mbind . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 85
Plan du document 116
Architecture 87
1 Périphérique et pilote de périphérique 116
Plan du document 88
1.1 Périphérique et pilote de périphérique . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 116
1 Introduction 88 1.2 Les périphériques dans les UNIX . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 117
1.1 Loi de Moore . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 88 1.3 2 types de périphériques . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 117
1.4 Périphériques de type bloc dans xv6 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 118
2 Processeur séquentiel 89 1.5 Principe de l’algorithme de iderw . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 118
3 Pipeline 89 2 Le cache d’entrées/sorties 119
3.1 Micro architecture d’un pipeline . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 90 2.1 Le cache d’entrée/sortie . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 119
3.2 Processeurs superscalaires . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 90 2.2 Principe d’un cache d’entrée/sortie . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 120
3.2.1 Processeurs superscalaires . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 91 2.3 Le buffer cache de xv6 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 120
3.2.2 Dépendance entre instructions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 91 2.4 Fonctionnement du buffer cache (1/3) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 121
3.3 Gestion des branchements . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 92 2.5 Fonctionnement du buffer cache (2/3) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 121
3.4 Prédiction de branchement . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 92 2.6 Fonctionnement du buffer cache (3/3) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 122
Télécom SudParis — François Trahay & Gaël thomas — 2020 — iii Télécom SudParis — François Trahay & Gaël thomas — 2020 — ivCSC4508 – Systèmes d’exploitation CSC4508 – Systèmes d’exploitation
3 Le journal 122
3.1 Opération versus écriture sur disque . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 123
3.2 Problèmes de cohérence . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 123
3.3 Mauvaises solutions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 124
3.4 Première idée : les transactions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 124
3.5 Deuxième idée : le journal . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 125
3.6 Troisième idée : le journal parallèle . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 125
3.7 Structure du journal . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 126
3.8 Principe d’algorithme du journal . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 126
3.9 Utilisation du journal . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 127
3.10 Mise en œuvre dans xv6 (1/3) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 127
3.11 Mise en œuvre dans xv6 (2/3) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 128
3.12 Mise en œuvre dans xv6 (3/3) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 128
4 Partitions et systèmes de fichiers 129
4.1 Système de fichiers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 129
4.2 Principe d’un système de fichiers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 130
4.3 Les partitions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 130
4.4 L’image disque . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 131
5 Le système de fichiers UFS/xv6 131
5.1 Structure globale du système de fichiers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 132
5.2 Le dinode . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 132
5.3 Les blocs de données d’un fichier . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 133
5.4 Ajout d’un bloc à un fichier . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 133
5.5 Les répertoires . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 134
5.6 Du chemin à l’inode . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 134
5.7 Création et suppression de fichier . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 135
6 La pile d’entrée/sortie de xv6 135
6.1 L’inode . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 136
6.2 Principales fonctions des inodes (1/3) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 136
6.3 Principales fonctions des inodes (2/3) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 137
6.4 Principales fonctions des inodes (3/3) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 137
6.5 Le fichier ouvert . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 138
6.6 Le descripteur de fichier . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 138
7 Ce qu’il faut retenir 139
Références 141
Index 143
Télécom SudParis — François Trahay & Gaël thomas — 2020 — v Télécom SudParis — François Trahay & Gaël thomas — 2020 — viCSC4508 – Systèmes d’exploitation CSC4508 – Systèmes d’exploitation
' $
Licence
Ce document est une documentation libre, placée sous la Licence de Documentation Libre GNU (GNU
Free Documentation License).
Copyright (c) 2020 François Trahay & Gaël Thomas
Permission est accordée de copier, distribuer et/ou modifier ce document selon les
termes de la Licence de Documentation Libre GNU (GNU Free Documentation License),
version 1.2 ou toute version ultérieure publiée par la Free Software Foundation; avec
#1 les Sections Invariables qui sont ‘Licence’ ; avec les Textes de Première de Couverture
qui sont ‘CSC4508 – Systèmes d’exploitation’
et avec les Textes de Quatrième de Couverture qui sont ‘Help’.
Une copie de la présente Licence peut être trouvée à l’adresse suivante :
http://www.gnu.org/copyleft/fdl.html.
Remarque : La licence comporte notamment les sections suivantes : 2. COPIES VERBATIM, 3.
COPIES EN QUANTITÉ, 4. MODIFICATIONS, 5. MÉLANGE DE DOCUMENTS, 6. RECUEILS DE
DOCUMENTS, 7. AGRÉGATION AVEC DES TRAVAUX INDÉPENDANTS et 8. TRADUCTION.
& %
Ce document est préparé avec des logiciels libres :
• LATEX : les textes sources sont écrits en LATEX (http://www.latex-project.org/, le site
du Groupe francophone des Utilisateurs de TEX/LATEX est http://www.gutenberg.eu.org).
Une nouvelle classe et une nouvelle feuille de style basées sur la classe seminar ont
été tout spécialement dévéloppées: newslide et slideint (projet fusionforge slideint,
https://fusionforge.int-evry.fr/www/slideint/);
• emacs: tous les textes sont édités avec l’éditeur GNU emacs (http://www.gnu.org/software/emacs);
• dvips: les versions PostScript (PostScript est une marque déposée de la société Adobe Systems In-
corporated) des transparents et des polycopiés à destination des étudiants ou des enseignants sont
obtenues à partir des fichiers DVI (« DeVice Independent ») générés à partir de LaTeX par l’utilitaire
dvips (http://www.ctan.org/tex-archive/dviware/dvips);
• ps2pdf et dvipdfmx: les versions PDF (PDF est une marque déposée de la société Adobe Sys-
tems Incorporated) sont obtenues à partir des fichiers Postscript par l’utilitaire ps2pdf (ps2pdf
étant un shell-script lançant Ghostscript, voyez le site de GNU Ghostscript http://www.gnu.org/-
software/ghostscript/) ou à partir des fichiers DVI par l’utilitaire dvipfmx;
• makeindex: les index et glossaire sont générés à l’aide de l’utilitaire Unix makeindex
(http://www.ctan.org/tex-archive/indexing/makeindex);
• TeX4ht: les pages HTML sont générées à partir de LaTeX par TeX4ht (http://www.cis.ohio-
-state.edu/~gurari/TeX4ht/mn.html);
• Xfig: les figures sont dessinées dans l’utilitaire X11 de Fig xfig (http://www.xfig.org);
• fig2dev: les figures sont exportées dans les formats EPS (« Encapsulated PostScript ») et PNG
(« Portable Network Graphics ») grâce à l’utilitaire fig2dev (http://www.xfig.org/userman/-
installation.html);
• convert: certaines figures sont converties d’un format vers un autre par l’utilitaire convert
(http://www.imagemagick.org/www/utilities.html) de ImageMagick Studio;
• HTML TIDY: les sources HTML générés par TeX4ht sont « beautifiés » à l’aide de HTML TIDY
(http://tidy.sourceforge.net) ; vous pouvez donc les lire dans le source.
Nous espérons que vous regardez cette page avec un navigateur libre: Firefox par exemple. Comme l’indique
le choix de la licence GNU/FDL, tous les éléments permettant d’obtenir ces supports sont libres.
Télécom SudParis — François Trahay & Gaël thomas — 2020 — vii Télécom SudParis — François Trahay & Gaël thomas — 2020 — viiiPrésentation du module 1 Présentation du module
' $
1 Présentation du module
Objectifs du module :
Comprendre le fonctionnement interne d’un système d’exploitation
Savoir interagir avec l’OS depuis un programme
#2
Structure du module :
[U] des séances orientées “userland”
[K] des séances orientées “kernel”
Présentation du module [G] des séances “plus générales”
& %
François Trahay
' $
1.1 Organisation
1. Processus
CI1 [U] Threads
CSC4508 – Systèmes d’exploitation CI2 [U] Programmation concurrente
CI3 [G] Mécanismes de synchronisation
CI4 [K] Appels systèmes
CI5 [K] Interruption et ordonnancement
CI6 [K] Sprint : finalisation de l’ordonnanceur
2. Mémoire
#3
CI7 [U] Mémoire virtuelle
CI8 [K] Memory Management Unit
CI9 [G] Architecture
CI10 [K] Sprint
3. Entrées/Sorties
CI11 [U] Entrées/sorties
CI12 [U] Synthèse : mini-projet
CI13 [K] Systèmes de fichier
CI14 [K] Sprint
& %
CI15 TP noté
2019–2020
1 Télécom SudParis — François Trahay — 2019–2020 — CSC4508 – Systèmes d’exploitation 2Présentation du module 1 Présentation du module Présentation du module 1 Présentation du module
' $ ' $
1.2 Séances kernel : XV6 1.4 Évaluation du module
Lors des séances [K], vous allez développer un OS
basé sur l’OS xv6
#4 #6
développement de certains mécanismes de l’OS À la fin du module, les étudiants évaluent le module.
séances sprint : Objectif : améliorer le module
finalisation du développement
évaluation par les enseignants
& % & %
' $
1.3 Évaluation
Évaluation :
20% - Contrôle continu lors des sprints :
“comment vous avez implémenté ce mécanisme de l’OS ?”
#5
“que se passe-t-il si X ?”
80% - TP noté avec plusieurs parties :
question(s) de cours
expliquer comment vous avez implémenté un mécanisme de l’OS
développer une application
& %
Télécom SudParis — François Trahay — 2019–2020 — CSC4508 – Systèmes d’exploitation 3 Télécom SudParis — François Trahay — 2019–2020 — CSC4508 – Systèmes d’exploitation 4Threads 1 Rôles d’un système d’exploitation
' $
1 Rôles d’un système d’exploitation
Abstraction : fournir une interface unique pour des matériels divers
exemple : accès à un disque dur, disque SSD, disque NVMe, etc.
#2
Gestion des ressources : assurer l’utilisation efficiente des ressources
ordonnancement des processus sur les CPUs, allocation mémoire, etc.
Protection : contrôler l’utilisation des ressources et gérer les erreurs
séparation de l’espace mémoire des processus, droits d’accès aux ressources, etc.
Threads
& %
' $
1.1 Pile logicielle
François Trahay
main
application
store_result
user space
fprintf high level API
#3
CSC4508 – Systèmes d’exploitation
libc
libraries syscall wrappers
write
write
filesystem network
kernel space
ipc scheduler
operating system buffer cache
memory sd_setup_read_write_cmnd
management block char netw
device driver
hardware CPU Mem Disk ...
& %
Le système d’exploitation est chargé d’exploiter des matériels divers. Il intègre donc des pilotes (drivers)
capables de dialoguer avec un matériel particulier. Les différents pilotes pour un même type de périphérique
offrent une même interface, ce qui permet aux couches supérieures de l’OS d’utiliser indifféremment le
matériel.
Le passage de l’espace utilisateur à l’espace noyau se fait via un appel système (syscall). Le noyau traite
la demande de l’application et retourne un entier positif ou nul en cas de succès, et -1 en cas d’échec.
Du point de vue d’une application, les appels système sont exposés sous la forme de fonctions (définies
2019–2020 dans la libc) chargées d’exécuter l’appel système.
5 Télécom SudParis — François Trahay — 2019–2020 — CSC4508 – Systèmes d’exploitation 6Threads 1 Rôles d’un système d’exploitation Threads 1 Rôles d’un système d’exploitation
' $
fprintf(stderr, "Error while accessing file ’%s’: %s\n", file, strerror());
1.2 Test du retour des appels système et des fonctions
// -> message "Error while accessing file ’plop’: No such file or directory"
exit(EXIT_FAILURE);
}
ou
struct stat buf;
Il faut toujours tester la valeur de retour d’un appel système et traiter les erreurs int rc = stat(file, &buf);
Évite la propagation d’erreurs (la découverte de l’erreur peut avoir lieux if(rc < 0) {
#4 beaucoup plus tard)
perror("Error while accessing file");
// -> message affiché: "Error while accessing file: No such file or directory"
I voir l’approche fail-fast présentée en CSC4102 exit(EXIT_FAILURE);
errno : variable externe indiquant la cause de la dernière erreur }
La section ERRORS du manuel d’une fonction décrit les causes d’erreur possibles
Traitement d’erreur générique. Il est possible de définir une macro affichant un message d’erreur et
indiquant où a eu lieu l’erreur. Par exemple :
#define FATAL(errnum, ...) do { \
& %
fprintf(stderr, "Error in %s:%d:\n", __FILE__, __LINE__); \
fprintf(stderr, __VA_ARGS__); \
fprintf(stderr, ": %s\n", strerror(errnum)); \
Témoignage d’un ancien ASR : « Sans insistance de [l’équipe pédagogique de CSC4508], cela ne nous
abort(); \
aurait pas sauté si vite aux yeaux que les problèmes (en début de la coupe de robotique) venait d’un manque
} while(0)
de gestion des erreurs sur un code qui n’avait pas été relu par suffisamment de monde ».
int main(int argc, char**argv) {
Comment vérifier le code de retour d’une fonction et traiter les problèmes ? La macro char *file = argv[1];
void assert(scalar expression) teste l’expression passée en paramètre et, si celle-ci est fausse, affiche struct stat buf;
un message d’erreur et termine le programme (avec la fonction abort()) : int rc = stat(file, &buf);
if(rc < 0) {
struct stat buf;
FATAL(errno, "Cannot access file ’%s’", file);
int rc = stat(file, &buf);
}
assert(rc>=0);
return EXIT_SUCCESS;
// -> en cas d’erreur, affiche:
}
// appli: appli.c:12: main: Assertion ‘rc>=0’ failed.
// affiche:
// Abandon
// Error in fatal.c:21:
Toutefois, la macro est à utiliser avec précaution car elle est désactivée lorsque le programme est compilé // Cannot access file ’plop’: No such file or directory
en mode optimisé (avec gcc -O3 par exemple). // Abandon
Il est donc préférable de tester le code de retour, afficher un message décrivant l’erreur, et éventuellement
terminer le processus. Debugger. Lorsqu’un programme appelle la fonction abort() afin de terminer le processus. Un fichier
core dump décrivant le processus lors de l’erreur peut alors être généré afin de débugger le programme avec
struct stat buf; gdb.
int rc = stat(file, &buf); Pour activer la génération d’un core dump, lancer la commande ulimit -c unlimited. Dès lors, la
if(rc < 0) { fonction abort() génère un core dump qui peut être fourni à gdb :
fprintf(stderr, "Error\n");
exit(EXIT_FAILURE); // ou abort(); $ ./fatal plop
} Error in fatal.c:21:
Cannot access file ’plop’: No such file or directory
Afficher la cause d’une erreur. Le fichier errno.h listes les erreurs standard. Le manuel de chaque Abandon (core dumped)
appel système (voir man 2 fonction, ou man 3 fonction) et de chaque fonction indique, dans la section
ERRORS, les différents codes d’erreurs qui peuvent être renvoyés. $ gdb ./fatal core
Le message d’erreur associé à une valeur de errno peut être obtenu avec strerror() ou perror() : GNU gdb (Debian 8.1-4+b1) 8.1
[...]
struct stat buf; Reading symbols from ./fatal...(no debugging symbols found)...done.
int rc = stat(file, &buf); [New LWP 11589]
if(rc < 0) { Core was generated by ‘./fatal plop’.
Télécom SudParis — François Trahay — 2019–2020 — CSC4508 – Systèmes d’exploitation 7 Télécom SudParis — François Trahay — 2019–2020 — CSC4508 – Systèmes d’exploitation 8Threads 2 Utilisation de la pile Threads 2 Utilisation de la pile
Program terminated with signal SIGABRT, Aborted. x86 32 bits. Sur les architectures x86 32 bits, les arguments sont placés sur la pile de façon à ce que le
#0 __GI_raise (sig=sig@entry=6) at ../sysdeps/unix/sysv/linux/raise.c:50 premier argument soit à l’adresse ebp+8, le deuxième à l’adresse ebp+12 (si le premier argument est stocké
50 ../sysdeps/unix/sysv/linux/raise.c: Aucun fichier ou dossier de ce type. sur 4 octets), etc.
(gdb) bt
#0 __GI_raise (sig=sig@entry=6) at ../sysdeps/unix/sysv/linux/raise.c:50 L’adresse de retour (c’est-à-dire l’adresse de l’instruction à exécuter après la fonction) est stockée sur la
#1 0x00007ffff7dfb535 in __GI_abort () at abort.c:79 pile à l’adresse ebp+4.
#2 0x0000555555555232 in main (argc=2, argv=0x7fffffffdcd8) at fatal.c:21
' $ ebp + 16 arg3 3rd argument
2 Utilisation de la pile ebp + 12 arg2 2nd argument
ebp + 8 arg1 1st argument
ebp + 4 foo+17 return adress
ebpfoo old ebp value
ebp (base pointer)
ebp - 4 var1 first local variable
esp (stack pointer)
Chaque appel de fonction crée un stack frame sur la pile ebp - 8 var2 second local variable
Un stack frame contient
#5
les variables locales Figure 1 : Stack frame sur architecture x86 32 bits
une sauvegarde des registres modifiés
les arguments de la fonction (spécifique aux architectures x86 32 bits)
l’adresse de retour de la fonction (spécifique aux architectures x86)
x86 64 bits. Sur les architectures x86 64 bits, les arguments sont passés via les registres rdi, rsi, rdx,
& %
rcx, r8 et r9. S’il y a plus de 6 arguments, les arguments suivants sont placés sur la pile.
' $
2.1 Contenu d’un stack frame r9 arg5
r8 arg4
rdx arg3
Un stack frame est défini par rsi arg2 rbp + 8 foo+17 return adress
une adresse de base qui indique où le frame commence (le registre rbp sur x86) rdi arg1 rbpfoo old rbp value
l’adresse du sommet de la pile (le registre rsp sur x86) rbp rbp - 4 var1 first local variable
#6 Entrée de fonction : rsp rbp - 8 var2 second local variable
sauvegarder rbp (avec push rbp)
réinitialiser rbp (avec mov rbp, rsp)
Sortie de fonction : Figure 2 : Stack frame sur architecture x86 64 bits
restoration de l’ancien rbp (pop rbp)
saut à l’adresse de retour (ret)
& %
Convention d’appel de fonction. En fonction de l’architecture CPU (et parfois du compilateur), la Arm. Sur les architectures Arm, les arguments sont passés via les registres (x0 à x7 sur Arm 64 bits).
façon de faire un appel de fonction peut varier. L’adresse de retour est également stockée dans un registre.
Télécom SudParis — François Trahay — 2019–2020 — CSC4508 – Systèmes d’exploitation 9 Télécom SudParis — François Trahay — 2019–2020 — CSC4508 – Systèmes d’exploitation 10Threads 2 Utilisation de la pile 2 Utilisation de la pile 2.2 Buffer overflow
x4 arg5 Exemple. Ici, le bug vient de la boucle sensée remplir le tableau qui effectue une itération de trop (à
cause du “
# include < stdlib .h > Exemple. Voici un exemple de stack overflow :
int main ( int argc , char ** argv ) { stack_overflow.c
int N = 4; # include < stdio .h >
char tab [ N ]; # include < stdlib .h >
int a = 17; # include < string .h >
for ( int i =0; iThreads Threads 3 Contexte d’exécution d’un processus
' $
}
return 0;
3 Contexte d’exécution d’un processus
Ici, la fonction foo ne vérifie pas que new_str est suffisament grand pour contenir str. Donc, si str est
trop long, strcpy déborde et peut écraser l’adresse de retour de foo. Contexte : contexte d’exécution + contexte du noyau
Voici un exemple d’exécution menant à un stack overflow :
Espace d’adressage : code, données et pile
$ gdb ./stack_overflow
(gdb) r coucouAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 0x0000008000000000
The program being debugged has been started already. Stack
Functions context
Start it from the beginning? (y or n) y Process context SP (stack pointer)
# 10
Starting program: stack_overflow coucouAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
Execution context
new_str = coucouAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA Data registers Shared libraries
Libs
Status register
userspace
Program received signal SIGSEGV, Segmentation fault. Stack pointer SP)
0x000055555555518e in foo (str=0x7fffffffe03e "coucou", ’A’ ) at stack_overflow.c:9 Program counter (PC)
9 } Kernel context
(gdb) bt Virt. Mem. structures Heap Dynamic allocation
(malloc, ...)
brk
Descriptor table
#0 0x000055555555518e in foo (str=0x7fffffffe03e "coucou", ’A’ ) at stack_overflow.c:9 brk pointer
.bss Unitialized variables
#1 0x4141414141414141 in ?? () .data Initialized variables PC (program counter)
#2 0x4141414141414141 in ?? () .text Instructions
0x0000000000000000
& %
#3 0x4141414141414141 in ?? ()
#4 0x4141414141414141 in ?? ()
#5 0x4141414141414141 in ?? ()
#6 0x4141414141414141 in ?? ()
#7 0x4141414141414141 in ?? ()
#8 0x4141414141414141 in ?? ()
#9 0x0000555555550041 in ?? ()
#10 0x0000000000000000 in ?? ()
(gdb)
' $
Ici, on observe qu’en sortant de la fonction foo, le programme tente d’exécuter l’instruction située à
l’adresse 0x4141414141414141 (0x41 est la valeur hexadécimale de ’A’), ce qui génère une erreur.
On pourrait exploiter le bug en insérant dans argv[1] l’adresse de la fonction void bar(int a, int b) 3.1 Fil d’exécution
ainsi que ses paramètres [?].
' $
2.2.2 Comment prévenir les buffer/stack overflow ? Fil d’exécution != Ressources
Vérifier les bornes lors de l’accès à un tableau Fil d’exécution (ou thread) : contexte d’exécution + pile
fait de manière automatique en Java Ressources : code, données, contexte du noyau
n’est pas fait en C/C++ car trop coûteux
Thread
Ne pas utiliser les fonctions “dangereuses” (strcpy, gets ...) # 11 Stack
Functions context
0x0000008000000000
Utiliser à la place les fonctions sûres (strncpy, fgets ...) SP (stack pointer)
Execution context
Pile non-exécutable (activé par défaut par Linux) Data registers
Status register
Libs
Shared libraries
userspace
#9 Stack pointer SP)
évite l’exécution de code arbitraire Program counter (PC)
Stack canaries Kernel context
Heap
.bss
Dynamic allocation
(malloc, ...)
Unitialized variables
brk
Un canari (une valeur spécifique) est positionné sur la pile à l’entrée de la Virt. Mem. structures
Descriptor table
.data
.text
Initialized variables
Instructions
PC (program counter)
fonction
0x0000000000000000
brk pointer
Si en sortant de la fonction, le canari a été modifié, il y a eu stack overflow
option -fstack-protector-all de gcc & %
Address space layout randomization (ASLR) (activé par défaut par Linux)
charge le code de l’application à une adresse aléatoire
& %
Télécom SudParis — François Trahay — 2019–2020 — CSC4508 – Systèmes d’exploitation 13 Télécom SudParis — François Trahay — 2019–2020 — CSC4508 – Systèmes d’exploitation 14Threads 3 Contexte d’exécution d’un processus Threads 4 Partage de données
' $
processus), il n’y a pas de hiérarchie entre les threads.
3.2 Processus multithread
' $
3.4 Autres fonctions Pthread
Plusieurs fils d’exécution
Ressources partagées
Thread 1 Thread 2
# 12 Functions context Stack 1 Functions context
Stack 1 Stack 2
SP (stack pointer) SP (stack pointer)
Stack 2
Execution context 1 Execution context 2
Data registers Data registers
Status register Status register
int pthread_exit(void* retval);
userspace
Shared libraries
Stack pointer SP) Libs Stack pointer SP)
Program counter (PC) Program counter (PC)
# 14
Termine le thread courant avec la valeur de retour retval
Heap Dynamic allocation brk
int pthread_join(pthread_t tid, void **retval);
(malloc, ...)
Kernel context
.bss Unitialized variables
Virt. Mem. structures
Descriptor table .data Initialized variables PC (program counter)
brk pointer .text Instructions
0x0000000000000000 Attend la terminaison du thread tid et récupère sa valeur de retour
& %
Dans un processus multi-thread, chaque thread possède un contexte (registres + pile). Le reste de la
mémoire (code, données, etc.) et les ressources (fichiers ouverts, etc.) sont partagés entre les threads.
Les piles des différents threads sont espacées en mémoire de manière à ce qu’elles puissent grossir. Tou- & %
tefois, si la pile d’un thread grossit trop, elle risque de déborder sur la pile d’un autre thread. Pour éviter ce
problème, la taille des piles est limitée (la commande ulimit -s donne la taille de pile maximum). Cette taille
limite peut être modifiée en ligne de commande (par exemple ulimit -s 32768), ou depuis un programme
(en utilisant la fonction setrlimit).
' $ ' $
3.3 Création d’un Pthread 4 Partage de données
Création d’un pthread L’espace mémoire est partagé entre les threads, notamment
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, les variables globales
# 13 void *(*start_routine) (void *), void *arg); # 15
les variables locales statiques
attr (in) : attributs du thread à créer
le contexte du noyau (flux, signaux, etc.)
start_routine (in) : fonction à exécuter une fois le thread créé
Ne sont pas partagées :
arg (in) : paramètre à passer à la fonction
thread (out) : identifiant du thread créé les variables locales
& % & %
Nous présentons ici l’API Pthread (POSIX thread) qui est la plus utilisée en C. La norme C11 définit une
autre interface pour la manipulation de threads. Toutefois, rares sont les implémentations de cette interface.
Le standard de facto reste donc Pthread. Techniquement, tout l’espace mémoire est partagé entre les threads. Il est donc possible de partager
Contrairement à la création de processus qui génère une hiérarchie (ie. un processus parent d’un autre toutes les variables, y compris les variables locales.
Télécom SudParis — François Trahay — 2019–2020 — CSC4508 – Systèmes d’exploitation 15 Télécom SudParis — François Trahay — 2019–2020 — CSC4508 – Systèmes d’exploitation 16Threads 4 Partage de données Threads 4 Partage de données
' $
4.1 Thread-safe for ( char * token = strtok ( string , " : " ) ;
token ;
token = strtok ( NULL , " : " ) ){
printf ( " \ t % s \ n " , token );
}
}
int main ( int argc , char ** argv ) {
extract_path ();
return 0;
}
Code thread-safe : donne un résultat correct lorsqu’exécuté simultanément par plusieurs Voici un exemple de résultat obtenu :
# 16 threads : Parsing ’/usr/local/bin:/usr/bin:/bin:/usr/local/games:/usr/games’
/usr/local/bin
Pas d’appel à du code non thread-safe /usr/bin
/bin
/usr/local/games
Protéger l’accès aux données partagées /usr/games
La fonction strtok n’est pas réentrante car elle se base sur un état précédent (un pointeur sur le dernier
caractère testé dans la chaîne). Ainsi, dans cet exemple, le traitement appliqué à chaque token ne peut pas
utiliser strtok. Par exemple :
strtok_example_bug.c
& % # include < stdlib .h >
# include < stdio .h >
' $
# include < string .h >
void extract_path () {
4.2 Réentrant char * string = getenv ( " PATH " );
printf ( " Parsing ’% s ’\ n " , string );
// string should contain a list of d i r e c t o r i e s s e p a r a t e d with :
// eg . / usr / local / bin :/ usr / bin :/ bin :/ usr / local / games :/ usr / games
// Extract the d i r e c t o r i e s
// eg . / usr / local / bin , / usr / bin , / bin , / usr / local / games , / usr / games
for ( char * token = strtok ( string , " : " ) ;
token ;
token = strtok ( NULL , " : " ) ){
// token co n t a in s a d i r e c t o r y ( eg . / usr / local / bin )
printf ( " \ t % s contains : " , token );
// Extract the s u b d i r e c t o r i e s
# 17 Code réentrant : code dont le résultat ne dépend pas d’un état précédent // eg . usr , local , bin
for ( char * word = strtok ( token , " / " ) ;
Ne pas maintenir d’état persistant entre les appels word ;
word = strtok ( NULL , " / " ) ){
printf ( " % s " , word );
exemple de fonction non réentrante : fread dépend de la position du curseur du flux }
printf ( " \ n " );
}
}
int main ( int argc , char ** argv ) {
extract_path ();
return 0;
}
& %
Aura pour résultat :
Parsing ’/usr/local/bin:/usr/bin:/bin:/usr/local/games:/usr/games’
Exemple : strtok. Un autre exemple de fonction non-réentrante est la fonction char* strtok(char* /usr/local/bin contains: usr local bin
str, char *delim). Cette fonction extrait des sous-chaînes d’une chaîne.
Par exemple, le code suivant affiche les différents répertoires de la variable PATH : Ici, le premier token (/usr/local/bin) est découpé en mots (usr, local, bin) par des appels suc-
cessif à strtok qui modifient l’état précédent de strtok, ce qui empêche les appels suivants à token =
strtok_example.c strtok(NULL, ":") de parcourir la chaîne string.
# include < stdlib .h >
# include < stdio .h >
# include < string .h > Rendre une fonction réentrante. Il est possible de rendre réentrante une fonction non-réentrante en
void extract_path () { ajoutant un paramètre correspondant à l’état de la fonction. Par exemple, la version réentrante de char*
char * string = getenv ( " PATH " ); strtok(char *str, const char *delim); est char* strtok_r(char *str, const char *delim, char
printf ( " Parsing ’% s ’\ n " , string );
**saveptr );
Ainsi, le programme précédent peut être corrigé en :
Télécom SudParis — François Trahay — 2019–2020 — CSC4508 – Systèmes d’exploitation 17 Télécom SudParis — François Trahay — 2019–2020 — CSC4508 – Systèmes d’exploitation 18Threads 4 Partage de données Threads
strtok_example_fixed.c • destruction :
# include < stdlib .h >
# include < stdio .h > – int pthread_key_delete(pthread_key_t *key););
# include < string .h >
• utilisation :
void extract_path () {
char * string = getenv ( " PATH " );
char * saveptr = NULL ; – void *pthread_getspecific(pthread_key_t key);
printf ( " Parsing ’% s ’\ n " , string );
– int pthread_setspecific(pthread_key_t key, const void *value);
for ( char * token = strtok_r ( string , " : " , & saveptr ) ; • initialisation :
token ;
token = strtok_r ( NULL , " : " , & saveptr ) ){
printf ( " \ t % s contains : " , token ); – int pthread_once(pthread_once_t *once_control, void (*init_routine) (void));
char * saveptr_word = NULL ;
for ( char * word = strtok_r ( token , " / " , & saveptr_word ) ;
word ;
word = strtok_r ( NULL , " / " , & saveptr_word ) ){
printf ( " % s " , word );
}
printf ( " \ n " );
}
}
int main ( int argc , char ** argv ) {
extract_path ();
return 0;
}
Qui aura pour résultat :
Parsing ’/usr/local/bin:/usr/bin:/bin:/usr/local/games:/usr/games’
/usr/local/bin contains: usr local bin
/usr/bin contains: usr bin
/bin contains: bin
/usr/local/games contains: usr local games
/usr/games contains: usr games
' $
4.3 TLS – Thread-Local Storage
' $
5 Synchronisation
Variable globale (ou locale statique) propre à chaque thread
Exemple : errno
# 18 Garantir la cohérence des données
Déclaration : Accès simultanés à une variable partagée en lecture/écriture
en C11 : _Thread_local int variable = 0; I x++ n’est pas atomique (constitué de load, update, store)
en C99 avec gcc : __thread int variable = 0; # 19 Accès simultanés à un ensemble de variables partagées
en C99 avec Visual studio : __declspec(thread) int variable = 0; I exemple : une fonction swap(a, b){ tmp=a; a=b; b=tmp; }
Plusieurs mécanismes de synchronisation
Mutex
Instructions atomiques
& % Conditions, sémaphores, etc. (cf CI3)
pthread_key. Une autre manière (plus portable, mais beaucoup plus pénible à écrire) de déclarer une
variable TLS est d’utiliser un pthread_key :
& %
• création :
Le programme suivant illustre le problème des accès simultanés à des variables partagées. Ici, deux threads
– int pthread_key_create(pthread_key_t *key, void (*destructor)(void*)); incrémentent chacun 1 000 000 000 fois la même variable :
Télécom SudParis — François Trahay — 2019–2020 — CSC4508 – Systèmes d’exploitation 19 Télécom SudParis — François Trahay — 2019–2020 — CSC4508 – Systèmes d’exploitation 20Threads 5 Synchronisation Threads 5 Synchronisation
' $
compteurBOOM.c
5.1 Mutex
/*
* compteurBOOM .c
*
* Probleme de s y n c h r o n i s a t i o n
* Type : pthread_mutex_t
*
*/
Initialisation :
# include < error .h >
# include < unistd .h > pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
# include < stdlib .h >
# include < stdio .h > int pthread_mutex_init(ptread_mutex_t *m, const
# include < pthread .h >
pthread_mutexattr_t *attr);
# 20
/* INT_MAX / 2 */
# define NBITER 1000000000 Utilisation :
int compteur = 0; int pthread_mutex_lock(pthread_mutex_t *mutex));
void * start_routine ( void * arg ) { int pthread_mutex_trylock(pthread_mutex_t *mutex);
int i ;
for ( i = 0; i < NBITER ; i ++) {
int pthread_mutex_unlock(pthread_mutex_t *mutex);
/* OOPS : FAUX acces a v a r ia b l e p a r tag ee non p rot eg ee */
compteur ++; Destruction :
}
pthread_exit ( NULL ); int pthread_mutex_destroy(pthread_mutex_t *mutex);
}
int main ( int argc , char * argv []) {
int rc ;
& %
pthread_t thread1 , thread2 ;
En utilisant un mutex, on peut corriger le programme compteurBOOM en assurant que les incrémenta-
rc = pthread_creat e (& thread1 , NULL , start_routine , NULL ); tions du compteur se font en exclusion mutuelle :
if ( rc )
error ( EXIT_FAILURE , rc , " p thread_c reate " );
compteur_mutex.c
rc = pthread_creat e (& thread2 , NULL , start_routine , NULL );
/*
if ( rc )
* compteurBOOM .c
error ( EXIT_FAILURE , rc , " p thread_c reate " );
*
* P rob le me de s y n c h r o n i s a t i o n
rc = pthread_join ( thread1 , NULL );
*
if ( rc )
*
error ( EXIT_FAILURE , rc , " pthread_join " );
*/
rc = pthread_join ( thread2 , NULL );
if ( rc )
# include < error .h >
error ( EXIT_FAILURE , rc , " pthread_join " );
# include < unistd .h >
# include < stdlib .h >
if ( compteur != 2 * NBITER )
# include < stdio .h >
printf ( " BOOM ! compteur = % d \ n " , compteur );
# include < pthread .h >
else
printf ( " OK compteur = % d \ n " , compteur );
/* INT_MAX / 2 */
# define NBITER 1000000000
exit ( EXIT_SUCCESS );
}
int compteur = 0;
p thread_mu te x_t mutex = P T H R E A D _ M U T E X _ I N I T I A L I Z E R ;
Alors que le compteur devrait valoir 2*1 000 000 000 = 2 000 000 000, l’exécution de ce programme donne
un autre résultat, par exemple : void * start_routine ( void * arg ) {
int i ;
$ ./compteurBOOM
BOOM! compteur = 1076588402 for ( i = 0; i < NBITER ; i ++) {
p th r ea d _ m u t e x _ l o c k (& mutex );
compteur ++;
p t h r e a d _ m u t e x _ u n l o c k (& mutex );
}
pthread_exit ( NULL );
}
int main ( int argc , char * argv []) {
int rc ;
pthread_t thread1 , thread2 ;
rc = pthread_creat e (& thread1 , NULL , start_routine , NULL );
if ( rc )
error ( EXIT_FAILURE , rc , " p thread_c reate " );
rc = pthread_creat e (& thread2 , NULL , start_routine , NULL );
if ( rc )
error ( EXIT_FAILURE , rc , " p thread_c reate " );
rc = pthread_join ( thread1 , NULL );
if ( rc )
error ( EXIT_FAILURE , rc , " pthread_join " );
Télécom SudParis — François Trahay — 2019–2020 — CSC4508 – Systèmes d’exploitation 21 Télécom SudParis — François Trahay — 2019–2020 — CSC4508 – Systèmes d’exploitation 22Threads 5 Synchronisation Threads
rc = pthread_join ( thread2 , NULL ); compteur_atomic.c
if ( rc )
error ( EXIT_FAILURE , rc , " pthread_join " ); /*
* compteurBOOM .c
if ( compteur != 2 * NBITER ) *
printf ( " BOOM ! compteur = % d \ n " , compteur ); * P rob le me de s y n c h r o n i s a t i o n
else *
printf ( " OK compteur = % d \ n " , compteur ); *
*/
exit ( EXIT_SUCCESS );
} # include < error .h >
# include < unistd .h >
# include < stdlib .h >
Si le résultat obtenu est correct, l’utilisation d’un mutex ralentit considérablement le programme (144s # include < stdio .h >
avec mutex, contre 4.1s sans mutex) # include < pthread .h >
/* INT_MAX / 2 */
# define NBITER 1000000000
_Atomic int compteur = 0;
void * start_routine ( void * arg ) {
int i ;
for ( i = 0; i < NBITER ; i ++) {
compteur ++;
}
pthread_exit ( NULL );
}
int main ( int argc , char * argv []) {
int rc ;
pthread_t thread1 , thread2 ;
rc = pthread_creat e (& thread1 , NULL , start_routine , NULL );
if ( rc )
error ( EXIT_FAILURE , rc , " p thread_c reate " );
rc = pthread_creat e (& thread2 , NULL , start_routine , NULL );
if ( rc )
error ( EXIT_FAILURE , rc , " p thread_c reate " );
rc = pthread_join ( thread1 , NULL );
if ( rc )
error ( EXIT_FAILURE , rc , " pthread_join " );
rc = pthread_join ( thread2 , NULL );
' $
if ( rc )
error ( EXIT_FAILURE , rc , " pthread_join " );
5.2 Opérations atomiques if ( compteur != 2 * NBITER )
printf ( " BOOM ! compteur = % d \ n " , compteur );
else
printf ( " OK compteur = % d \ n " , compteur );
exit ( EXIT_SUCCESS );
}
Opération s’exécutant de manière atomique Ici, le résultat est correct et le programme nettement plus rapide qu’en utilisant un mutex :
C11 définit un ensemble de fonctions effectuant des opérations atomiques • sans synchronisation : 4.1s
# 21 C atomic_fetch_add(volatile A *object, M operand); • avec mutex : 144s
_Bool atomic_flag_test_and_set(volatile atomic_flag *object); • avec opération atomique : 35s
C11 définit des types atomiques
les opérations sur ces types sont atomiques
déclaration : _Atomic int var; ou _Atomic(int) var;
Bibliographie du chapitre
& %
On peut corriger le programme compteurBOOM en utilisant des opérations atomiques. Pour cela, il suffit de
déclarer le compteur comme _Atomic int. L’incrémentation du compteur utilise alors l’opération atomique
atomic_fetch_add.
Télécom SudParis — François Trahay — 2019–2020 — CSC4508 – Systèmes d’exploitation 23 Télécom SudParis — François Trahay — 2019–2020 — CSC4508 – Systèmes d’exploitation 24Programmation concurrente 2 Mécanismes de synchronisation inter processus
' $
1 Introduction
Contenu de cette séance :
#2 découverte des mécanismes de synchronisation existants
mécanismes inter processus
mécanismes intra processus
étude des patterns de synchronisation les plus classiques
Programmation concurrente
& %
François Trahay
' $
2 Mécanismes de synchronisation inter processus
CSC4508 – Systèmes d’exploitation
IPC : Inter Process Communication
#3
basés sur des objets IPC dans l’OS
utilisation : souvent via une entrée dans le système de fichier
I fournit une persistance des données
& %
2019–2020
25 Télécom SudParis — François Trahay — 2019–2020 — CSC4508 – Systèmes d’exploitation 26Programmation concurrente 2 Mécanismes de synchronisation inter processus Programmation concurrente 3 Mécanismes de synchronisation intra-processus
' $ ' $
2.1 Tubes 2.3 Sémaphore
Objet constitué d’une valeur et d’une file d’attente
Fichiers spéciaux gérés en FIFO Création :
Tubes anonymes sémaphore nommé : sem_t *sem_open(const char *name, int oflag,
mode_t mode, unsigned int value);
int pipe(int pipefd[2]);
I name est une clé de la forme “/cle”
I crée un tube accessible par le processus courant
I accessible également aux futurs processus enfants sémaphore anonyme : int sem_init(sem_t *sem, int pshared, unsigned
#4 I pipefd[0] pour lecture, pipefd[1] pour écriture #6 int value);
I si pshared != 0, utilisable depuis plusieurs processus (via une mémoire
Tubes nommés partagée)
int mkfifo(const char *pathname, mode_t mode);
Utilisation :
crée une entrée dans le système de fichier accessible par n’importe quel processus int sem_wait(sem_t *sem);
Utilisation (presque) comme un fichier “normal” int sem_trywait(sem_t *sem);
lecture bloquante int sem_timedwait(sem_t *sem, const struct timespec
pas de lseek *abs_timeout);
& % & %
int sem_post(sem_t *sem);
Vous avez déja manipulé des tubes sans forcément vous en apercevoir : en bash, l’enchaînement de
commandes reliées par des pipes se fait via des tubes anonymes créés par le processus bash.
Ainsi, lorsqu’on exécute cmd1 | cmd2 | cmd3, bash crée 2 tubes anonymes et 3 processus, puis redirige
(grâce à l’appel système dup2, cf le Cours Intégré 11) les entrées et sorties standards des processus vers les
différents tubes.
' $
' $ 3 Mécanismes de synchronisation intra-processus
2.2 Mémoire partagée
Permet de partager certaines pages mémoire entre plusieurs processus
Création d’un segment de mémoire partagée de taille nulle :
int shm_open(const char *name, int oflag, mode_t mode);
I name est une clé de la forme “/cle” #7
Basés sur des objets partagés en mémoire
#5
Changement de la taille du segment :
Utilisation possible d’IPC
int ftruncate(int fd, off_t length);
Projeter en mémoire le segment :
void *mmap(void *addr, size_t length, int prot, int flags, int
fd, off_t offset);
flags doit contenir MAP_SHARED
& %
& %
Nous reverrons plus tard (lors du CI 11 sur les entrées/sorties) une autre utilisation de mmap.
Télécom SudParis — François Trahay — 2019–2020 — CSC4508 – Systèmes d’exploitation 27 Télécom SudParis — François Trahay — 2019–2020 — CSC4508 – Systèmes d’exploitation 28Vous pouvez aussi lire