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 — ii
CSC4508 – 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 — iv
CSC4508 – 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 — vi
CSC4508 – 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 — viii
Pré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 2
Pré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 4
Threads 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 6
Threads 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 8
Threads 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 10
Threads 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; i
Threads 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 14
Threads 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 16
Threads 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 18
Threads 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 20
Threads 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 22
Threads 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 24
Programmation 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 26
Programmation 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 28
Vous pouvez aussi lire